ohm: migrate orphaned subagent transcripts (0019.x, 0026.x) + renumber dup 0046.0 -> 0073

Backfills transcripts that existed only locally in ohm-infra:
- 0019.1/.2/.3 - UX-polish wave subagent transcripts (driver 0019.0 never finalized)
- 0026.1-.9 - security-audit-0026 subagent transcripts (driver abandoned/closed-out by 0068; audit drove published 0030 remediation)
- 0073.0 - PPE/progressive-delivery + engineering-handbook session, originally drafted as a duplicate 0046.0; reassigned next free number (0072 taken by a concurrent session)

sessions.json: add 0019/0073 titles, update 0026 title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-05 03:28:59 -07:00
parent 4521fdebc0
commit 9e6756f678
14 changed files with 1727 additions and 1 deletions
@@ -0,0 +1,158 @@
# SESSION-0019.1 — App.css + index.css token sweep
Parent: SESSION-0019.0-TRANSCRIPT-… (UX-polish wave, rfc-app v0.21.0, roadmap item #31)
## Goal
Sweep `frontend/src/App.css` (2434 lines) and `frontend/src/index.css`
onto the design-token system established in `frontend/src/styles/tokens.css`:
hardcoded hex → nearest semantic/primitive token, font-size literals →
`--text-*` scale, border-radius literals → `--radius-*` scale, safe
spacing where it maps cleanly, a coherent transition vocabulary + a
consistent `:focus-visible` ring, and base rules pointed at semantic
tokens. Hard constraint: never rename/remove a selector (parallel
siblings reference these class names); values + additive rules only.
## Pre-state
- Worktree: `/Users/benstull/git/rfc-app-v0.21.0-appcss`
- Branch `feature/v0.21.0-appcss-sweep`, based on `5be2c48` (token-module
foundation).
- index.css used literal `#1a1a1a` / `#fafaf8` / inline font stack.
- App.css had ~98 distinct hex (66× `#fff`, 51× `#1a1a1a`, plus a long
tail of near-duplicate grays: #888/#999/#777/#666/#555 etc.), 16
distinct font-size px values, 12 distinct radius px values.
## Turn-by-turn arc
1. Read tokens.css (the foundation) + both target files in full
(App.css needed two reads — 2434 lines). Confirmed branch/base.
2. Extracted the full distinct-hex inventory via grep+uniq across both
files to build the mapping deterministically rather than guessing.
3. Round-1 script (`/tmp/sweep.py`): regex word-boundary,
case-insensitive, longest-key-first replacement of all hex that map
cleanly to a ramp step or status/accent token. 615 replacements.
- One dict-key collision worth noting: `#ddd` appears as both a
gray-300 candidate and an on-dark-muted candidate. Resolved to
`--c-gray-300` (the consolidation target); the header user-name
`#ddd`-on-dark shifts imperceptibly to #d1d5db.
4. Re-grepped remainder; 35 distinct hex left — all genuinely off-token
(true blues, violet tints/shades, functional state dots, deep
diff-contrast shades, warm oranges with no token).
5. Round-2 script (`/tmp/sweep2.py`): conservative — only the 7 hex that
are unambiguously the same ROLE as an existing token and visually
near it (light-red error bgs/borders → danger-bg/-border, warm soft
bgs → warning-bg-soft). 15 replacements. Left everything else.
6. index.css base `:root``var(--font-sans)`, `var(--color-text)`,
`var(--color-bg)`.
7. Round-3 script (`/tmp/sweep3.py`): font-size px → `--text-*` (280),
border-radius px → `--radius-*` (128), `rgba(255,255,255,.15/.25)`
`--color-on-dark-soft/-hover` (5), the three 48px header-band values
`--header-height` (3). Note: script rewrite invalidated an Edit I
had queued for `.app-header`; re-read + re-applied.
8. Promoted `.app-header` to semantic `--color-header-bg` /
`--color-text-inverse`.
9. Appended (not interleaved) the interaction-polish layer: a
transition vocab on hover-reactive selectors + one `:focus-visible`
ring using `--color-focus-ring`. Additive only — no resting-state
change, reduced-motion handled by the token module's media query.
10. Build: first attempt failed on `VITE_APP_NAME is required` (env
gate, not CSS). Re-ran with `VITE_APP_NAME=OHM` → built in 249ms,
only the expected ~500kB chunk-size warning. CSS is sound.
11. Committed; wrote this transcript.
## Hex → token MAPPING TABLE
Grays (→ neutral ramp):
| literal(s) | token | note |
|---|---|---|
| #ffffff, #fff | --c-white | |
| #fafafa, #fafaf8, #fafaf9, #fafbfc, #f9f9f9, #f9fafb | --c-gray-50 | |
| #f3f4f6, #f5f5f5, #f6f6f4, #f7f7f5, #f7f6f0 | --c-gray-100 | |
| #f0f0ee, #fcfcfb | --c-gray-150 | warm canvas |
| #e5e5e5, #e5e7eb, #e5e5e0, #e7e5e4, #eee | --c-gray-200 | |
| #d1d5db, #d4d4d4, #ddd, #ccc | --c-gray-300 | **#ccc#d1d5db slightly lighter; #ddd#d1d5db imperceptible** |
| #9ca3af, #aaa, #b0b0b0, #94a3b8 | --c-gray-400 | #94a3b8 is slightly cooler→neutral, minor |
| #6b7280, #888, #999, #777, #666, #555 | --c-gray-500 | **#555#6b7280 and #999#6b7280 are the most noticeable gray shifts (the desired consolidation)** |
| #4b5563, #444, #475569 | --c-gray-600 | **#444#4b5563 noticeable; #475569#4b5563 slightly warmer** |
| #374151, #333 | --c-gray-700 | **#333#374151 slightly cooler, noticeable** |
| #1f2937, #222 | --c-gray-800 | **#222#1f2937 noticeable** |
| #111 | --c-gray-900 | |
| #1a1a1a | --c-ink (header→--color-header-bg, base text→--color-text) | |
Accent / status (→ family tokens):
| literal | token |
|---|---|
| #5b5bd6 | --c-accent |
| #4338ca | --c-accent-strong |
| #7c3aed | --c-violet |
| #166534 | --c-success-fg |
| #dcfce7 | --c-success-bg |
| #991b1b | --c-danger-fg |
| #b91c1c | --c-danger-fg-strong |
| #fef2f2, #fff5f5 | --c-danger-bg |
| #fecaca, #fca5a5, #fcc | --c-danger-border |
| #fee2e2, #fee | --c-danger-bg (light red error bg; **slightly pinker→#fef2f2**) |
| #92400e | --c-warning-fg |
| #b45309 | --c-warning-accent |
| #fef3c7 | --c-warning-bg |
| #fffbeb, #fff7ed, #fff8e0 | --c-warning-bg-soft (**#fff7ed warm→#fffbeb, minor**) |
Font-size (→ --text-* scale; round-to-nearest flagged):
10→2xs · 11→xs · 12→sm · 13→base · 14→md · **15→lg(16)** ·
16→lg · **17→xl(18)** · 18→xl · **19→xl(18)** · 22→2xl ·
**24→2xl(22)** · **26→3xl(28)** · 28→3xl. 9px left as literal
(below the 10px scale floor — the .beta-chip micro-tag).
Border-radius (→ --radius-* scale):
2→xs · **3→sm(4)** · 4→sm · **5→md(6)** · 6→md · 8→lg ·
**10→xl(12)** · 12→xl · **14→xl(12)** · **20→pill** (model-pill,
already pill-shaped at its height) · 99→pill · 999→pill. `50%`
(circle dots) left untouched.
On-dark + layout:
rgba(255,255,255,0.15)→--color-on-dark-soft;
rgba(255,255,255,0.25)→--color-on-dark-hover;
48px header band (×3)→--header-height.
rgba(255,255,255,0.08) (header link hover) LEFT — no token.
Left as literals (no clean token, intentional): blues #3b82f6 #2563eb
#1e40af #dbeafe #bfdbfe #eff6ff #eef2ff #c7d2fe; violet tints/shades
#c4b5fd #ede9fe #ddd6fe #faf5ff #4c1d95 #3737a0 #3730a3; status dots /
functional #ef4444 #dc2626 #fbbf24 #f59e0b #d97706 #10b981; deep
diff-contrast #14532d #7f1d1d #78350f #bbf7d0 #f0fdf4; warm/misc
#9a3412 #c2410c #4a3f00 #cb6a6a #cfc8a8 #e6dca0 #fde68a #fdba74
#fed7aa #f1f5f9. Spacing (--space-*) left untouched this pass —
prioritized color/type/radius per the brief; no spacing literal was
both clearly mappable and zero-layout-risk enough to be worth it.
## Cut state
- Branch: `feature/v0.21.0-appcss-sweep`
- Final commit: `cbf02d5507f91cc5bf16a68b3f8a8b6b52990849`
- Files changed: `frontend/src/App.css`, `frontend/src/index.css` (only).
- Replacements: ~630 color, 280 font-size, 128 radius, 5 on-dark, 3
header-height. Build green (`VITE_APP_NAME=OHM npm run build`, 249ms,
expected chunk-size warning only).
## What the driver needs to know
- No selector renamed/removed — safe for parallel sibling subagents.
- Gray consolidation deliberately shifts a handful of mid-grays by a
visually-noticeable amount (#555/#999→gray-500, #444→gray-600,
#333→gray-700, #222→gray-800). This IS the intended polish; operator
should eyeball muted-text and secondary-button text before deploy.
- 15px reading-body → 16px (lg): markdown/entry/landing body text gets
1px larger. Intentional rounding; check it reads well.
- The appended polish layer adds a global `:focus-visible` ring and
transitions to broad element selectors (button/a/input/…). Low risk
(focus-visible never fires on pointer), but it is the one place that
touches elements beyond the named classes.
## §19.2 candidates
- The on-dark translucent-white pattern (`--color-on-dark-soft/-hover`)
and the focus-ring/transition vocabulary are deployment-agnostic and
would belong in a future `flotilla-core`-adjacent shared UI layer if
rfc-app's frontend ever extracts one — but that's an rfc-app concern,
not flotilla. No flotilla-side extraction implied by this work.
@@ -0,0 +1,96 @@
# SESSION-0019.2 — header rename + inbox icon + light inbox UX
Parent: SESSION-0019.0-TRANSCRIPT-… (UX-polish wave, rfc-app v0.21.0)
Subsession: 0019.2
Window: 2026-05-28T11-27 → 2026-05-28T11-31 (PST)
## Goal
Roadmap #24 (rename), #25 (inbox icon + light inbox UX), and the
header's share of #31 polish — confined to the App.jsx header region,
Inbox.jsx, and a new Inbox.css. No version/changelog/tag/merge/push.
## Pre-state
- Branch `feature/v0.21.0-header-inbox` at the foundation commit (token
module `frontend/src/styles/tokens.css` present).
- Header link text was "About" (already routing to /philosophy).
- Inbox trigger used a 📮 emoji with title "Notifications inbox (§15.2)".
- Inbox.jsx: working panel; row click marks-read + navigates; one
"Mark all read (under filter)" button; terse empty copy; no per-row
mark-read; unread indicated only by `.inbox-row.unread` bg tint.
- No Amplitude `track()` calls inside Inbox.jsx (only api.js calls:
listNotifications, markNotificationRead, markNotificationsReadByFilter).
So "preserve §15 tracking" = don't break the data path; nothing to add.
## Turn-by-turn arc (honest)
1. Read tokens.css, the App.jsx header block (~150238), Inbox.jsx full,
and the §15 inbox block of App.css (15731641) to learn existing class
names + property values before touching anything.
2. #24: one-word change "About" → "Philosophy" (kept the title attr).
3. #25 icon: replaced `<span>📮</span>` with a hand-rolled inline SVG
envelope (18×18, viewBox 0 0 24 24, stroke=currentColor, ~1.75 stroke,
rounded). Set `aria-label="Inbox"`, retitled "Inbox (§15.2)". Badge
block untouched.
4. #25 UX: refactored Inbox.jsx. Extracted `markOneRead(item)` (used by
both row-click and the new per-row affordance) which also decrements
the local unreadCount. Added an `inbox-unread-dot` span + `read`/`unread`
row class, a per-row "Mark as read" button (inline check SVG) that sits
OUTSIDE the Link and `preventDefault/stopPropagation`s so it marks
without navigating, shown only for unread rows. Simplified the
mark-all label to "Mark all read" with an explanatory title. Replaced
empty copy with a "You're all caught up." state that branches on
whether filters are active.
5. Wrote Inbox.css (tokenized). Discovered via import order that Inbox.jsx
is imported at App.jsx line 6, BEFORE App.css at line 30 — so under
ESM depth-first eval Inbox.css injects FIRST and App.css wins ties.
Wrote override rules one notch more specific (`.inbox-list .inbox-row…`)
and `.inbox-empty .muted` to beat global `.muted`. New classes need no
guard. All values via var(--space/-text/-color/-radius/-motion/-ease).
6. Build: first `npm run build` failed — VITE_APP_NAME required (config
guard, unrelated to my change; no .env in worktree). Re-ran with
`VITE_APP_NAME="OHM RFC" npm run build` → green (expected ~971kB
chunk warning only).
7. Verified `git status` shows only App.jsx + Inbox.jsx modified and
Inbox.css new (node_modules symlink left untracked). Committed exactly
those three.
## Cut state
- Branch: `feature/v0.21.0-header-inbox`
- Final commit: 317738ed7998a7af5abe0b64769941cb11808ab2
- Files changed: frontend/src/App.jsx (header region only),
frontend/src/components/Inbox.jsx, frontend/src/components/Inbox.css (new)
- Build: green (chunk-size warning only).
## What the driver needs to know (inbox UX review pointers)
The inbox pass is LIGHT — same layout, panel, filters, bundle toggle,
deep-links, and API calls as before. Exactly what changed for review:
- Unread/read: unread rows now get an accent left-bar (inset box-shadow)
+ warm tint + a filled accent dot + medium-weight summary; read rows
drop the dot (space reserved, stays aligned) and mute the summary.
- Per-row affordance: a check-icon button appears on row hover/focus
(always visible on touch via `@media (hover:none)`) that marks the row
read without navigating. Uses the same markOneRead path.
- Mark-all: relabeled "Mark all read" (+ title clarifying it's
filter-scoped). Same `markNotificationsReadByFilter` call, same
disabled-when-all-read guard.
- Empty state: "You're all caught up." with filter-aware sub-copy.
- Behavior preserved: row-click still marks-read then navigates +
closes; unreadCount now also decremented locally on single marks (was
only reset to 0 on mark-all before — minor, improves badge accuracy,
SSE still authoritative per §15.3).
## §19.2 candidates
- Full inbox redesign deferred to a #25-followup: the operator's
reference screenshot did not transmit, so this stayed a polish pass
within the existing structure rather than a re-layout. Revisit with
the screenshot.
- The inbox-trigger / inbox-* base rules still live in App.css with
hardcoded hex (a sibling is tokenizing App.css this wave). If that
sweep lands, the few overrides in Inbox.css could shed their
extra-specificity guards once import order is normalized.
@@ -0,0 +1,141 @@
# Session 0019.3 — Transcript
Parent: SESSION-0019.0-TRANSCRIPT-… (UX-polish wave, rfc-app v0.21.0)
Subagent ordinal: .3 — roadmap item #32 (session/transcript page
polish) + the /docs surfaces' share of #31.
## Goal
In the isolated worktree `/Users/benstull/git/rfc-app-v0.21.0-docs`
on branch `feature/v0.21.0-docs-32`:
(a) Collapse single-transcript session roots — render the transcript
inline at `/docs/sessions/:nnnn` instead of a dead-end placeholder;
multi-transcript sessions inline the `.0` driver transcript and
list the siblings.
(b) Add a transcript metadata header (title, started/ended parsed from
the filename ISO segments, derived duration, optional `tldr` from
the manifest, external view-source link).
(c) Docs-surface token polish + consistent loading/empty/error states +
aria-labels on icon-only controls.
## Pre-state
- Foundation commit carried `frontend/src/styles/tokens.css` (the #31
design-token module). Build requires `VITE_APP_NAME` (no default).
- `/docs/sessions/:nnnn` (DocsSessionIndex.jsx) rendered a
`.docs-session-overview` placeholder: "N transcript(s) in this
session. Select one from the navigation."
- DocsSessionTranscript.jsx rendered body via MarkdownPreview with a
breadcrumb but no metadata header.
- Backend `/api/docs/sessions/manifest` (api.py:177-178) returns
`result["manifest"]` verbatim — the full `sessions.json` object —
so arbitrary per-entry fields already reach the frontend.
## Turn-by-turn arc (honest)
1. Read tokens.css, SESSION-PROTOCOL.md (§1 naming + the 0017.0
folder-layout amendment + the sessions.json shape), all seven owned
docs components, api.js docs helpers, docs_sessions.py, and the
api.py manifest route.
2. Confirmed the manifest endpoint is a pass-through → **no backend
change needed** for `tldr`. The decision branch in the task was
resolved in favor of "already passes through".
3. Read the existing docs CSS block in App.css (read-only) to learn
which classes a sibling owns (.docs-article, .docs-empty,
.docs-error, .docs-source-link, .docs-session-overview, etc.) so
Docs.css would not collide.
4. Rewrote DocsSessionTranscript.jsx: exported `gitSourceUrl`,
`transcriptOrdinal`, `parseTranscriptMeta`, and `TranscriptMetaHeader`;
added a manifest fetch for title+tldr; rendered the header above the
body.
5. Rewrote DocsSessionIndex.jsx to inline-render the primary transcript
(driver `.0` preferred, else `files[0]`) and list siblings; reused
the imported header + ordinal helpers.
6. Created Docs.css (new classes only, all token-valued).
7. Brought DocsUserGuide's bare `<p className="error">` onto the shared
`.docs-error` + retry convention.
8. Build failed first run (VITE_APP_NAME unset — env requirement, not
my change); re-ran with `VITE_APP_NAME="OHM Verify" npm run build`
→ green (974 kB index chunk; >500 kB warning expected/benign).
9. Staged the four files explicitly (avoided the `frontend/node_modules`
symlink), committed.
## Design decisions
**Inline-collapse (a).** URL stays stable to the session number — the
session root fetches the index, picks a primary file, fetches its body,
and renders the same `TranscriptMetaHeader` + `philosophy-body` the
standalone route uses. No `<Navigate>`, no 301. Primary selection:
`SESSION-NNNN.0-TRANSCRIPT…` if present, else `files[0]` (backend
returns the list pre-sorted). For multi-transcript sessions, a
`.docs-session-siblings` strip lists every transcript ordinal — the
primary marked "(shown below)" with `aria-current`, the rest linking to
`/docs/sessions/:nnnn/:filename`. No empty placeholder remains on any
path that has at least one transcript.
**Metadata header (b).** `parseTranscriptMeta` regexes the
`-TRANSCRIPT-<start>--<end>.md` ISO segment (`YYYY-MM-DDTHH-MM` x2,
colons-as-dashes per protocol). Times are built as **local** Dates and
rendered as wall-clock via `toLocaleString` — the filename encodes no
timezone (PST implied), and duration is a difference of two same-basis
Dates so the implied-TZ ambiguity cancels. Legacy renamed-letter
transcripts (no segment) → all derived fields null → header shows just
the title + source link (graceful degrade). Duration omitted when
start==end or unparseable.
**tldr schema (b).** Consumed as a **string** field named `tldr` on the
per-session entry of `sessions.json`, i.e. keyed by the 4-digit session
number alongside `title`:
```json
{ "0019": { "title": "UX-polish wave …", "tldr": "One-line summary." } }
```
Read via `getSessionsManifest()``payload[nnnn].tldr`; rendered only
when `typeof === 'string'` and truthy. Absent ⇒ no TL;DR line.
**Polish (c).** Docs.css styles ONLY the two new element families
(`.docs-transcript-meta*`, `.docs-session-siblings*`), every value a
tokens.css custom property, to avoid racing the #31 App.css sweep for
the pre-existing docs selectors. DocsUserGuide normalized to the shared
error convention. All icon-only controls already carried aria-labels
(verified the drawer toggle `☰`, scrim) — no aria gaps found in owned
files.
## Cut state
- Branch: `feature/v0.21.0-docs-32`
- Final commit SHA: `adb5d2571532ad5992900791d295ef214ddfba0d`
- Files changed (4):
- `frontend/src/components/DocsSessionIndex.jsx` (rewrite — inline collapse)
- `frontend/src/components/DocsSessionTranscript.jsx` (rewrite — meta header + exports)
- `frontend/src/components/DocsUserGuide.jsx` (error-state normalize)
- `frontend/src/components/Docs.css` (new)
- **Backend: NOT changed.** docs_sessions.py untouched — manifest
pass-through already exposes `tldr`. (No py_compile needed.)
- Build: GREEN (`VITE_APP_NAME=… npm run build`; 974 kB chunk warning benign).
## What the driver needs to know
- **No backend coordination required** for this item — frontend renders
`tldr` when present; the field's value-backfill is the documented
out-of-scope follow-up.
- **sessions.json `tldr` schema to backfill** (driver/operator task in
`wiggleverse/ohm-session-history`): add an optional `"tldr": "<one
line>"` string to any per-session entry next to `"title"`. The
frontend already consumes it on both the inline session-root view and
the standalone transcript view. Absent ⇒ no-op.
- Inline-collapse changes the `/docs/sessions/:nnnn` semantics: it is no
longer nav-only. The left-nav transcript rows + sibling links still
point at the standalone `/docs/sessions/:nnnn/:filename` route, which
is unchanged and now also carries the metadata header.
- Build needs `VITE_APP_NAME` set (env requirement, not from this item).
## §19.2 candidates
- `parseTranscriptMeta` / `transcriptOrdinal` are filename-shape logic
that mirrors the backend `_TRANSCRIPT_FILENAME_RE` and the nav's own
`transcriptOrdinal` in DocsLayout.jsx — there are now two frontend
copies of the ordinal regex (DocsLayout owns one; I export one). A
future consolidation could lift a single `lib/transcripts.js` and have
DocsLayout import it (DocsLayout is sibling-owned, so I did not touch
it). Worth flagging for a follow-up de-dup.