OUT-3679: drop token prop from client tree, enforce live-token reads#1203
OUT-3679: drop token prop from client tree, enforce live-token reads#1203priosshrsth wants to merge 1 commit intoanit/out-3679-update-to-latest-sdk-task-appfrom
Conversation
…eads Removes the token prop from every client-side component so the live token from useTokenRefresh becomes the single source of truth. Server fetchers still receive token via prop (they run during SSR with a fresh URL searchParam), and ClientSideStateUpdate keeps it as the seed input. Highlights: - createUploadFn now takes a token getter and resolves it at upload time, so closure-captured uploadFn references no longer go stale - TaskBoard, ActivityWrapper, Subtasks, TaskEditor, CommentCard, ReplyCard, CommentInput, ReplyInput, TaskCard, RealtimeTemplates, TaskBoardAppBridge, ManageTemplatesAppBridge, HeaderBreadcrumbs, DeletedRedirectPage, etc. all drop the token prop - TaskDataFetcher / OneTaskDataFetcher / OneTemplateDataFetcher are client fetchers with stable SWR keys; the fetcher injects the live token at request time - TaskCard reads workflowState change / assignee change tokens via requireLiveToken() instead of Redux selector Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR removes the Two P1 issues need attention before merge:
Confidence Score: 3/5Not safe to merge as-is — two P1 issues can cause render crashes and broken navigation links on first page load before the token store is seeded. Two P1 findings: a guaranteed ZodError crash in DeletedRedirectPage if the token store is not seeded at render time, and a similar first-paint issue in TemplateBoard. These are direct regressions introduced by removing the prop-based fallback without adding a null guard. src/components/layouts/DeletedRedirectPage.tsx and src/app/manage-templates/ui/TemplateBoard.tsx Important Files Changed
Sequence DiagramsequenceDiagram
participant Layout as Layout (SSR)
participant uTR as useTokenRefresh
participant Store as assemblyTokenStore (module var)
participant Redux as Redux taskBoardSlice
participant Bridge as AssemblyBridge
participant Comp as Client Component (e.g. DeletedRedirectPage)
Layout->>uTR: mount with initialToken?
alt initialToken provided
uTR->>Store: setLiveToken(initialToken) [lazy useState — synchronous]
end
uTR->>Bridge: getCurrent() + onTokenUpdate()
Bridge-->>uTR: token (async, via postMessage)
uTR->>Store: setLiveToken(next)
uTR->>Redux: dispatch(setToken(next))
Redux-->>Comp: selector change — re-render
Comp->>Store: getLiveToken() — fresh token
Note over Comp,Store: If initialToken is absent AND first render fires before Bridge callback, getLiveToken() returns undefined — crash
Reviews (1): Last reviewed commit: "fix(OUT-3679): drop token prop from clie..." | Re-trigger Greptile |
| entity?: 'Task' | 'Template' | ||
| }) => { | ||
| const tokenstring = getLiveToken() ?? z.string().parse(token) | ||
| const tokenstring = z.string().parse(getLiveToken()) |
There was a problem hiding this comment.
Uncaught ZodError if token store not yet seeded
z.string().parse(getLiveToken()) is called during the render body. getLiveToken() returns string | undefined; if the Assembly bridge's useEffect subscription hasn't delivered the first token yet (or if useTokenRefresh was called without an initialToken), liveToken is undefined. Zod will throw ZodError: Expected string, received undefined during render, crashing this page.
The previous code guarded against this with getLiveToken() ?? z.string().parse(token) — the SSR-time prop was the fallback. Removing both the fallback prop and the ?? guard turns a recoverable situation into an unhandled render crash.
| entity?: 'Task' | 'Template' | ||
| }) => { | ||
| const tokenstring = getLiveToken() ?? z.string().parse(token) | ||
| const tokenstring = z.string().parse(getLiveToken()) |
There was a problem hiding this comment.
getLiveToken() can return undefined on first render if the token store hasn't been seeded yet (e.g., useTokenRefresh was mounted without an initialToken and the effect hasn't fired). Falling back to an empty string keeps the Link intact as a no-op while avoiding a ZodError crash; the token will be present in the URL once the component re-renders after setToken is dispatched to Redux.
| const tokenstring = z.string().parse(getLiveToken()) | |
| const tokenstring = getLiveToken() ?? '' |
| href={{ | ||
| pathname: getCardHrefTemplate(template), | ||
| query: { token }, | ||
| query: { token: getLiveToken() }, |
There was a problem hiding this comment.
getLiveToken() computed at render may be undefined on first paint
getLiveToken() returns string | undefined. If this component renders before useTokenRefresh's useEffect has fired (the initial hydration pass when useTokenRefresh was not passed initialToken), the live token is still unset and getLiveToken() returns undefined. The resulting link query string will carry undefined as the token value, sending the user to a page that fails authentication.
useTokenRefresh dispatches setToken to Redux on each rotation (which triggers re-renders and refreshes the link), but the very first render before that dispatch is the risky window.
Summary
tokenprop from every client-side component so the live token fromuseTokenRefreshis the single source of truth for client calls.createUploadFnnow takes a token getter and resolves it at upload time, so closure-captureduploadFnreferences no longer go stale across the 5-min token rotation.AllTasksFetcher,WorkflowStateFetcher,AssigneeFetcher,TemplatesFetcher,ValidateNotificationCountFetcher) keeptokenas a prop — they run at SSR with a fresh URL searchParam._fetchers/*(TaskDataFetcher,OneTaskDataFetcher,OneTemplateDataFetcher) now use stable SWR keys;fetcherinjects the live token at fetch time.What this is built on
This stacks on the previous PR which introduced
assemblyTokenStore,useTokenRefresh, and the live-token-injection infetcher. With those primitives in place, this PR is the cleanup pass that removes prop drilling.Files of interest
src/utils/createUploadFn.ts—token: string→token: () => string | undefinedsrc/components/cards/TaskCard.tsx— drops Reduxtokenselector read; usesrequireLiveToken()for assignee/workflow/due-date mutations*AppBridge*,Subtasks,ActivityWrapper,TaskEditor,TaskBoard, comment/reply cluster — drop the prop(home),client,detail/[task_id],manage-templates) — stop forwardingtokento client components but still pass it to server fetchers andClientSideStateUpdateTest plan
CustomLinkafter rotation — destination page authenticatesOut of scope (follow-up)
The inline
'use server'action closures inpage.tsxfiles (e.g.updateTaskDetail,deleteTask,postAttachment,editTemplate, ...) still close over the SSR-timetoken. They will stale at minute 6 when invoked from the client. Fixing them requires either (a) addingtokenas the first arg of each inline action and having callers passrequireLiveToken(), or (b) moving them to module-scope'use server'files that read token from request headers. Recommend a separate PR.🤖 Generated with Claude Code