merge upstream/dev into feat/migrate-electric-to-zero

Resolve 8 conflicts:
- Accept upstream deletion of 3 composio_*_connector.py (unified Google connectors)
- Accept our deletion of ElectricProvider.tsx, use-connectors-electric.ts,
  use-messages-electric.ts (replaced by Zero equivalents)
- Keep both new deps in package.json (@rocicorp/zero + @slate-serializers/html)
- Regenerate pnpm-lock.yaml
This commit is contained in:
CREDO23 2026-03-24 17:40:34 +02:00
commit 5d8a62a4a6
207 changed files with 28023 additions and 12247 deletions

View file

@ -6,7 +6,6 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { loginMutationAtom } from "@/atoms/auth/auth-mutation.atoms";
import { Spinner } from "@/components/ui/spinner";
import { getAuthErrorDetails, isNetworkError } from "@/lib/auth-errors";
@ -212,15 +211,13 @@ export function LocalLoginForm() {
<button
type="submit"
disabled={isLoggingIn}
className="w-full rounded-md bg-blue-600 px-4 py-1.5 md:py-2 text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all text-sm md:text-base flex items-center justify-center gap-2"
className="relative w-full rounded-md bg-blue-600 px-4 py-1.5 md:py-2 text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all text-sm md:text-base flex items-center justify-center gap-2"
>
{isLoggingIn ? (
<>
<span className={isLoggingIn ? "invisible" : ""}>{t("sign_in")}</span>
{isLoggingIn && (
<span className="absolute inset-0 flex items-center justify-center">
<Spinner size="sm" className="text-white" />
<span>{t("signing_in")}</span>
</>
) : (
t("sign_in")
</span>
)}
</button>
</form>

View file

@ -6,7 +6,7 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { type ExternalToast, toast } from "sonner";
import { registerMutationAtom } from "@/atoms/auth/auth-mutation.atoms";
import { Logo } from "@/components/Logo";
import { Spinner } from "@/components/ui/spinner";
@ -131,7 +131,7 @@ export default function RegisterPage() {
setError({ title: errorDetails.title, message: errorDetails.description });
// Show error toast with conditional retry action
const toastOptions: any = {
const toastOptions: ExternalToast = {
description: errorDetails.description,
duration: 6000,
};
@ -289,15 +289,13 @@ export default function RegisterPage() {
<button
type="submit"
disabled={isRegistering}
className="w-full rounded-md bg-blue-600 px-4 py-1.5 md:py-2 text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all text-sm md:text-base flex items-center justify-center gap-2"
className="relative w-full rounded-md bg-blue-600 px-4 py-1.5 md:py-2 text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all text-sm md:text-base flex items-center justify-center gap-2"
>
{isRegistering ? (
<>
<span className={isRegistering ? "invisible" : ""}>{t("register")}</span>
{isRegistering && (
<span className="absolute inset-0 flex items-center justify-center gap-2">
<Spinner size="sm" className="text-white" />
<span>{t("creating_account_btn")}</span>
</>
) : (
t("register")
</span>
)}
</button>
</form>

View file

@ -0,0 +1,30 @@
import { type NextRequest, NextResponse } from "next/server";
const OAUTH_RESULT_COOKIE = "connector_oauth_result";
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ search_space_id: string }> }
) {
const { search_space_id } = await params;
const searchParams = request.nextUrl.searchParams;
const result = JSON.stringify({
success: searchParams.get("success"),
error: searchParams.get("error"),
connector: searchParams.get("connector"),
connectorId: searchParams.get("connectorId"),
});
const redirectUrl = new URL(`/dashboard/${search_space_id}/new-chat`, request.url);
const response = NextResponse.redirect(redirectUrl, { status: 302 });
response.cookies.set(OAUTH_RESULT_COOKIE, result, {
path: "/",
maxAge: 60,
httpOnly: false,
sameSite: "lax",
});
return response;
}

View file

@ -86,7 +86,7 @@ export function DocumentsFilters({
placeholder="Search types"
value={typeSearchQuery}
onChange={(e) => setTypeSearchQuery(e.target.value)}
className="h-6 pl-6 text-sm bg-transparent border-0 shadow-none focus-visible:ring-0"
className="h-6 pl-6 text-sm bg-transparent border-0 shadow-none"
/>
</div>
</div>
@ -172,7 +172,7 @@ export function DocumentsFilters({
<Input
id={`${id}-input`}
ref={inputRef}
className="peer h-9 w-full pl-9 pr-9 text-sm bg-sidebar border-border/60 focus-visible:ring-1 focus-visible:ring-ring/30 select-none focus:select-text"
className="peer h-9 w-full pl-9 pr-9 text-sm bg-sidebar border-border/60 select-none focus:select-text"
value={searchValue}
onChange={(e) => onSearch(e.target.value)}
placeholder="Search docs"

View file

@ -498,7 +498,7 @@ export function DocumentsTableShell({
checked={allMentionedOnPage || (someMentionedOnPage && "indeterminate")}
onCheckedChange={(v) => toggleAll(!!v)}
aria-label={hasChatMode ? "Toggle all for chat" : "Select all"}
className="border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary"
className="shrink-0"
/>
</div>
</TableHead>
@ -644,6 +644,16 @@ export function DocumentsTableShell({
return <StatusIndicator status={doc.status} />;
}
if (state === "failed") {
if (isMentioned) {
return (
<Checkbox
checked={isMentioned}
onCheckedChange={() => handleRowToggle()}
aria-label="Remove from chat"
className="shrink-0"
/>
);
}
return (
<>
<span className="group-hover:hidden">
@ -653,8 +663,8 @@ export function DocumentsTableShell({
<Checkbox
checked={isMentioned}
onCheckedChange={() => handleRowToggle()}
aria-label={isMentioned ? "Remove from chat" : "Add to chat"}
className="border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary"
aria-label="Add to chat"
className="shrink-0"
/>
</span>
</>
@ -665,7 +675,7 @@ export function DocumentsTableShell({
checked={isMentioned}
onCheckedChange={() => handleRowToggle()}
aria-label={isMentioned ? "Remove from chat" : "Add to chat"}
className="border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary"
className="shrink-0"
/>
);
})()}
@ -878,7 +888,7 @@ export function DocumentsTableShell({
checked={isMentioned}
onCheckedChange={() => handleCardClick()}
aria-label={isMentioned ? "Remove from chat" : "Add to chat"}
className="border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary"
className="shrink-0"
/>
) : (
<StatusIndicator status={doc.status} />
@ -986,7 +996,7 @@ export function DocumentsTableShell({
disabled={isDeleting}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeleting ? "Deleting" : "Delete"}
{isDeleting ? <Spinner size="sm" /> : "Delete"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
@ -1104,7 +1114,7 @@ export function DocumentsTableShell({
disabled={isBulkDeleting}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isBulkDeleting ? "Deleting..." : "Delete"}
{isBulkDeleting ? <Spinner size="sm" /> : "Delete"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View file

@ -35,15 +35,37 @@ import { membersAtom } from "@/atoms/members/members-query.atoms";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { Thread } from "@/components/assistant-ui/thread";
import { MobileEditorPanel } from "@/components/editor-panel/editor-panel";
import { MobileHitlEditPanel } from "@/components/hitl-edit-panel/hitl-edit-panel";
import { MobileReportPanel } from "@/components/report-panel/report-panel";
import {
CreateConfluencePageToolUI,
DeleteConfluencePageToolUI,
UpdateConfluencePageToolUI,
} from "@/components/tool-ui/confluence";
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
import { GenerateReportToolUI } from "@/components/tool-ui/generate-report";
import {
CreateGmailDraftToolUI,
SendGmailEmailToolUI,
TrashGmailEmailToolUI,
UpdateGmailDraftToolUI,
} from "@/components/tool-ui/gmail";
import {
CreateCalendarEventToolUI,
DeleteCalendarEventToolUI,
UpdateCalendarEventToolUI,
} from "@/components/tool-ui/google-calendar";
import {
CreateGoogleDriveFileToolUI,
DeleteGoogleDriveFileToolUI,
} from "@/components/tool-ui/google-drive";
import {
CreateJiraIssueToolUI,
DeleteJiraIssueToolUI,
UpdateJiraIssueToolUI,
} from "@/components/tool-ui/jira";
import {
CreateLinearIssueToolUI,
DeleteLinearIssueToolUI,
@ -96,6 +118,25 @@ import {
trackChatResponseReceived,
} from "@/lib/posthog/events";
/**
* After a tool produces output, mark any previously-decided interrupt tool
* calls as completed so the ApprovalCard can transition from shimmer to done.
*/
function markInterruptsCompleted(contentParts: Array<{ type: string; result?: unknown }>): void {
for (const part of contentParts) {
if (
part.type === "tool-call" &&
typeof part.result === "object" &&
part.result !== null &&
(part.result as Record<string, unknown>).__interrupt__ === true &&
(part.result as Record<string, unknown>).__decided__ &&
!(part.result as Record<string, unknown>).__completed__
) {
part.result = { ...(part.result as Record<string, unknown>), __completed__: true };
}
}
}
/**
* Extract thinking steps from message content
*/
@ -161,6 +202,19 @@ const TOOLS_WITH_UI = new Set([
"delete_linear_issue",
"create_google_drive_file",
"delete_google_drive_file",
"create_calendar_event",
"update_calendar_event",
"delete_calendar_event",
"create_gmail_draft",
"update_gmail_draft",
"send_gmail_email",
"trash_gmail_email",
"create_jira_issue",
"update_jira_issue",
"delete_jira_issue",
"create_confluence_page",
"update_confluence_page",
"delete_confluence_page",
"execute",
// "write_todos", // Disabled for now
]);
@ -712,6 +766,7 @@ export default function NewChatPage() {
case "tool-output-available": {
// Update the tool call with its result
updateToolCall(contentPartsState, parsed.toolCallId, { result: parsed.output });
markInterruptsCompleted(contentParts);
// Handle podcast-specific logic
if (parsed.output?.status === "pending" && parsed.output?.podcast_id) {
// Check if this is a podcast tool by looking at the content part
@ -1090,6 +1145,7 @@ export default function NewChatPage() {
updateToolCall(contentPartsState, parsed.toolCallId, {
result: parsed.output,
});
markInterruptsCompleted(contentParts);
setMessages((prev) =>
prev.map((m) =>
m.id === assistantMsgId
@ -1441,6 +1497,7 @@ export default function NewChatPage() {
case "tool-output-available":
updateToolCall(contentPartsState, parsed.toolCallId, { result: parsed.output });
markInterruptsCompleted(contentParts);
if (parsed.output?.status === "pending" && parsed.output?.podcast_id) {
const idx = toolCallIndices.get(parsed.toolCallId);
if (idx !== undefined) {
@ -1678,6 +1735,19 @@ export default function NewChatPage() {
<DeleteLinearIssueToolUI />
<CreateGoogleDriveFileToolUI />
<DeleteGoogleDriveFileToolUI />
<CreateCalendarEventToolUI />
<UpdateCalendarEventToolUI />
<DeleteCalendarEventToolUI />
<CreateGmailDraftToolUI />
<UpdateGmailDraftToolUI />
<SendGmailEmailToolUI />
<TrashGmailEmailToolUI />
<CreateJiraIssueToolUI />
<UpdateJiraIssueToolUI />
<DeleteJiraIssueToolUI />
<CreateConfluencePageToolUI />
<UpdateConfluencePageToolUI />
<DeleteConfluencePageToolUI />
<SandboxExecuteToolUI />
{/* <WriteTodosToolUI /> Disabled for now */}
<div key={searchSpaceId} className="flex h-[calc(100dvh-64px)] overflow-hidden">
@ -1686,6 +1756,7 @@ export default function NewChatPage() {
</div>
<MobileReportPanel />
<MobileEditorPanel />
<MobileHitlEditPanel />
</div>
</AssistantRuntimeProvider>
);