Skip to content

Give the loopback page its own entry, so it stops serving 2026's components - #32

Open
linhdmn wants to merge 2 commits into
fix/execution-rungfrom
fix/standalone-restore
Open

linhdmn wants to merge 2 commits into
fix/execution-rungfrom
fix/standalone-restore

Conversation

@linhdmn

@linhdmn linhdmn commented Oct 4, 2026

Copy link
Copy Markdown
Member

What was wrong

The opt-in loopback page (dashboard.standalone: true) served a component set frozen at 2026-09-23.

Commit 633c1e2 folded the dashboard into the DSH UI: it repointed web/build.mjs at web/entry.tsx, dropped the outfile: …, 'dashboard.js' line, and moved the self-mount plus the SSE transport out of web/app.tsx into the host-only entry — while keeping dashboard.js in assets/, because the standalone opt-in was meant to keep working. After that fold no source in the tree could build a working standalone bundle. Every component added since (08f1cb5 metrics, 237ea70 proposals) lands in web/app.tsx, which is client.js's input and not dashboard.js's.

The page was not broken, which is why nothing failed: it mounted, rendered its approval thread, workspace tree and run list, and answered decisions. It had no MEASUREMENTS pane and no PROPOSALS pane — the point of watching a run on a loopback page with no harness UI beside it.

And make dashboard-bundle could not fix it, because the builder did not write that file at all.

Measured, not reasoned

Same server, same snapshot (one carrying metrics and recommendations), two bundles, real Chromium:

bundle pane-metrics pane-proposals
committed dashboard.js (2026-09-23) 0 0
built from today's web/standalone.tsx 1 1

The staleness check missed it for the same structural reason: web/standalone.tsx was not an input of anything, because there was no such file. Run the builder and it does not touch the artefact; change the page and the hash does not move. Both halves are individually defensible and jointly invisible.

The fix

  • web/standalone.tsx (135 lines) mounts today's DashboardApp over the three endpoints the loopback server already serves — /api/events (SSE), /api/state, POST /api/approvals/:id — through the same DashboardSource seam the host entry uses. React is bundled here rather than external, which is the opposite of the host entry and for the opposite reason: there is no other React on this page. status() is deliberately absent, which useBriefsEnabled treats as UNKNOWN and renders a failed brief rather than hiding it.
  • web/build.mjs writes both artefacts from one shared esbuild literal, differing only in external. web/standalone.tsx joins SOURCE_INPUTS, so the staleness hash now covers the loopback page too.
  • Two checks, because either alone is a comment. test/assistant-ui.test.ts asserts the builder writes dashboard.js from that entry and that the hash covers it — CI, bare clone, no browser. test/e2e-standalone-panes.mjs drives the real startDashboard in real Chromium and asserts both panes appear once the server has something to show.

The e2e was verified to fail on the old artefact: it mounts the page and reports both panes absent.

One finding inside the finding

A hand-written MetricsSummary threw Cannot read properties of undefined (reading 'length') inside MetricsPanel: alerts, and each axis' latest and samples, are required fields, and a fixture that remembers only the interesting ones produces a shape the server never emits. The test now asks summarize() for its own numbers from five invented records — shorter, and the only version that cannot drift from the real shape silently.

Verified

  • make test — 726/726
  • make check — green, all drift checks (52 KNOWN-ISSUES sections agreeing, actuator tables identical in 3 copies, no dead exports)
  • make e2e-standalone-panes — mounted, both panes drawn, zero console/network errors
  • .githooks/pre-commit --all — security scan ok

KNOWN-ISSUES §1cf records the defect, the reason the fold created it, and why both existing checks were blind to it.

@linhdmn
linhdmn added this pull request to stack #38 October 5, 2026 00:36
…onents

`assets/assistant-ui/dashboard.js` had no builder after 633c1e2 repointed
`web/build.mjs` at `web/entry.tsx` and dropped its `outfile` line, while
keeping the artefact for the standalone opt-in. The fold also moved the
self-mount and the SSE transport out of `web/app.tsx` into the host-only
entry, so nothing in the tree could build a working standalone bundle any
more — and every component added since (`08f1cb5` metrics, `237ea70`
proposals) lands in `web/app.tsx`, which is `client.js`'s input and not
`dashboard.js`'s.

Measured: the committed artefact served the loopback page mounting its thread,
workspace tree and run list with `pane-metrics: 0` and `pane-proposals: 0`,
against a snapshot carrying both. Same server, same snapshot, bundle built from
today's `web/standalone.tsx`: both panes drawn.

`web/standalone.tsx` mounts today's `DashboardApp` over the three endpoints the
loopback server already serves, through the same `DashboardSource` seam the host
entry uses, with React bundled rather than external — the opposite of the host
entry, for the opposite reason (no other React on this page). `web/build.mjs`
writes both artefacts from one shared esbuild literal, differing only in
`external`, and `web/standalone.tsx` joins `SOURCE_INPUTS` so the staleness hash
covers the loopback page: it was the half of the check that let the artefact
drift for ten days without a word.

Two checks, because either alone is a comment. `test/assistant-ui.test.ts`
asserts the builder writes `dashboard.js` from that entry and that the hash
covers it — CI, bare clone, no browser. `test/e2e-standalone-panes.mjs` drives
the real `startDashboard` in real Chromium and asserts both panes appear once the
server has something to show. Verified it fails on the old artefact: that bundle
mounts the page and reports both panes absent.

The e2e's metrics fixture asks `summarize()` for its own numbers rather than
hand-writing them — a hand-built `MetricsSummary` threw
`Cannot read properties of undefined (reading 'length')` in `MetricsPanel`,
because `alerts` and each axis' `latest`/`samples` are required fields and a
fixture remembering only the interesting ones produces a shape the server never
emits.
The dead-export check flagged it, and it was right: the helper is used at the
tool boundary below and nothing outside src/plugin.ts imports it, so the export
was a surface with no user.
@linhdmn
linhdmn force-pushed the fix/standalone-restore branch from c8e621f to f74c554 Compare October 5, 2026 03:52
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.

1 participant