diff --git a/CLAUDE.md b/CLAUDE.md index 56e69c39..f04ecd6e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -343,6 +343,25 @@ fix flows to every page automatically; two hand-written HTML pages never will. `write_rows`, verbatim `["cc"|"cf"|"cs", …]`). The record schema lives in ONE place — the header comment of `js/arena-telemetry.js` — mirror any firmware change there + in `wedge-scan.py` (`CTL_TAGS`/`CTL_STATE_KINDS`). Never drain while the post-mortem owns the link (its `canPoll`). +- **Bridge-loss lifecycle (LAB-303 S1, ww18 2026-10-15) — the other fail-closed rule:** an OPEN + bridge socket that closes without `disconnect()` asking is a LOSS (`disconnect()` clears `_ws` + BEFORE `close()` — that is the discriminator; keep it). `FicTracBridgeClient` then forces apply + OFF, calls `_failPending('bridge lost')` (THE one place every request/reply waiter settles — + register any NEW request type there, e.g. #254's connector_command waiters) and only then emits + `'lost'`. `ArenaSession` → `runner.onBridgeLost(info)`: it STOPS the run when apply was on, when + `sequenceUsesBridge` finds a bridge-routed plugin command (today `fictracPluginNames`; add any new + bridge-routed plugin set there), or with `runSequence({bridgeRequired})` (the Studio passes it for + recorded runs) — via the ordinary `stop()` (serial link is fine → real STOP), never a controller + fault (no post-mortem). Otherwise a test run continues with a `bridge-lost` event. Outcome + `BRIDGE_LOST` (`summary.bridgeLost`; `run-log.js` + adapter) is NOT auto-committed, and a push + refuses a `log_export` whose name ≠ the run's `log.bridgeFile` (a restarted bridge serves another + file). Studio: persistent `#runAlert` (`Studio.showRunAlert`), not the auto-hiding `#banner`. +- **Never await a remote call before the `?p=` load (LAB-303 S2).** `initFromUrl` awaited the course + repo's GitHub `patterns/` listing first, so a stalled api.github.com request left `?p=` links + empty until a refresh. Best-effort listings are now fire-and-forget; that is safe because + pattern-preview byte sources are LAYERED (`js/studio-preview-sources.js`: builtin < shared < + colocated < uploaded — every write names its layer, arrival order never decides). The awaits + before the load are allowlisted in `tests/test-studio-preview-sources.js`. - **Wire module exports:** `js/arena-wire-g6.js` defines more than it exports — when adding encoders/decoders, add them to the export list AND a test; audit with `Object.keys(require('./js/arena-wire-g6.js'))` vs the page's `Wire.*` diff --git a/arena_studio.html b/arena_studio.html index 5e3a4a97..8eed44ca 100644 --- a/arena_studio.html +++ b/arena_studio.html @@ -820,6 +820,11 @@ #ghQuickLinks .ql-link:hover{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,transparent)} .banner{background:color-mix(in srgb,var(--warn) 12%,transparent);border:1px solid var(--warn);color:var(--warn);border-radius:8px;padding:8px 12px;margin:10px 20px;font-size:12px;display:none} .banner.show{display:block} + /* Run alert: a safety event the operator must SEE (bridge lost mid-run) — stays until + dismissed or the next run starts, unlike #banner, which later messages replace. */ + .run-alert{background:color-mix(in srgb,var(--danger) 12%,transparent);border-color:var(--danger);color:var(--danger);font-weight:600;align-items:center;gap:12px} + .run-alert.show{display:flex} + .run-alert button{margin-left:auto;flex:0 0 auto;background:transparent;border:1px solid currentColor;color:inherit;border-radius:5px;padding:1px 8px;cursor:pointer;font:inherit} /* ── Edit view: tab bar (Designer | YAML) + YAML pane ───────────── */ .edit-view .edit-tabs{display:flex;border:1px solid var(--border);border-radius:5px;overflow:hidden;flex:0 0 auto} @@ -2502,6 +2507,7 @@ +