Progressive lenses: a replay of two real runs
Artefact: the app window (theme CSS verbatim; native chrome approximated)Controls and commentary: not product
app windowwindow · sidebar · rail: native, approximatedweb view: shipped theme CSSnew: N1–N5tapestry: session-tapestry.css + ported renderstand-ins: thumbnail frames, quote text, flocking; Project = stats row only
Commentary
Measured, then projected
Today is the run as it happened: per-session Whisper start and end from bristlenose.log, each LLM call's completion time from llm-calls.jsonl. Progressive is a projection from the same numbers: each session readable after its own transcription and speaker call, in session order; its quotes after its own topic and quote calls, reusing the wall time each took in the real run. FOSSDA s3 spent 30 min in quote extraction and returned nothing, a real failure, and it holds the barrier up.
Settled, and drawn
- Rail: only Sessions is selectable at the start, in normal type. Quotes turns normal at the first quotes, Signals at the barrier. Nothing in the rail shimmers. Codebooks stays disabled during the run in v1.
- Grid, three row states: readable (the shipped row), being processed (one row; its filename in
.bn-thinking-shimmer), waiting. No shimmer for a session that arrives with a transcript.
- Every true fact at ingest: number, start, duration, file, in the shipped formats. Facts not yet known stay empty.
- Thumbnails: a placeholder only where the probe confirms video, then the frame, about 0.14 s each.
- Progress: the shipped
desktop.chrome.pipeline.* strings, static, in the subtitle and the status line.
- Quotes: grouped by session until the barrier; the grouping control appears only then.
- Session tapestry, in layers: the disclosure appears once a row is readable. Speaker turns arrive with the transcript; scene colours about 1.5 s per hour of video later; sentiment marks with that session's quotes; section flags and theme spans at the barrier.
- Option-click on a disclosure opens or closes every readable row. A row that becomes readable later arrives closed.
The grid's geometry is untouched
sessions-grid.css is inlined whole: tracks, subgrid, the shrink pair, the seven container thresholds and the name ladder. Use Window width to walk the rungs; the chrome bar prints the table's measured width. Rows render every cell at every width, as SessionsTable.tsx does, and the container queries decide what shows.
The implementation, as surgical edits
SessionRow: data-session and the ID link only when the session is readable; otherwise aria-disabled="true". That alone removes pointer and hover (sessions-grid.css's [data-session] rules, unchanged).
- The one row being processed:
.bn-thinking-shimmer on its filename span. Base colour = the row's resting colour; contrast set for this surface.
- Speakers, sentiment and thumbnail render nothing until known. The sparkline's "—" (no quotes with a sentiment) appears only once quotes are back.
- Treatment A only: one rule, muted text on a row without
data-session.
- Thumbnail: an empty
.bn-video-thumb while the frame is grabbed, only for probe-confirmed video.
toggleTapestry(sid, all): the open-all set is the readable sessions, not every session.
tapestryScale.ts: fitScale(durations, width): drop durations under median/4 or over 4 × median, then min(4, max(shortest / usable, longest / (4 × usable))), from ingest durations so it never moves, with no lower clamp (the mockup's fitShortest is the reference); zoomScale's far end becomes min(0.25, fit/4).
- The tapestry route returns what exists. Its shape already allows a missing layer:
colour null, sections empty, a quote's section/theme null. So turns can ship as soon as a session is imported, with no schema change. (Not yet checked: how SessionTapestry draws with those layers absent. This port draws them absent and looks right.)
- No edit to
sessions-grid.css tracks, thresholds or ladder, nor to session-tapestry.css.
Event log (simulated run time)
New in this mockup (inventory)
| N1 | Row treatment A: color: var(--bn-colour-muted) on a row without data-session. |
| N2 | .bn-thinking-shimmer on the processing row's filename; base = the row's resting colour; per-surface contrast (knob, 20% as the web chip). |
| N3 | Empty .bn-video-thumb (no image, no play icon) as the thumbnail placeholder. |
| N4 | Split-flap on a count's pipeline increments. |
| N5 | Quote grouping control, in .section-heading-action, after the barrier. Shape undecided. |
String plan
| Where | String | Source |
| Rail | Project · Sessions · Quotes · Codebooks · Signals · Discussion | common.nav.* |
| Grid headers | ID · Speakers · Start / User journey · Sentiment · Duration · Interviews | common.sessions.col* |
| Speakers, unnamed | Moderator · Participant · Observer | common.sessions.speakerPlaceholder.* |
| Project stats | session(s) · of sessions · quote(s) · theme(s) · section(s) | common.dashboard.* |
| Status, subtitle | Transcribing · Finding quotes · Grouping themes; "3 of 10" | desktop.chrome.pipeline.* |
| Quotes headings | Sections · Themes | common.quotes.sections/themes |
| Grouping control | By session · Sections & themes | new, owner writes |
| Session group heading | #1 · 01-bruce-perens.mp4 | new shape, from shipped parts |
Open
- Row treatment A or B (toggle). Project lens during the run (toggle): with it disabled, the split-flap has nowhere visible to run until the end.
- Which thumbnail frame replaces the placeholder: the 60 s fallback straight away, or the transcript-chosen one at readable.
- Playback before a row is readable. The media exists from the first second, and the shipped thumbnail and filename both open the player. Drawn: the filename is plain until readable; the thumbnail keeps its shipped
cursor: pointer.
- With animation off or Reduce Motion on, the processing row looks like a waiting row (the spec's static state);
aria-busy on it would be new.
- In Edo light, muted (#4a698a) and accent (#0f5c9e) are close in hue; under A, waiting rows can read as links. Edo dark and Default are distinct.
- Which stage the status line names when transcription and quote finding overlap (drawn: Transcribing while any session is still to transcribe).
- A transcription failure as a fourth row state (M4). Messages Q1–Q6.
- Timeline scale (proposed, now the default here). One shared scale, fixed at ingest from every session's duration, in four steps: (1) leave out outliers either way, sessions shorter than a quarter of the median or longer than 4× it, so a minority of tiny ones (a 5-min test beside real interviews) or of very long ones cannot set the scale for the rest, while a project that is mostly one length keeps them, because they move the median; left-out sessions still draw at this scale, a tiny one with white space and a very long one scrolling further; (2) fit the shortest that remain to the width, so no timeline opens with white space on its right and a single 5-minute first try fills the width exactly; (3) cap the longest at 4× the width, and where the spread is wider let the cap win, so only the shortest show white space; (4) the shipped 4 s/px ceiling wins over everything, which matters only below a ~220 px table. In these runs: FOSSDA (21.6 to 100 min) hits the cap, and s10 opens at about 88% of the width; Rockclimbing's 77-second recording is above a quarter of its 3.8-min median, so it stays in and sets the scale (spread 3.3×, under the cap). The shipped rule is on the toggle.
- The tapestry here is a render-only port: hover, flag raise, playhead, quote popover and keyboard are not ported.
Discussion did not run in either trial, so it never lights. The flocking canvas is a stand-in for the shoal, shown only in Today mode: with Sessions selectable from the first second, progressive mode never needs it.