ReviewOS

stacks/stx

fix(hydration): a component's client scope is never registered, so its signals are undefined and it renders nothing

#2064
Closed glennmichael123 opened this 9 days ago ·
9 days ago

What happens

A component's client scope is never registered, so every signal declared in its <script client> block is undefined at evaluation time and the component renders nothing.

In HQ.training this is the first-run tour. A brand-new account is redirected to /m/welcome correctly, and then sees a blank screen.

[stx] hydration invariant failed after stx:load: 1 scope(s) left unhydrated —
      stx_onboarding_1_1n0xzc (not registered). Recovering with the ambient
      scope; please report with the page that produced it (stacksjs/stx#1773).
[stx] hydration invariant failed after stx:load: 2 expression(s) never evaluated —
      obCtx.role → obCtx is not defined | obPart → obPart is not defined.

The same two errors repeat after DOMContentLoaded.

The DOM afterwards:

{ onboarding: 1, steps: 0, activeSteps: 0, bodyText: "" }

[data-onboarding] is present; every [data-step] beneath it is gone, and the page has no text at all.

stx's message names #1773, which is closed — so this is either a regression of that class or a new instance of it.

Versions

@stacksjs/stx 0.2.481, stacks 0.75.151, Bun 1.4.3, macOS, Chrome and WebKit both.

The component

resources/components/Onboarding.stx, 585 lines, mounted as <Onboarding phone />.

The two reported identifiers are ordinary signal declarations in its <script client> block:

const obCtx = state<TourContext>(tourContext(obAuth.getUser?.()))
const obPart = state<'tour' | 'setup'>('tour')

Nothing about them looks unusual, which is why I am reporting it here rather than treating it as misuse — but I have not bisected the component, so I cannot rule out that something else in those 585 lines is what stx is choking on. If it would help, I can try to reduce it to a minimal case.

It is not page-specific

Worth separating, because it narrows where to look: the failure follows the component, not the route.

  • On /m/welcome, where <Onboarding> is the whole page, the result is a blank screen.
  • On /m, where the same component is mounted but not the main content, the page renders correctly — greeting, fitness figures, tab bar — and logs the identical two errors with a different scope id (stx_onboarding_9_h44rpf).

So the component never hydrates anywhere; it is only visible as a failure where the component is the page.

Other errors on the same page

Possibly related, possibly dev-server noise — included because I would rather over-report than hide something:

Uncaught SyntaxError: Failed to execute 'replaceChild' on 'Node': Unexpected string
    at /_stx/router.8e1509caec700289.js:5:25463
Uncaught SyntaxError: Cannot use import statement outside a module
An unknown error occurred when fetching the script.   (×12)

What I could not determine

Whether this reproduces in production. Reproduced against a local dev server only; I could not test the deployed site. Some of the errors above are the shape of a dev-mode module-loading problem, so a production build may behave differently. That is worth establishing before anyone sizes the fix.

Why it matters downstream

This is the first screen a new account sees. An app whose onboarding renders blank looks broken at exactly the moment a new user decides whether to continue.

9 days ago

Audited and verified fixed on current main. The failure class is covered by the existing component scope registration path and side-effect import regression coverage (commits 338e4ba and 03282fd). , , and pass: 30 tests, 0 failures. The reported 0.2.481 failure no longer reproduces on current stx.

9 days ago

Correction to the completion note: the verified suites were side-effect-import.test.ts, component-edge-cases.test.ts, and cached-client-component.test.ts. Result: 30 tests passed, 0 failed. The reported 0.2.481 hydration failure does not reproduce on current main.

Sign in to comment on this issue.