git:20260813.98f3068 to git:20260906.58b8675

1 added, 0 removed. Audit A to A.

# DOX — mockups
Standalone static design/geometry mockups. One row per entry. Served via
`serve_mockup`; not built, not shipped, not imported by any package.
Pre-existing entries here predate the per-file doc protocol and are
undocumented; add a row when you touch one.
| File | Purpose |
|------|---------|
| `blackhole-settings/` | Explore-mode mockup for a proposed `pi-blackhole` dashboard plugin. Two surfaces, split by data scope. `index.html` = GLOBAL config (`settings-section` claim): scalar accordion groups + per-worker fallback-chain rail for `observer`/`reflector`/`dropper`/base `model`; `#stateSel` switches healthy / invalid-JSON / not-installed. `session-card.html` = PER-SESSION pipeline (`session-card-memory` claim, unclaimed by any plugin today): 3 density variants (B/C picked), 5 runtime states, plus a `content-view` drill-in. Scope rule: global file → settings page, `<sessionId>-pending.json` → session card. Encoded rules: unknown config keys preserved on save; unparseable config renders NO form; exact (`.exact`, from disk) vs approximate (`.approx`, dashboard-derived proxy) are visually distinct registers; the proxy never triggers an alarm. Studio + light via `data-theme`; 0 contrast failures across 8 theme/state combos. See change: add-blackhole-plugin (D6, D12, D13). |
| `chat-selection-anchor/` | Self-contained A/B repro for the chat selection-anchoring geometry (`index.html`, `Fixes: ON/OFF` via `id="fixToggle"`). Mirrors `ChatView`'s virtualized scroll container and copies `computeAnchorCorrection` behaviour from `packages/client/src/lib/chat/selection-anchor.ts` — divergence is a bug. See change: anchor-chat-selection-against-row-growth (D8); measured evidence in its `design.md` § Measured Evidence. |
+ | `gol-field/` | **SHIPPED to `site/`**: `gol.js` copied to `site/gol.js` (import repointed to `./vendor/`), `site/index.html` boots the baked `both` calibration on a second `#life` canvas (board layout, icons on, fleet thinned 22/1.15), `site/build.mjs` allowlists `gol.js`. The lab panel stays mockup-only. Candidate replacement for the pi-dashboard.dev page background: Conway's Game of Life instead of the session-slab fleet. `index.html` = full clone of `site/index.html` (assets + fonts pulled from `../../site/`, so it must be served from the REPO ROOT, not the mockup dir) with a fixed lab panel; `gol.js` = the field module, imports `../../site/vendor/three.module.min.js`. FIVE background modes in the panel: three Life grid compositions, plus `fleet` (the shipped `site/field.js`, imported unmodified for direct comparison) and `both` (Life as the raked floor + a 22-slab fleet drifting above it). `fleet`/`both` are served by a SECOND canvas `#fleet`: neither field module exposes a destroy(), so both are built once at load and the switch is `display` + `set('paused')` — re-initialising per click would leak a WebGL context. Both renderers are `alpha:true` with no `scene.background`, so the two canvases composite; `#fleet` paints last. Cost of holding two live contexts: ~58→46 fps on a real GPU, and roughly half throughput under SwiftShader — the shipped page would only ever build one. Three grid compositions via `set('layout', …)`: `stack` (default — one InstancedMesh per generation on a constant-velocity conveyor receding into fog, i.e. the board's own history), `board` (one generation flat in XZ, camera-raked, far rows ramped away from the copy), `wall` (one generation square to camera, corridor pushes cells BACK rather than shrinking them). Pointer coupling is TWO channels off one `pointermove`, scaled by the `sway` dial (default 1.6): the camera slides (translation parallax, the original behaviour) and `world` yaws −px·0.26 / pitches +py·0.11 so the field turns TOWARD the pointer. The rotation is applied to the world group, never the camera — rotating the camera swings the grid-space text corridor off the copy. Pitch is deliberately ~40% of yaw (more tips the grid off its own plane). `baseYaw` (the board layout's 0.18) is stored, not written into `world.rotation` by `build()`, or the rebuild would fight the parallax; it is declared at the top of the module because a `let` in the parallax section put `build()` in its TDZ and killed the whole field. `set('sway')` re-aims at the last known pointer, otherwise the change (including 0) is invisible until the mouse next moves. Two cell-treatment dials: **File icons** (per-cell file-type badge — Default preset baked per user calibration: mode `both` (Fleet + Life), file icons ON, iconTone 0.35, gps 2.2, depth 8, grid 30, fog 1.0, corridor 1.0, sway 1.6, mask free. 12 Pictogrammers MDI 7.4.47 glyphs (language-typescript/-javascript/-python/-go/-rust/-markdown/-css3, code-json, file-pdf-box/-word-box/-excel-box/-powerpoint-box) vendored as path data into a 4×3 canvas atlas, sampled inside `cellMat.onBeforeCompile` on the local +Z face only; 'Icon colour' dial drives a `uIconTone` uniform that desaturates toward glyph luminance AND lowers opacity (0 = mono engraving, 1 = full hue, default 0.35) so calibration never re-bakes the atlas, per-instance `iconIdx` on a per-layer `GEO_CELL.clone()` because instanced attributes live on geometry and 8 layers would fight over one array; toggle is a uniform flip, no rebuild) and **Life mask** (glyph rendered at GRID resolution, thresholded to a `Uint8Array`; outside cells are absent from the SIMULATION, not draw-culled, so gliders bounce inside the letterforms; watchdog threshold scales by mask area; substrate inside the mask rises to `MASK_FLOOR` 0.55 so the silhouette reads as a solid slab; `maskZoom` backs the camera off until the whole glyph fits AND multiplies the fog distances, or the retreated glyph sits inside the fog band; picking a mask visibly moves the corridor slider to 0). Panel gotcha that cost a debug cycle: the icon/mask rows are `.lab-seg` too — the layout switcher MUST bind `button[data-layout]`, not all seg buttons, or picking a mask calls `setLayout(undefined)` and silently pauses both fields. `window.__field` exposes the module for headless audits. House rules kept from `site/field.js`: chamfered matte slabs + clearcoat sheen (no transmission, no bloom, no env map), product tokens only, ONE accent cell (sticky while alive, never picked inside the text corridor), `[data-field]` section moods, and the ping-pong rule — scroll feeds an unsigned energy pool that scales the generation RATE, never a position. Single-layer layouts draw dead cells as a recessed substrate (`DEAD_FLOOR`); a stagnation watchdog reseeds under 7% population. Audited 60fps / 0 overflow / sticky header / no page errors at 390+1440 × dark+light. |
| `empty-model-selector.html` | State-board mockup for the openable empty `ModelSelector`. Mirrors the live component chrome (All-Providers select + Favs, filter, grouped `provider/id` rows, cap icons, token badges, footer). Shows: today's dead disabled button; state 1 refreshing (open fires `request_models`, no manual ↻); state 2 genuinely empty → `⚙ Open provider settings` link (D4-A); state 3 empty+errored → reopen-to-retry + `⚙ Providers` (D5-B); state 4 partial failure → resolved models + thin `⚠ N provider unavailable · ⚙ Providers` footer (D1-B). Studio+light via `data-theme`. See change: open-empty-model-selector. |
| `selector-decisions.html` | Decision-board mockup pairing each open question with pickable option cards (blue border = recommended). D1 per-provider refresh-error location (A full footer / B thin hint+Settings ✓ / C silent); D2 health-pill source (A on-demand / B auto-probe-on-save + manual Test ✓ / C background poll); D3 Test-button pill outcomes (Connected·N / 401 / Unreachable) with verbatim error string on a second line; D4 empty-link target (Settings→Providers ✓ / model-proxy auth); D5 empty-error retry (inline Retry / reopen-to-retry ✓). Studio+light. See change: open-empty-model-selector + surface-provider-health-in-settings. |