Skip to content

dogfood: fix four verified webjs iteration-loop gaps from the tic-tac-toe session #845

Description

@vivek7405

Problem

A full-stack tic-tac-toe dogfood session (scaffolded with npm create webjs@latest, cli 0.10.32 / core 0.7.30 / server 0.8.43) reached a good final app in both dark and light themes, but the agent had to re-write code four times after writing the lit/React-shaped guess, booting it, seeing it fail in the browser, and diagnosing the webjs divergence. These are agent iteration loops in the precise sense (wrong-code, run, discover, rewrite), caused by webjs being new (no training data), not by unclear prompts.

Since the scaffold is the first-class training data for agents learning webjs, the durable fix is to make the correct idiom unmissable in what webjs create generates and in the READ-FIRST gotchas doc, plus fix the underlying framework behaviour where it is a genuine bug.

Every claim below was verified against the framework source before fixing (repros in the PR). Two of the four original claims were re-scoped after investigation; one earlier candidate (.env not gitignored) was disproved (the scaffold template has ignored .env since June, so the session diagnosis was wrong). This is deliberate: only genuine gaps are fixed.

Loop B (VERIFIED framework bug, re-scoped). A multi-hole attribute goes stale when only a later hole changes

Originally filed as "unkeyed list mis-patch". Investigation disproved that (reconcileArray is provably positional and correct for a fixed-length list). The real bug: a mixed attribute (class="base ${a} ${b}") is anchored at its first hole in render-client.js, with the later holes as noop parts. updateInstance's per-hole dirty-check then drops a change confined to a later hole, so the attribute keeps its stale value. Minimal repro: render class="s ${'A1'} ${'B1'}", then change only the second hole to 'B2'; the attribute stays "s A1 B1". This is a silent client-correctness bug affecting any element with two or more holes in one attribute. Fixed (point each non-anchor member at the anchor so any hole change rebuilds the whole attribute), with a red-before-fix regression test.

Loop A (VERIFIED, re-scoped). An interpolated <style> / <script> child is an SSR/client asymmetry

renderToString emits the interpolated content (verified), but the client parser deliberately drops a raw-text hole as noop (render-client.js L286-293: "per-render interpolation isn't supported"). So a component that renders html with a <style>${CSS}</style> paints correctly at SSR, then wipes it to empty on hydration. The client not supporting it is by design (the compile cache is keyed on strings); the gap is that it fails silently instead of being caught. Fix: a webjs check rule flagging an interpolated child of <style>/<script>, plus a gotchas entry pointing at static styles / layout CSS / css.

Loop E (VERIFIED, docs gap not a runtime change). A GET-action re-fetch returns the SSR seed

The SSR action seed (#472) is consume-once and keyed by hash + fn + args with no time bound, so the FIRST client call of a GET action consumes the SSR-time snapshot, even when that call happens after a mutation ("refresh the tally after a win" returned the pre-win value). The agent's rewrite to a deterministic optimistic bump is in fact the recommended webjs pattern, so this is a guidance gap, not a runtime bug: nothing warned that a GET-action re-read serves the seed. Fix: a gotchas entry ("a GET action's first client call resolves from the SSR seed; for a post-mutation refresh use optimistic update or revalidate(), not a re-call").

Loop H (VERIFIED scaffold gap). The generated theme apparatus fights a custom palette

The scaffold's generated app/layout.ts ships a dual .dark + [data-theme] token system and a head theme-detection script (packages/cli/lib/create.js ~L1136-1240). A custom single-theme :root palette was overridden by it (the board rendered white) until the agent stripped the whole apparatus. Complements #815/#816 (use a unique design): the wiring should not fight the design. Fix: mark the theme apparatus as one clearly-removable block in the generated layout with a one-line how-to.

Loops correctly AVOIDED this session (guidance already works, no action): the browser-test it()+chai trap (the agent checked ui: 'tdd' first, #837 item 2) and the page-action throw redirect() 307 footgun (returned an ActionResult, #837 item 1).

Scope

All four are fixed in ONE PR (no follow-up issues):

  • B: packages/core/src/render-client.js mixed-attribute commit, plus packages/core/test/rendering/ regression (red before fix) and Bun parity note.
  • A: a new webjs check rule in packages/server/src/check.js (rule table ~L58, follow no-redirect-in-api-route L138), plus agent-docs/lit-muscle-memory-gotchas.md.
  • E: agent-docs/lit-muscle-memory-gotchas.md + agent-docs/advanced.md (seed section) + a gallery note.
  • H: packages/cli/lib/create.js layout generator (removable-block comment); scaffold test asserts the marker.
  • All four also land in agent-docs/lit-muscle-memory-gotchas.md. Docs synced via webjs-doc-sync; scaffold synced + generate/boot/webjs check verified via webjs-scaffold-sync.

Acceptance criteria

  • B: a mixed attribute whose later-only hole changes is re-applied; regression test red before the fix, green after.
  • A: an interpolated <style>/<script> child fails a webjs check rule with an actionable message; counterfactual test red before the fix.
  • E: the seed foot-gun and the correct post-mutation-refresh pattern are documented; a gallery note shows the idiom.
  • H: the generated app/layout.ts marks the theme apparatus as one removable block; a scaffold test asserts the marker.
  • All four appear in agent-docs/lit-muscle-memory-gotchas.md.
  • Docs + scaffold synced; webjs create generate + boot + webjs check verified; tests at every layer touched.

Closes #845 is handled by the PR body.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions