Skip to content

Commit bf7c377

Browse files
fix(chat): resolve the landing route without blocking on the database
Server-resolving the first workflow meant a session lookup, an access check and a query had to finish before anything rendered. A slow or unreachable database left the user on a blank page under a populated sidebar — worse than the instant redirect it replaced, and with no signal that anything was wrong. Redirect straight to `/w` instead and let it pick from the workflow list the layout already prefetches, so the choice costs no round trip and cannot hang. Repoints the sidebar's primary action rather than hiding it: the slot that offered "New chat" now offers "New workflow" and creates one, since with Chat off there is no composer to open but the intent is the same. Sends the CLI key handoff to signup rather than login. It is reached from a terminal — usually the setup wizard standing up a fresh self-host — where the visitor has no account yet. Both auth pages cross-link carrying the callback, so a returning user is one click from login with their destination intact. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ErcRgvi7VQBeKDQ3MBMha
1 parent 0c520bf commit bf7c377

5 files changed

Lines changed: 23 additions & 54 deletions

File tree

apps/sim/app/cli/auth/page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ export const dynamic = 'force-dynamic'
1919
/**
2020
* Browser half of the CLI key handoff.
2121
*
22-
* Signed-out visitors bounce through login carrying a *re-serialized*
22+
* Signed-out visitors bounce through signup carrying a *re-serialized*
2323
* `callbackUrl` — only the params the handoff understands survive, so the round
2424
* trip cannot be used to smuggle anything else back into this page. The request
2525
* is validated before that bounce: a bogus callback is rejected here rather
2626
* than after making the user sign in for nothing.
27+
*
28+
* Signup rather than login because this page is reached from a terminal: the
29+
* setup wizard sends people here while standing up a self-host, and someone
30+
* configuring Sim for the first time has no account yet. Both auth pages
31+
* cross-link carrying the same `callbackUrl`, so a returning user is one click
32+
* from login with their destination intact.
2733
*/
2834
export default async function CliAuthPage({
2935
searchParams,
@@ -43,7 +49,7 @@ export default async function CliAuthPage({
4349
challenge: resolution.request.challenge,
4450
pairing: resolution.request.pairing,
4551
})
46-
redirect(`/login?callbackUrl=${encodeURIComponent(`/cli/auth?${query}`)}`)
52+
redirect(`/signup?callbackUrl=${encodeURIComponent(`/cli/auth?${query}`)}`)
4753
}
4854

4955
return (
Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,21 @@
11
import { redirect } from 'next/navigation'
2-
import { getSession } from '@/lib/auth'
32
import { isChatEnabled } from '@/lib/core/config/env-flags'
4-
import { getFirstWorkflowIdForWorkspace } from '@/lib/workflows/queries'
5-
import { getWorkspaceHostContextForViewer } from '@/lib/workspaces/host-context'
63

74
/**
85
* Resolves the workspace landing route. With Chat enabled that is the chat
9-
* composer; otherwise it is the workspace's first workflow, resolved here so the
10-
* browser makes a single server redirect instead of bouncing through `/w`, which
11-
* would mount a client component and flash a spinner before redirecting again.
6+
* composer; otherwise `/w`, which selects the first workflow from the workflow
7+
* list the layout already prefetched.
128
*
13-
* Access is checked before resolving: this page and the layout render
14-
* concurrently, so redirecting first would put a real workflow id in a
15-
* non-member's URL bar and history before the layout denies them. `getSession`
16-
* and `getWorkspaceHostContextForViewer` are both request-memoized, so the
17-
* checks are shared with the layout rather than duplicated.
9+
* Deliberately does no work of its own. Resolving the workflow here would mean
10+
* a session lookup, an access check, and a query before anything renders — and
11+
* a slow database would leave the user on a blank page instead of a redirect,
12+
* since there is nothing to show until all three finish.
1813
*/
1914
export default async function WorkspacePage({
2015
params,
2116
}: {
2217
params: Promise<{ workspaceId: string }>
2318
}) {
2419
const { workspaceId } = await params
25-
26-
if (isChatEnabled) {
27-
redirect(`/workspace/${workspaceId}/home`)
28-
}
29-
30-
const session = await getSession()
31-
if (!session?.user) {
32-
redirect('/login')
33-
}
34-
35-
const hostContext = await getWorkspaceHostContextForViewer(workspaceId, session.user.id)
36-
if (!hostContext) {
37-
// The layout renders WorkspaceAccessDenied for this case.
38-
return null
39-
}
40-
41-
const workflowId = await getFirstWorkflowIdForWorkspace(workspaceId)
42-
redirect(workflowId ? `/workspace/${workspaceId}/w/${workflowId}` : `/workspace/${workspaceId}/w`)
20+
redirect(`/workspace/${workspaceId}/${isChatEnabled ? 'home' : 'w'}`)
4321
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -733,12 +733,14 @@ export const Sidebar = memo(function Sidebar({
733733
const topNavItems = useMemo(
734734
() =>
735735
[
736+
// Same slot either way: the primary "start something new" action. With
737+
// Chat off that is a workflow, since there is no composer to open.
736738
{
737739
id: 'home',
738-
label: 'New chat',
739-
icon: Home,
740-
href: `/workspace/${workspaceId}/home`,
741-
hidden: !isChatEnabled,
740+
label: isChatEnabled ? 'New chat' : 'New workflow',
741+
icon: isChatEnabled ? Home : Plus,
742+
href: isChatEnabled ? `/workspace/${workspaceId}/home` : undefined,
743+
onClick: isChatEnabled ? undefined : createWorkflow,
742744
},
743745
{
744746
id: 'search',
@@ -755,7 +757,7 @@ export const Sidebar = memo(function Sidebar({
755757
hidden: permissionConfig.hideIntegrationsTab,
756758
},
757759
].filter((item) => !item.hidden),
758-
[workspaceId, openSearchModal, permissionConfig.hideIntegrationsTab]
760+
[workspaceId, openSearchModal, createWorkflow, permissionConfig.hideIntegrationsTab]
759761
)
760762

761763
const workspaceNavItems = useMemo(

apps/sim/lib/workflows/queries.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,6 @@ function scopeCondition(
6464
return and(base, isNull(workflow.archivedAt))
6565
}
6666

67-
/**
68-
* The workspace's first active workflow in list order, or `null` when it has
69-
* none. Shares {@link orderByClause} with {@link listWorkflowsForUser} so the
70-
* workspace landing route resolves to the same workflow the sidebar lists
71-
* first. Performs no auth checks — callers enforce workspace access before
72-
* invoking.
73-
*/
74-
export async function getFirstWorkflowIdForWorkspace(workspaceId: string): Promise<string | null> {
75-
const [row] = await db
76-
.select({ id: workflow.id })
77-
.from(workflow)
78-
.where(and(eq(workflow.workspaceId, workspaceId), isNull(workflow.archivedAt)))
79-
.orderBy(...orderByClause)
80-
.limit(1)
81-
return row?.id ?? null
82-
}
83-
8467
/**
8568
* Lists workflows visible to a user as the contract wire shape, shared by the
8669
* `GET /api/workflows` route and the workspace sidebar prefetch. Performs no auth

scripts/setup/cli-auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export async function browserKeyFlow(origin: string): Promise<string | null> {
7676
'Confirm this code in your browser'
7777
)
7878
p.log.info(
79-
`Opening your browser — sign in and approve; the key comes back automatically.\n If it doesn't open: ${link(authUrl, authUrl)}`
79+
`Opening your browser — create your account (or sign in) and approve; the key comes back automatically.\n If it doesn't open: ${link(authUrl, authUrl)}`
8080
)
8181
openBrowser(authUrl)
8282

0 commit comments

Comments
 (0)