The wiring-lint test used percent-formatted regex patterns — UP031 under
the ruff 0.15.6 the CI pre-commit pins (the older venv binary let it
through; checked repo-wide against the exact pin now). f-strings with
doubled quantifier braces, plus one over-long fixture line in the
livepass generator split.
Copilot threads, both validated rather than blindly applied:
- closeShelf's scrim-ownership scan now skips detached entries. The
thread's throw scenario doesn't occur on the real removal path (a pane
close detaches an ANCESTOR, so _hostOf still resolves inside the
detached subtree) — but a detached shelf is genuinely not a scrim
owner, so the guard is correct beyond being defensive.
- toast.js drops the popover attribute via removeAttribute instead of
the null assignment. The claim that null leaves popover="null" is
refuted — the IDL is nullable and null removes the attribute (verified
empirically in headless Chrome) — but removeAttribute reads correct
without requiring that spec knowledge.
The models conversion dropped the legacy onclick= from the submit button
and wired detect/recalibrate/capgrid/thinking in the boot IIFE — but never
the submit itself. submitCreateModel existed with nothing calling it: Save
clicked dead with no error, exactly what a live test surfaced. None of the
gates could see it — the markup lint checks anatomy not wiring, the review
finders verified the submit function's internals, and the livepass renders
never clicked Save.
Audited every id-bearing button inside every dialog.hatch across both apps
for click wiring: model-create-submit was the only true positive (the
batch confirm buttons wire through cards.js's $() prefix helper and
new-ws-submit wires 106 lines from its getElementById — audit false
alarms). The new test_every_hatch_button_is_wired pins the class: direct
getElementById wiring or wiring through the assigned variable, with the
two prefix-built cards.js ids allowlisted; verified it fails against the
pre-fix tree. Livepass now drives the actual click: Save → busy → one PUT
→ shelf closes + toast.
The ui app has no admin pane host and new-ws is a launcher invokable from
anywhere, so every surface lands on the document-modal DIALOG tier (no
shelves). hatch.css gains the one md dialog width (560) the v1 design
specified; the ui index links hatch.css/hatch.js alongside the other
shared assets.
- New workstream: md create dialog, WS-NEW plate; the fork path keeps its
title/semantics (WS-FORK plate, skill + attach rows hidden via the
hidden attribute). Submit brackets with setBusy; errors land in the
sh-alert with the scroll-into-view rule.
- Rename: a styled prompt() — single field, Enter submits, no plate.
- Delete-workstream + revoke-MCP mirror the console confirm exactly:
danger chrome, prose body, Cancel autofocus, red filled action.
The four hand-rolled focus traps, the Escape/overlay-click dispatch and
the body-overflow lock die (native dialog + hatch.js own all of it); the
global-shortcut handler defers on dialog:modal instead of the overlay-ID
list. The forked legacy modal CSS leaves style.css (~270 lines). The
markup-shape lint now scans both index files, the asset assertion and the
parse-time-bridge guard extend to the ui app.
Dual design review of the shelf stack (one primed on intent, one cold)
adjudicated; accepted findings:
- The busy lock held the door for the mouse only: Enter on the focused
primary re-fired submits, scrim clicks closed a shelf mid-flight, and
the dialog tier's native Escape (cancel event) dismissed a busy confirm.
One capture-phase guard + a scrim busy check + cancel interception close
all three for every surface; setBusy now announces aria-busy. Contract
assertions added to the busy test (it previously claimed scrim coverage
it didn't assert).
- The MCP auth radio cards were destroyed by the .sh-body label cadence —
the exact specificity war the toggle-switch/cap exceptions guard
against, missed for .segmented-option. Restated at 0,2,1.
- Light-theme off-state toggle tracks measured ~1.2:1 (WCAG 1.4.11 wants
3:1): real ink fill on light, recessed look kept on dark.
- Failed submits on tall shelves rendered out of view — _showModalError
scrolls the alert into view. user-roles gains the missing busy bracket
and in-shelf errors instead of toast-only.
- Toasts render under the dialog tier's top layer: promote the toast to a
manual popover only while a modal dialog is open (popovers stack above
later dialogs); attribute dropped after so the everyday fade survives.
- Ambient glow now follows the kind accent (cyan/red surfaces no longer
bloom amber); skill editor's content column pins sticky so the SKILL.md
pane stays visible while the meta column scrolls; MCP-detail stacking
re-keyed from viewport to @container pane; dark-body micro-text and the
origin badge brought up to the contrast floor; quiet foot actions
(Validate regex, Detect) lifted from body-copy gray.
One chrome vocabulary (machined head/foot strips on --code-bg, kind LED,
designation plate, center-fading hairline), two mounting points:
- .hatch--shelf: pane-scoped, NON-modal (dialog.show()). Mounts inside the
pane's .hatch-host, docks right, dims only that pane via a lazy sibling
.pane-scrim, and contains focus with inert on the pane's other children —
rail/tabs/other panes stay live. Split panes work by construction; the
bottom-sheet degradation is an @container query on the pane, not a
viewport media query, so a narrow split degrades too. Controller-owned
Escape defers to any document-modal dialog stacked above.
- .hatch--dialog: document-modal (showModal()) confirm/show-once tier;
top layer stacks it above any shelf with no z-index ladder.
Smart-input primitives ship alongside (seg, chips, readout, capgrid,
match-strip, rawhatch, autofill) for the Phase-1 surfaces. data-busy locks
the container while a submit is in flight (LED pulses, dismissal refused).
Light-theme micro-text gets the .tab-menu-key one-step-up contrast pass.
Classic scripts reach the ESM controller via the window.TurnstoneHatch
bridge (toast.js pattern, handler-time only); invariants pinned in
tests/test_hatch_js.py incl. a markup-shape lint over dialog.hatch.