Round 2 fixer report for the mind-standard group. 2026-10-13. Files owned: prototype/mind.html, prototype/standard.html, prototype/server.py. Also touched, additively: prototype/data/ets.sample.json (two visitor-copy keys under venue). Not touched: shared.css, index.html, research/, brief/.
Verification: headless Chrome for Testing via Playwright, 1440x900 at DPR 2 and 390x844 at DPR 3 with mobile emulation. An offline pass (demo mode through ?demo=1, plus mocked /api/mind routes for the error, waiting, stop and reset states) and a live pass through server.py on 127.0.0.1:8777 (5 Claude exchanges: mind x3, standard dock x2, within the 8-exchange allowance). Every shot listed below was read as pixels before judging. Console: zero page errors and zero console errors in every real context; the only console lines are the browser's own "Failed to load resource" entries in the two mocked contexts where the route was deliberately aborted or answered 400. uv run --script server.py --selftest is green (scanner fuzz 400/400, sanitizer exact counts, history echo, budget, history trust, digest leak check). The server was stopped by its exact PID (44101); lsof shows 8777 free and the pid file was removed by the server's own finally.
| Decision | State in these files |
|---|---|
| 2. Dates | Hero mono line // may 3–6, 2027 · austin, tx; mind opener names Austin, May 3–6, 2027. |
| 3. One accent | No #e8a06a, rgba(232,160,106,…), --ets-ember or --ets-signal left in either page. Hover and pressed fills use var(--accent-ink) (the opening fixer added it to shared.css, #8f0c43); the mind's realm sun halo is rgba(255,243,232,.35); the standard hero gradient is #fafaf9 to #ffffff with no peach radial. |
| 4. Persona | Unnamed "the mind" in the top bar and dock head; sentence-case captions; no exclamation marks; error copy owned by the client, mapped by code. |
| 5. Head | The CSS egg is gone. Both pages carry an inline SVG mannequin cranium from the nose bridge up (smooth matte dome, gentle brow ridge, two shallow soft hollows, a soft bridge, dissolving into white through a gradient mask; no dark streaks or drips). The 24 px glyph (symbol #gHead) carries the same silhouette in the mind's top bar, the dock trigger and the dock head. The slot keeps the CSS a baked shader still would need (absolute right 6%, bottom 0, width:min(38vw,520px), bottom mask, 7 s breathe) so a still can replace the SVG without layout change; see Skipped. |
| 6. Presence | #head fades to opacity 0 over 800 ms the moment data-state leaves empty and never returns; the 24 px glyph fades into the top bar beside the brand and pulses while thinking; the top bar is an opaque var(--bg) band with an 18 px fade below it. Measured #head opacity after answers: 0 at both widths. |
| 7. Escape hatch and sample marker | Every page-backed component ends with [ open the map: lineup / agenda / venue / tickets / partners ] (text_block: [ open the map ]); session lists deep-link to standard.html#panel-dN and standard.html selects that day on load and on hashchange. Sample markers: speaker grids, session lists (· sample when the data is placeholder), partner walls, every standard section eyebrow, the hero facts strip, the merge bar. |
Decision 1 belongs to index.html; mind.html supports its hand-off: a draft in sessionStorage['ets27.draft'] (or a ?q= deep link, stripped from the URL with replaceState) lands in the composer, focused, caret at the end, and is never auto-sent.
| # | Before | After | Shots |
|---|---|---|---|
| mind phone overflow (three reports) | Document 672–741 px wide at 390; chips row never scrolled; answers clipped. | .stage, .turn, .ui use grid-template-columns:minmax(0,1fr); .thread, .turn, .ui, .comp, .composer-wrap, .opener have min-width:0; .chips has min-width:0;max-width:100%; .ask and .say use overflow-wrap:anywhere. Measured: scrollWidth = innerWidth = 390 in the empty state, after one answer and after two; 1440 stays 1440 after a 300-character unbroken question. |
r2-mind-empty-390x844.png, r2-mind-after1-390x844.png (live), r2-mind-after2-390x844.png (offline) |
| Finding | Before | After | Shots |
|---|---|---|---|
| Head shows through as a grey disc | Fixed egg at opacity .92 behind the thread. | Fades to 0 once live; glyph in an opaque top bar; thinking indicator is a breathing accent dot after the composer prompt plus the pulsing glyph. | r2-mind-after1-1440x900.png, r2-mind-topbar-scrolled-1440x900.png, r2-mind-midstream-1440x900.png |
| No per-component link to the map | Zero links in the thread. | .esc anchor after the source line in every data-bearing component (see decision 7). |
r2-mind-after1-*.png |
| New answer lands behind the sticky composer | First component at y 787 under the composer. | The turn scrolls into view (scroll-margin-top:96px) on the first caption text, on each component and after done, unless the visitor scrolled during the stream; .thread has 8 rem bottom padding and html 8 rem scroll-padding-bottom. Measured first component top 196–236 px against a composer at 790 px. |
r2-mind-after2-1440x900.png |
| Live regions | Whole thread aria-live. |
No aria-live on the thread; one visually hidden role=status written three times per turn ("Thinking", then the final caption plus "Showing 12 speakers, sample data, 4 follow-up suggestions.", or the error line); aria-busy on the streaming turn only. |
metrics in the run log |
| No timeout, no stop | UI frozen on a hung request. | AbortController per turn; // one moment after 2 s; the unavailable state after 20 s; [ stop ] beside [ send ] while busy; Escape in the composer aborts; pagehide aborts. |
r2-mind-waiting-1440x900.png |
| Chips as list items | role=list / role=listitem on buttons. |
Plain buttons in a role=group labelled "Suggested questions"; // and > are CSS alt-text / "" so they stay visual only. |
r2-mind-empty-*.png |
| Composer has no focus indicator | Outline removed, nothing else. | .composer:focus-within turns the hairline accent and adds a 1 px accent shadow (measured rgb(193,17,90)); same in the dock. |
r2-mind-composer-focus-*.png |
| Page loses its heading | Opener h1 display:none when live. |
The h1 stays as a visually hidden heading for the session; each question is an h2 (.ask); kit heads are h3; speaker names, session titles and the venue name are h4. |
metrics |
| Failed request switches to demo forever | Any non-OK response set demo mode permanently. | Demo mode only with ?demo=1 or a failed boot probe. A runtime failure renders the 4.7 state: // the mind is quiet right now — the map still works. plus a door-style Open the map card, the composer disabled with that placeholder, and /api/health re-probed with backoff (3 s doubling to 30 s); the composer re-enables without a reload (verified: re-enabled after the route was restored). 400 resets the conversation (new session id, // starting fresh …); session_full likewise; 429 is busy. The unanswered user turn is popped so the server's exact history stays matched; a retry chip ask that again is offered. |
r2-mind-unavailable-*.png, r2-mind-reset-*.png |
| Provisional components never reconcile | Streamed components stayed as rendered. | On done the final answer.ui is diffed by position against the provisional nodes (same tree kept, others replaced without the entry animation); on error they are removed, or dimmed (retired) for truncated and stopped turns. |
metrics |
| Finding | Before | After | Shots |
|---|---|---|---|
Nav wordmark boxed by .logo |
Partner tiles and the wordmark shared .logo. |
Partner tiles are .partner; computed wordmark: Unbounded 800, no border. |
r2-standard-top-1440x900.png |
| The mind absent from the hero | Peach radial and bluish wash. | The SVG head at the right third with its cut plane on the hero's lower edge, breathing; on phones behind the lockup at 60 % with a white gradient under the copy; hero gradient #fafaf9 to #fff. |
r2-standard-top-1440x900.png, r2-standard-top-390x844.png |
| Mobile menu: Register overprints Partners | Absolutely positioned .reg. |
Register is the last list row; rows are 44 px; the menu closes on link activation and on Escape (focus returns to [ menu ]); the button has aria-controls. Measured rows About 68–112 … Partners 244–288, Register 304–348. |
r2-standard-menu-390x844.png |
| Hero numerals at 16 px | Invalid clamp(2rem,1.4rem+2vw,3.4rem). |
clamp(3rem, 2rem + 4vw, 6rem): computed 89.6 px at 1440, 48 px at 390. |
r2-standard-top-*.png |
| Dock is a dialog in name only | Off-screen but focusable; aria-modal. |
Closed: inert and visibility:hidden after the slide (Tab from the trigger leaves the page; nothing inside is reachable). Open: a non-modal complementary region (aside with aria-label, no role=dialog); on desktop main gets 440 px right padding so nothing sits under the sheet; focus returns to whichever control opened it (trigger, hero link or /). |
r2-standard-dock-open-*.png, metrics |
| Phone merge hidden behind the sheet | 76 vh sheet over the merged grid. | After a merge the sheet collapses to a 57 px peek bar (caption plus [ expand ]); [ collapse ] / [ expand ] are the keyboard equivalents; the merged section scrolls under the nav (top 73 px). |
r2-standard-dock-merge-390x844.png, r2-standard-dock-peek-390x844.png |
| Desktop dock covers the merge and its clear control | Clear button at x 1254 under the dock. | [ clear ] leads the merge bar (x 48–117, dock starts at 1000). |
r2-standard-dock-merge-1440x900.png |
| Landmarks and lists | No main; divs everywhere. |
main#main wraps the hero (a section labelled by its h1) and the sections; skip link "Skip to content" to #main; lineup is ul/li with h3 names; sessions are ol/li with h3 titles; venue card titles h3; one h1. |
metrics |
| Sample markers missing | One hero note. | Every section eyebrow carries · sample: ets26 … in --muted-strong; the facts strip labels say sessions · sample etc.; the merge bar says · sample. |
r2-standard-agenda-1440x900.png |
| Second page-backed component wipes the first; merged grid duplicates the lineup | clearMerges per component; an 8-card grid inserted above 77 cards. |
Merges clear once at the start of ask. Design-direction 5.6 order: filter (speaker grids hide non-matching lineup cards; session lists select the day and hide non-matching rows; a tiered partner wall hides other tiers) with the bar // showing 12 speakers · sample and [ clear ]; reveal (venue, tickets, full partner wall) scrolls and draws a 1 px accent outline for 2 s; materialize in the sheet only when nothing on the page matches, with [ open page: … ]. Escape clears a merge first (focus to the dock input, or the section heading when the sheet is collapsed), then closes the dock. Live: 12 of 77 cards shown, 65 hidden, zero duplicate grids. |
r2-standard-dock-merge-1440x900.png |
| Finding | After |
|---|---|
| Host, Origin, Content-Type, budget | Host must be 127.0.0.1:8777 or localhost:8777 (421) on every method; a present Origin outside those two is 403; non-JSON bodies are 415; missing or non-numeric Content-Length is 411 and anything outside 1..262144 bytes is 413. Process-wide Budget: 30 turns per 10 minutes and 80,000 output tokens per day summed from usage (429 with Retry-After and a rate / budget code). A non-loopback --host is refused unless --allow-remote --token T is given, after which POST needs Authorization: Bearer T. Probed live: 200 / 404 (server.py) / 421 / 403 / 415 / 411 / 400 / 400 / HEAD 200 / NUL path 404. |
| Client-supplied assistant turns | build_messages never reads assistant content. When the stored user texts do not match, the turn starts from the new message alone and the stream opens with {"type":"notice","code":"reset"}; both clients then drop their stale local history and show // picking up fresh — earlier answers are not in context. The cap now answers with session_full (SSE error, or a 400 with that code for an oversized body). The selftest asserts a forged assistant turn never reaches the request. |
| Provisional components | At most LIMITS["components"] (4) ui events are emitted, numbered by emitted count. |
| Fallback storage | to_history_content keeps every block after the last fallback block and every earlier block that is not thinking, redacted_thinking or tool_use (selftest). |
| Body cap, socket timeout, crash paths | Handler.timeout = 20; strict length parsing; the body must be a JSON object with a string mode; static resolution wraps ValueError/OSError into 404; a bare --pid-file exits with a usage line. |
| Heartbeat | A : hb comment line goes out at most once a second on any upstream event (pings included), so a closed tab surfaces within a second and the with block closes the upstream stream. |
| Cache breakpoint | Top-level cache_control={"type":"ephemeral"} on beta.messages.stream (SDK 1.13.0 accepts it). Live turn 2 read 19,300 cached tokens and wrote 288. |
| Measured prefix | The first cold turn logs prefix_measured=19327; the startup line marks the chars/4 figure as approximate. |
say cap and error copy |
say capped at 320 characters in sanitize_answer and in the streamed deltas; error events carry a code only; reasons are logged server-side. |
| PID file | Bind first, then O_CREAT|O_EXCL; a stale file naming a live process is refused; the file is unlinked in a finally that also covers start-up failures. |
| request_id, logging, sanitizer, static | stream.request_id on every turn line with refusal and reset; missing timings print -; BadRequestError logs the status and error.type only; drops are counted once (selftest asserts exact counts, and the component-cap overflow); only the MIME allowlist is served (no .py), .mjs .woff .wasm .avif .gif .mp4 .webm added, HEAD handled, Server: ets27-proto/0.2 without the Python version. |
| Digest | The venue block uses the visitor copy; the research note and the unconfirmed street pin never reach the model (selftest asserts). |
who's speaking, the agenda, where is it, tickets, partners), [ send ], [ stop ], [ close ], [ collapse ], [ expand ], [ clear ], [ open the map ], [ menu ]; uppercase stripped from every mono element except .nav a.link and .reg; day tabs // mon 3; session meta, kv keys, tier labels and eyebrows lowercase.var(--accent-ink) (measured rgb(143,12,67) on the hero Register); footer links hover white with an underline; the reveal highlight is a static 1 px accent outline for 2 s.background:var(--bg), no backdrop-filter, hairline on .scrolled (computed blur none).rgba(255,243,232,.35); composer rule 1 px --line-strong; opener paragraph Unbounded 300 at 18 px, 40 ch.venue.public_note and venue.getting_there.public_guidance added to the fixture (additive, placeholder stays true) and rendered by both routes and the digest; the research note stays in the file for the team..card and .tcol transparent; tier titles Industry · early / · standard; incl. fees on its own line.scroll-margin-top:73px; day tabs a single nowrap scrolling row; the countdown drops to Free Your Mind below 400 px; the footer gains bottom padding on phones so the Ask pill clears the link row.tabindex, aria-controls, role=tabpanel with aria-labelledby, Arrow keys, Home, End; 44 px on coarse pointers). ArrowRight from mon 3 focuses and selects tue 4.Ask the mind → is a link to mind.html that JS upgrades to open the dock, the dock trigger is hidden under .no-js, and both pages carry a noscript line; mind.html hides the composer without JS./ ignores modified keys, IME composition and editable targets; [ shortcuts off ] in the dock foot, remembered in localStorage['ets27.shortcuts']; the 1 and 2 shortcuts were never in these files. (Verified: Meta+/ does nothing, bare / opens the dock.)var(--muted) (now 6.0:1 after the shared.css token change).//, >, // source: and → use CSS alt-text so they are not read; card portraits have empty alt and referrerpolicy=no-referrer, initials are aria-hidden, fixture URLs are used only when https:.var(--bg) band for the bar's height with a short fade below it.sessionStorage hand-off, ?q= stripped, submit handler attached before the fixture loads (an early Enter is queued, never a native GET).interactive-widget=resizes-content plus a visualViewport listener that pads the sticky composer (--kb).pointer:coarse.· sample; the speaker grid and partner wall already did.aria-label="Ask the mind" with the visible Ask, close is "Close the mind", the arrow is in an aria-hidden span; focus returns to the opener.finally when no final answer arrived; the failed ask stays visible with the retry chip.pagehide; the server heartbeat surfaces the broken pipe.| Turn | Surface | Outcome | Cache read / write | First caption | Total |
|---|---|---|---|---|---|
| 1 | mind 1440, "who's speaking" | ok, 12 speakers + choices | 0 / 19,327 (cold) | 3.1 s | 4.8 s |
| 2 | mind 1440, "what is on day 2" | ok, 10 sessions + choices | 19,300 / 288 | 1.5 s | 4.0 s |
| 3 | mind 390, "who's speaking" | ok, 12 speakers + choices | 19,327 / 0 | 1.2 s | 3.0 s |
| 4 | standard 1440, "who is speaking" | ok, filtered 12 of 77 | 19,293 / 61 | 1.2 s | 3.0 s |
| 5 | standard 390, "who is speaking" | ok, filtered 12 of 77, peek | 19,354 / 0 | 1.2 s | 3.0 s |
No refusals, fallbacks or dropped ids. Request ids are in the server log line for each turn.
prototype/shots/, prefix r2-)Live: r2-mind-empty-1440x900.png, r2-mind-midstream-1440x900.png, r2-mind-after1-1440x900.png, r2-mind-after2-1440x900.png, r2-mind-topbar-scrolled-1440x900.png, r2-mind-empty-390x844.png, r2-mind-midstream-390x844.png, r2-mind-after1-390x844.png, r2-mind-topbar-scrolled-390x844.png, r2-standard-top-1440x900.png, r2-standard-agenda-1440x900.png, r2-standard-dock-open-1440x900.png, r2-standard-dock-midstream-1440x900.png, r2-standard-dock-merge-1440x900.png, r2-standard-tickets-1440x900.png, r2-standard-footer-1440x900.png, r2-standard-hover-register-1440.png, r2-standard-top-390x844.png, r2-standard-agenda-390x844.png, r2-standard-menu-390x844.png, r2-standard-dock-open-390x844.png, r2-standard-dock-merge-390x844.png, r2-standard-dock-peek-390x844.png.
Offline (demo or mocked): r2-mind-after2-390x844.png (demo), r2-mind-composer-focus-*.png, r2-mind-unavailable-*.png, r2-mind-waiting-*.png, r2-mind-reset-*.png, r2-mind-nojs-*.png, r2-standard-agenda-d2-1440x900.png (deep link #panel-d2), r2-standard-nojs-1440x900.png.
Round-1 shots were not overwritten.
sessionStorage.setItem('ets27.draft', text) then navigate to ./mind.html (a ?q= deep link also works and is stripped). The draft lands in the composer focused; nothing is auto-sent.standard.html#panel-d1 … #panel-d4 select the day and scroll to the agenda; #lineup, #agenda, #venue, #register, #partners are the section ids the escape links use.localStorage['ets27.shortcuts'] = 'off' disables single-key shortcuts; the fork can honour the same key.shared.css: --accent, --accent-ink, --line, --line-ui, --line-strong, --muted, --muted-strong, --bg, --surface, --surface-strong, --ease-out, --ease-out-expo, --ease-in-out, the type and spacing scales. Nothing in these pages references --ets-ember, --ets-glow or --ets-signal, so the deprecated aliases can go once index.html is clear of them.prototype/assets/head-*.avif later is a one-element change.shared.css edits (delete --ets-signal, --ets-ember): not mine; the opening fixer has already removed --ets-signal and turned --ets-ember into a raspberry alias. Exact line for the orchestrator once index.html no longer references them: remove --ets-ember: var(--ets-raspberry); and --ets-glow: var(--ets-sun-halo); from :root in shared.css.done event (production-hygiene nit): kept for the prototype's own timing display in the shots and logs; drop it when the client ships.referrerpolicy=no-referrer and the https-only check are in place.count_tokens at startup: replaced by logging the first cold turn's cache write (19,327), which is the real figure and costs no boot-time network call.N sessions · 2 days. Showing cross-day matches in one list is a later change.--host … --allow-remote --token): implemented, not exercised.