From d6ec115348d0581fc2e6729298db7f31c776d1d6 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 7 Apr 2026 16:11:31 -0700 Subject: [PATCH 1/3] v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha --- apps/sim/app/(auth)/signup/signup-form.tsx | 11 +++-------- .../app/workspace/[workspaceId]/home/home.tsx | 12 ++++++++++-- .../w/[workflowId]/components/panel/panel.tsx | 19 ++++++++++++++++++- apps/sim/lib/posthog/events.ts | 5 +++++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 55a0508ec1b..afb27cd729a 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -270,10 +270,8 @@ function SignupFormContent({ name: sanitizedName, }, { - fetchOptions: { - headers: { - ...(token ? { 'x-captcha-response': token } : {}), - }, + headers: { + ...(token ? { 'x-captcha-response': token } : {}), }, onError: (ctx) => { logger.error('Signup error:', ctx.error) @@ -282,10 +280,7 @@ function SignupFormContent({ let errorCode = 'unknown' if (ctx.error.code?.includes('USER_ALREADY_EXISTS')) { errorCode = 'user_already_exists' - errorMessage.push( - 'An account with this email already exists. Please sign in instead.' - ) - setEmailError(errorMessage[0]) + setEmailError('An account with this email already exists. Please sign in instead.') } else if ( ctx.error.code?.includes('BAD_REQUEST') || ctx.error.message?.includes('Email and password sign up is not enabled') diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index d76f17ff454..38367339197 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -223,6 +223,14 @@ export function Home({ chatId }: HomeProps = {}) { posthogRef.current = posthog }, [posthog]) + const handleStopGeneration = useCallback(() => { + captureEvent(posthogRef.current, 'task_generation_aborted', { + workspace_id: workspaceId, + view: 'mothership', + }) + stopGeneration() + }, [stopGeneration, workspaceId]) + const handleSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() @@ -334,7 +342,7 @@ export function Home({ chatId }: HomeProps = {}) { defaultValue={initialPrompt} onSubmit={handleSubmit} isSending={isSending} - onStopGeneration={stopGeneration} + onStopGeneration={handleStopGeneration} userId={session?.user?.id} onContextAdd={handleContextAdd} /> @@ -359,7 +367,7 @@ export function Home({ chatId }: HomeProps = {}) { isSending={isSending} isReconnecting={isReconnecting} onSubmit={handleSubmit} - onStopGeneration={stopGeneration} + onStopGeneration={handleStopGeneration} messageQueue={messageQueue} onRemoveQueuedMessage={removeFromQueue} onSendQueuedMessage={sendNow} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 4d485c763ce..da51910789b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -4,6 +4,7 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react' import { createLogger } from '@sim/logger' import { History, Plus, Square } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' +import { usePostHog } from 'posthog-js/react' import { useShallow } from 'zustand/react/shallow' import { BubbleChatClose, @@ -33,6 +34,7 @@ import { import { Lock, Unlock, Upload } from '@/components/emcn/icons' import { VariableIcon } from '@/components/icons' import { useSession } from '@/lib/auth/auth-client' +import { captureEvent } from '@/lib/posthog/client' import { generateWorkflowJson } from '@/lib/workflows/operations/import-export' import { ConversationListItem } from '@/app/workspace/[workspaceId]/components' import { MothershipChat } from '@/app/workspace/[workspaceId]/home/components' @@ -101,6 +103,9 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel const params = useParams() const workspaceId = propWorkspaceId ?? (params.workspaceId as string) + const posthog = usePostHog() + const posthogRef = useRef(posthog) + const panelRef = useRef(null) const fileInputRef = useRef(null) const { activeTab, setActiveTab, panelWidth, _hasHydrated, setHasHydrated } = usePanelStore( @@ -264,6 +269,10 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel loadCopilotChats() }, [loadCopilotChats]) + useEffect(() => { + posthogRef.current = posthog + }, [posthog]) + const handleCopilotSelectChat = useCallback((chat: { id: string; title: string | null }) => { setCopilotChatId(chat.id) setCopilotChatTitle(chat.title) @@ -394,6 +403,14 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel [copilotEditQueuedMessage] ) + const handleCopilotStopGeneration = useCallback(() => { + captureEvent(posthogRef.current, 'task_generation_aborted', { + workspace_id: workspaceId, + view: 'copilot', + }) + copilotStopGeneration() + }, [copilotStopGeneration, workspaceId]) + const handleCopilotSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() @@ -833,7 +850,7 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel isSending={copilotIsSending} isReconnecting={copilotIsReconnecting} onSubmit={handleCopilotSubmit} - onStopGeneration={copilotStopGeneration} + onStopGeneration={handleCopilotStopGeneration} messageQueue={copilotMessageQueue} onRemoveQueuedMessage={copilotRemoveFromQueue} onSendQueuedMessage={copilotSendNow} diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index 537a9864282..faf9895bf62 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -378,6 +378,11 @@ export interface PostHogEventMap { workspace_id: string } + task_generation_aborted: { + workspace_id: string + view: 'mothership' | 'copilot' + } + task_message_sent: { workspace_id: string has_attachments: boolean From fb0c7c5bc6f717b083b21fcd15e750c6d235363a Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Mon, 4 May 2026 16:50:38 -0400 Subject: [PATCH 2/3] fix double wrap reponse of guest session handler --- apps/sim/app/api/auth/[...all]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/api/auth/[...all]/route.ts b/apps/sim/app/api/auth/[...all]/route.ts index b09ce7e7e67..6ff9bfd6db2 100644 --- a/apps/sim/app/api/auth/[...all]/route.ts +++ b/apps/sim/app/api/auth/[...all]/route.ts @@ -1,7 +1,7 @@ import { toNextJsHandler } from 'better-auth/next-js' import { type NextRequest, NextResponse } from 'next/server' import { auth } from '@/lib/auth' -import { createAnonymousGetSessionResponse, ensureAnonymousUserExists } from '@/lib/auth/anonymous' +import { createAnonymousSession, ensureAnonymousUserExists } from '@/lib/auth/anonymous' import { isAuthDisabled } from '@/lib/core/config/feature-flags' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -24,7 +24,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (path === 'get-session' && isAuthDisabled) { await ensureAnonymousUserExists() - return NextResponse.json(createAnonymousGetSessionResponse()) + return NextResponse.json(createAnonymousSession()) } return betterAuthGET(request) From 1c327da83262169fdc7bfa481986673a36f62a25 Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Mon, 4 May 2026 17:02:42 -0400 Subject: [PATCH 3/3] remove dead code, and fix test --- apps/sim/app/api/auth/[...all]/route.test.ts | 16 ++++++---------- apps/sim/lib/auth/anonymous.ts | 4 ---- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/apps/sim/app/api/auth/[...all]/route.test.ts b/apps/sim/app/api/auth/[...all]/route.test.ts index d9aa74cab91..f87f1a01673 100644 --- a/apps/sim/app/api/auth/[...all]/route.test.ts +++ b/apps/sim/app/api/auth/[...all]/route.test.ts @@ -8,11 +8,9 @@ const handlerMocks = vi.hoisted(() => ({ betterAuthGET: vi.fn(), betterAuthPOST: vi.fn(), ensureAnonymousUserExists: vi.fn(), - createAnonymousGetSessionResponse: vi.fn(() => ({ - data: { - user: { id: 'anon' }, - session: { id: 'anon-session' }, - }, + createAnonymousSession: vi.fn(() => ({ + user: { id: 'anon' }, + session: { id: 'anon-session' }, })), isAuthDisabled: false, })) @@ -30,7 +28,7 @@ vi.mock('@/lib/auth', () => ({ vi.mock('@/lib/auth/anonymous', () => ({ ensureAnonymousUserExists: handlerMocks.ensureAnonymousUserExists, - createAnonymousGetSessionResponse: handlerMocks.createAnonymousGetSessionResponse, + createAnonymousSession: handlerMocks.createAnonymousSession, })) vi.mock('@/lib/core/config/feature-flags', () => ({ @@ -63,10 +61,8 @@ describe('auth catch-all route (DISABLE_AUTH get-session)', () => { expect(handlerMocks.ensureAnonymousUserExists).toHaveBeenCalledTimes(1) expect(handlerMocks.betterAuthGET).not.toHaveBeenCalled() expect(json).toEqual({ - data: { - user: { id: 'anon' }, - session: { id: 'anon-session' }, - }, + user: { id: 'anon' }, + session: { id: 'anon-session' }, }) }) diff --git a/apps/sim/lib/auth/anonymous.ts b/apps/sim/lib/auth/anonymous.ts index 839e65487ec..7504ee7fd62 100644 --- a/apps/sim/lib/auth/anonymous.ts +++ b/apps/sim/lib/auth/anonymous.ts @@ -103,7 +103,3 @@ export function createAnonymousSession(): AnonymousSession { }, } } - -export function createAnonymousGetSessionResponse(): { data: AnonymousSession } { - return { data: createAnonymousSession() } -}