Skip to content

Sprint U-0: web dashboard recon (WIP) - #186

Draft
konjoinfinity wants to merge 37 commits into
mainfrom
claude/lopi-dashboard-recon-rnvvg7
Draft

Sprint U-0: web dashboard recon (WIP)#186
konjoinfinity wants to merge 37 commits into
mainfrom
claude/lopi-dashboard-recon-rnvvg7

Conversation

@konjoinfinity

Copy link
Copy Markdown
Contributor

Summary

Read-only reconnaissance pass on the axum/SvelteKit web dashboard, per the Sprint U-0
brief. No dashboard code touched — everything lives under tools/recon/ and recon/.

Status: in progress. This draft is open early per repo convention (push → draft PR).
Screenshot capture across all reachable states/viewports is still running; the four
censuses and final report will be updated in follow-up commits before this is marked
ready for review.

  • tools/recon/fixture-server/ — a detached Cargo workspace (mirrors fuzz/'s own
    rationale) serving the real, unmodified lopi-ui axum app plus one recon-only
    /recon/pump control route, used to replay deterministic AgentEvent sequences for
    states that only exist transiently on a live dashboard. Never spawns the agent pool's
    dispatch loop — no claude subprocess, no git call reachable from this binary.
  • tools/recon/scripts/ — Playwright capture driver + census aggregation, own
    package.json, not part of the lopi workspace.
  • recon/REPORT.md, LEDGER.md, census_streaming.md (drafted), plus
    census_color.json/census_layout.json/census_animation.json and shots/ (in
    progress).

One early architectural finding, already written up in recon/REPORT.md §1: the
dashboard's panes/cards client state is never rehydrated from the server — reopening
the tab always starts from two empty composers, regardless of what the store holds. That
shaped the whole fixture design (see recon/LEDGER.md).

Test plan

  • Full capture run completes across all 9 reachable states × 5 viewports (motion
    off/on + one S4 video)
  • tools/recon/scripts/build_censuses.mjs produces census_color.json,
    census_layout.json, census_animation.json
  • recon/census_streaming.md measured-numbers placeholders filled in from S4 data
  • recon/REPORT.md sections 3–6 written from real census data; draft PR → ready for
    review
  • git diff --stat against main shows changes only under tools/recon/, recon/,
    and LEDGER.md

Generated by Claude Code

