Repository navigation
fix(build): stop bouncing founders off their own Live dashboard (#1037) - #1039
Merged
Merged
Conversation
A founder with a genuinely registered, server-confirmed-live company was silently bounced from ?screen=live&company=<slug> back to "My companies" within ~1-2s, with no error shown. Reproduced 4/4 live on production, and 3/3 here in a real Chromium browser against a local dev server. Two independent bounce paths, both attributed by instrumenting the real dispatch sites and reading the origin back out of the browser. Neither consulted the server about the deep-linked company at all: A. ['resume-669','companies'] — the #669 active-build resume effect. `state.screen !== 'landing'` is not a usable "already has a destination" check on a fresh mount: it is ALWAYS 'landing' there, because the deep-link effect dispatches in the same effect flush and its GOTO_SCREEN only reaches `state` in the NEXT commit (the ordering #761 already documents). So the effect read the pointer on every mount, then dispatched GOTO_SCREEN(pointer.screen) once getSession() resolved — straight over the deep link's own destination. `noResumeScreens` did not list 'companies', so the pointer routinely said 'companies': the founder landed back on the exact screen they clicked "Open dashboard" on. B. ['buildapp-mycompanies','landing'] — ScreenRouter's my-companies front door. Its resolve-time re-check assumed "?screen= deep links win (they move screen off 'landing' before this fetch resolves)", but ScreenRouter is a CHILD of BuildProvider and React runs child effects BEFORE parent effects, so a fast/cached response resolves while the ref still reads 'landing'. Latency-dependent, which is what made the bounce look intermittent (delaying the response past ~400ms, the guard held). The stale per-slug cache named in the report is what makes path A reachable: the resume effect bails via `if (!saved)`, so a browser with no `ainative_build_<slug>` entry never bounced — exactly why clearing that one key worked around it. Fixes: - isSavedBuildStateComplete: the deep-link effect's `resolve-app` skip is now gated on the cache actually being complete (a real registered chatId AND the active track's completion flag) instead of merely existing. The reported entry had builtCompany/builtMVP false, appChatId/productChatId empty and generated/done empty for a company resolve-app confirmed live; that shape is written by ordinary use on any browser that merely touched a company, and proved nothing. A genuinely up-to-date cache still skips the network entirely — verified in-browser at zero added requests — so only the stale case spends the one cheap GET it needs. - isExplicitCompanyDeepLink: the URL is correct from the first byte and no commit ordering can race it, so both async effects now defer to it. shouldResumeFromPointer and shouldRouteToCompaniesIndex gather each effect's full precondition set into one pure, tested decision. - 'companies' added to noResumeScreens: like 'live', it is a browsing index, not a mid-build screen, and should never have been persisted as a resume target. The pre-existing #807/#832 `verified` guard is unaffected and now sees strictly more traffic: verified in-browser that a genuinely missing company still lands on the honest "We couldn't find a company called ..." state rather than being re-verified into a false positive. #948's auth gate and #669's bare-reload resume are both preserved and covered by tests. Verification: npx vitest run — 538 files / 6940 tests passed, 0 failures. npx tsc --noEmit — only the pre-existing .next/types error for app/api/build/ask/route.ts, confirmed identical on a clean tree.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Confirmed live in production tonight: a founder with a genuinely registered, live company gets silently bounced from their own Live dashboard back to "My companies," reproduced 4/4 times (#1037). The issue's own investigation pinpointed a contributing factor (stale local build-state cache skipping server re-validation) but the actual bounce mechanism needed further tracing.
Root cause, traced via live dispatch-site instrumentation — two independent race conditions, not the single mechanism originally suspected:
if (state.screen !== 'landing') return— useless on a fresh mount, sincestate.screenis always'landing'there (the deep-link effect's ownGOTO_SCREENdispatch doesn't reachstateuntil the next commit). The effect reads the resume pointer on every mount and, ~1-2s later, dispatchesGOTO_SCREEN(pointer.screen)directly over the deep link's real destination. Compounded bynoResumeScreensnot including'companies', so a founder sitting on the companies list with a slug still in persisted state gets bounced right back to where they came from on their very next "Open dashboard" click.my-companiesfetch can resolve before the deep-link dispatch ever commits. Latency-dependent, which is likely why it was never caught before.Fixed by making both async "helpful redirect" effects defer to an explicit
?screen=&company=deep link — the URL is correct from the first byte and no commit ordering can race it.Bonus finding (not fixed here, flagging separately): the same stale-cache condition causes
Live.tsxto fire real, paid codegen POSTs (/api/build/company-app,/api/build/company-product) on every single affected dashboard load, sinceappChatId/productChatIdstay empty after a partial restore. Worth its own issue — matches the network trace in #1037's original report.Test plan
verifiedguard and bug(security): unauthenticated visitor can reach live company generation via active-build localStorage restore #948's auth gate — confirmed this fix doesn't regress either)npx tsc --noEmit— cleanTiming note
Found and fixed hours before a product summit. Not tested against real production (no test account available in that session; local reproduction was deterministic 3/3 and gave stronger evidence via dispatch-site instrumentation than prod testing could). Recommend an out-of-band deploy once merged, given this blocks reaching the Live dashboard entirely for affected founders with no visible error.
Closes #1037