ReviewOS

stacks/smakelo

Convert the client surfaces from vanilla DOM to stx signals

#25
Closed chris opened this 8 days ago ·
8 days ago

AGENTS.md is explicit: stx <script> blocks use signals (state / derived / effect) and composables, never var, document.* or window.*. Thirteen views do not follow it:

index.stx, order.stx, place-body.stx, track.stx, kitchen.stx, courier.stx, manage.stx, earnings.stx, shares.stx, orders.stx, saved.stx, claims.stx, t.stx.

Why they ended up that way. These pages are prerendered static files whose interactive parts render from JSON APIs, and the vanilla path was the one that kept working while several stx behaviours did not: per-request views dropping awaited values (stacksjs/stacks#2390), in a client script breaking the build (#2391), rendering empty in the dev server but not the build (#2392), and the client router following a link click through preventDefault (#2393). It was drift rather than a decision, and it works, which is exactly why it is worth writing down rather than leaving as an unexplained inconsistency.

Cost of fixing. Every interactive surface, with real regression risk: these are the flows that have been verified end to end in production (ordering, tracking, the kitchen board, the courier console, reviews, shares). Any conversion should go one view at a time with the browser check repeated after each.

Worth doing if the app is meant to read as a reference Stacks build, which is half its purpose. Worth deferring if the priority is the remaining feature work in #16 and #17.

Flagged during the design pre-flight in #19 rather than passed over.

8 days ago

Done. All thirteen surfaces render from signals.

What the pattern bought, beyond following AGENTS.md: the markup is markup again, so a formatter and a reviewer can see it; escaping happens on the way out rather than by remembering to call a helper (a probe with <img src=x onerror=alert(1)> in a renders it as text); and the CSRF token, visitor token, POST helper and money formatter now live in one resources/functions/session.ts instead of a dozen copies. On discover it removed a real coupling: filtering used to hide DOM nodes, so the map had to be told separately what the grid had hidden. Controls, grid and pins now read one derived list.

Two stx rules learned the hard way, both of which fail silently and are now filed as stacksjs/stacks#2394:

  1. The setup marker is stamped onto the <body> of the page being compiled. With the body inside an @included partial, stx found none: the script ran, the fetches returned 200, and not one binding applied. That reads like an API problem, not a hydration one.
  2. Signals must be declared in the page's own script. Handed back from a factory and destructured, they are invisible to the compiler. Destructuring plain functions from a module is fine, which is what let the 280 generated place views share their handlers while declaring their own state.

What still touches the DOM, and why it should: the two maps and Stripe Elements own their own DOM; one data-attribute read carries the embedded card payload; and one bridge reads the translation dictionary. No page builds UI from strings any more, and no page carries a hand-rolled escape helper.

Verified each surface against built output rather than the dev server, since that is what production serves: ordering end to end including modifiers and tip, the kitchen board with owner replies, the courier stop advancing, merchant availability toggling, payouts, claims, shares, and on a real listing the review composer correctly absent with the reason shown.

Sign in to comment on this issue.