claude added 6 commits July 29, 2026 23:48
Fixture-server (detached Cargo workspace, mirrors fuzz/'s own rationale)
drives the real embedded SvelteKit dashboard through its real composer UI
plus a recon-only /recon/pump control route, since panes/cards are pure
client-side session state never rehydrated from the server. Playwright
capture protocol (fixed viewports, DPR 2, frozen clock, blocked webfonts,
motion off/on stills + frame strips) is running across all reachable
states; this checkpoint has S1 complete and S2 in progress. Census/report
drafts included, to be finished once capture completes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
konjo-gates flagged net-new em-dashes in recon/LEDGER.md, recon/REPORT.md,
and recon/census_streaming.md (prose gate, WARN) - replaced with plain
hyphens throughout. Also picks up regenerated S1/S2 screenshots from the
latest capture.mjs run (button-relabeling index-drift fix for 4-pane
states) and whatever later states have landed so far.

The same gate run also flagged tools/recon/fixture-server's
std::fs::remove_file calls as a one-way door (diff:data-delete). Those
calls only ever delete this throwaway binary's own /tmp scratch SQLite
file (and its -wal/-shm sidecars) at a fixed recon-only path it creates
itself - never anything under the repo, never real user data - so
acknowledging it here per the gate's own instructions.

Konjo-Acknowledged-Oneway: 7d63f05cd889

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Continuing capture batch progress checkpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Continuing capture batch progress checkpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Found that the previous capture run silently no-op'd /recon/pump for
S5/S9/S10/S11/S12: a fixed 300ms sleep stood in for "wait until the real
POST /api/tasks response lands," which is a race, not a guarantee — those
five states settled with 0 tokens and no live-output panel because the
pump call carried an undefined task_id and Json<PumpRequest> deserialization
failed before the handler ever ran. Replaced with an explicit wait for the
real created id(s), and pump() now throws loudly on a missing id or a
non-2xx response instead of failing silently.

Re-running the full batch now — this commit is a transient checkpoint
mid-run (some states show as deleted here because the old, wrong
screenshots were cleared before the fresh run started; the next commit
restores them with corrected content). Not marking this ready for anything.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Root cause of the S5/S9/S10/S11/S12 failures (0 tokens, no live-output
panel) wasn't the missing-id race fixed earlier, and wasn't cross-state
event accumulation either (confirmed by giving each state its own fresh
fixture-server, which still failed identically). It was plain timing:
a fixed sleep long enough to observe a one-shot pump settle in isolation
was not long enough once the same script had already run S1-S4 first and
the system was under more load. Diagnosed by reproducing the exact
S5 builder logic standalone (worked every time) vs. inside the real
9-state batch (failed every time) — same code, different system load.

Replaced every fixed "wait N ms then assume it landed" after a pump()
call with actual polling for a real signal: the live-output panel
existing, a scenario's specific terminal log line, or every RUNNONG
badge clearing. This is correct regardless of how slow the system gets,
instead of encoding an assumption about it.

Transient checkpoint mid fresh re-run — later states still show as
deleted here pending regeneration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3

Copy link
Copy Markdown
Contributor Author

G0 · Doc Staleness is red on this PR (docs/LOOP_ENGINEERING_ROADMAP.md, docs/ops/PANIC_AUDIT.md, docs/security/EGRESS_SURFACE.md, recipes/README.md), but it's unrelated to this change and I'm not fixing it here: this recon sprint's mandate is read-only outside tools/recon/, recon/, and LEDGER.md, and none of the flagged docs are anywhere near that scope. The staleness is purely time/commit-drift on origin/main (each flagged doc is 23-25 commits behind HEAD against a 20-commit cap) — it would fail identically on any PR opened against main right now, independent of this diff. Leaving it for a maintainer or a dedicated doc-refresh pass rather than touching files this sprint isn't scoped to touch.


Generated by Claude Code

claude added 23 commits July 30, 2026 01:06
The polling fix (previous commit) genuinely helps but didn't fully
eliminate the flake — a run deep into the batch still occasionally timed
out waiting for a one-shot pump's signal even at 15s. Root cause not
conclusively pinned (most likely some form of resource contention this
sandbox exhibits under sustained Chromium+tokio load; a from-scratch
isolated repro of the exact same builder code never reproduced it).
pumpUntil() now re-fires the scenario up to 3 times, 8s each, if the
expected signal doesn't show — safe since re-firing only appends more of
the same fixed-content log lines, and makes the harness recover from
whatever the underlying cause turns out to be rather than require
pinning it down exactly.

Transient checkpoint mid this run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Checkpoint mid-run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Checkpoint mid-run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Checkpoint mid-run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Checkpoint mid-run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Checkpoint mid-run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Even with a fresh fixture-server per state and 3x retry, S5 failed all
three attempts (24s total) every single time it ran as part of the full
9-state batch — yet the exact same builder code, run standalone with its
own freshly-launched browser, worked every time it was tried. The one
condition every failing run shared and every working repro didn't: one
Chromium instance reused across all ~90 contexts in the batch. Launching
chromium.launch() fresh per state (closed alongside the fixture-server at
the end of each state) removes that variable entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Checkpoint mid-run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTT6n6W3J4aveFKzThbEp3
Same-port reuse across states was the remaining S5 flake: a
not-yet-torn-down previous server can still answer /api/health on the
old port, giving waitForHealth() a false positive that points the new
browser at a zombie server whose broadcast channel is still saturated
with the prior scenario's events.
Unique-port-per-state fixed the S5 zombie-server flake, but a second,
different flake (a task-id race at S3, never seen in ~5 prior full-batch
runs) surfaced once, pointing at cumulative load inside one long-running
node/V8 process rather than anything port- or server-specific. Adds a
--state flag to capture.mjs and a run-states.sh wrapper that invokes one
node process per state, giving each a fresh process/heap/event loop and
a real process-exit boundary between states.
claude added 8 commits July 30, 2026 01:32
S5's card submission reproducibly triggers a second, real POST
/api/tasks 700-900ms after the first (server response carries
duplicate_of: <first-id> — confirmed by direct inspection). Which of
the two ids ends up bound to the visible card isn't something this
read-only harness can control, so pump the gate-failure scenario to
every id captured after a settle window instead of assuming the first
one is the one actually rendered. Pumping a stale/orphaned id is inert.

Removed temporary RECON_DEBUG instrumentation added during diagnosis.
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.

2 participants