Skip to content

fix(build): stop bouncing founders off their own Live dashboard (#1037) - #1039

Merged
developer-ainative merged 1 commit into
mainfrom
fix/1037-stale-cache-live-bounce
Oct 9, 2026
Merged

developer-ainative merged 1 commit into
mainfrom
fix/1037-stale-cache-live-bounce

Conversation

@developer-ainative

Copy link
Copy Markdown
Contributor

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:

  • Path A: an active-build resume effect guards itself with if (state.screen !== 'landing') return — useless on a fresh mount, since state.screen is always 'landing' there (the deep-link effect's own GOTO_SCREEN dispatch doesn't reach state until the next commit). The effect reads the resume pointer on every mount and, ~1-2s later, dispatches GOTO_SCREEN(pointer.screen) directly over the deep link's real destination. Compounded by noResumeScreens not 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.
  • Path B: a second effect's guard rests on a comment claiming "deep links win because they move screen off 'landing' before this fetch resolves" — false, because React runs child effects before parent effects, so a fast/cached my-companies fetch 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.tsx to fire real, paid codegen POSTs (/api/build/company-app, /api/build/company-product) on every single affected dashboard load, since appChatId/productChatId stay empty after a partial restore. Worth its own issue — matches the network trace in #1037's original report.

Test plan

Timing 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

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.
@developer-ainative
developer-ainative merged commit 43bd12d into main Oct 9, 2026
1 check passed
@developer-ainative
developer-ainative deleted the fix/1037-stale-cache-live-bounce branch October 9, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live dashboard unreachable for a registered company when local build-state cache is stale (silent bounce to My companies)

1 participant