The first TNDM deck came from a screenshot gallery. It exercises every parser frame type and has a real feedback loop to close; the parsing rules are not gallery-specific. See md-parser.

flowchart LR
  A["Source gallery Markdown<br/>2026-08-20 feedback tour"] -->|POST deck import| B["Deck qwizz-2026-08-20<br/>76 sections · 323 frames · 320 images"]
  B --> C["Tandem session<br/>review, verdicts and annotations"]
  C -->|GET export.md| D["Decision export<br/>YAML stats + decision bullets"]
  D -. not merged yet .-> E["Source gallery<br/>route callouts + Feedback log"]
  E -->|re-import same deck id| B
PropertyMeasured value
Source/home/loca/dev/wrdp/q5vault/audits/qwizz/2026-08-20-feedback-tour.md
Published routehttps://wiki.wrdp.loca.zone/audits/qwizz/2026-08-20-feedback-tour; HTTP 200 checked 2026-08-20
Frontmatter title🎞️ QuizWizz UI/UX Feedback Tour — Complete Shot Gallery
Release statusreleaseEligible: false (2026-08-20-feedback-tour.md:7); importing or reviewing this bench-local evidence is not a release decision.
CaptureEvery reachable QuizWizz surface, persona and state on 2026-08-20: 315 screenshots plus 5 contact-sheet boards, grouped by wave and route (2026-08-20-feedback-tour.md:10)
Source structureDeck interpretation
No h1Deck title comes from frontmatter (src/lib/mdframes/mdframes.test.ts:101-103).
11 h2 sectionsTour yamlsum, Coverage, Blocking defects found during capture, Boards, waves A–E, Feedback log, Method & residue (src/lib/mdframes/mdframes.test.ts:39-51,112-114).
65 h3 route sections<route-id> — <surface>, such as g01 — landing/front page (2026-08-20-feedback-tour.md:88).
Route bodyURL in backticks, image plus immediate <sub> metadata, then callouts (2026-08-20-feedback-tour.md:88-105).
Warning and note calloutsFindings in this route carries QA findings; Feedback — <route-id> carries the tour-note placeholder (2026-08-20-feedback-tour.md:1495-1499).

A source pair at 2026-08-20-feedback-tour.md:92-93:

![Guest homepage fold: nav, hero heading, tagline, CTA buttons.](/assets/qwizz-tour/2026-08-20/shots/g01-d.png)
<sub>g01-d.png · desktop 1280x900 · fold</sub>

The <sub> becomes frame sub and splits on · into tags: ["g01-d.png", "desktop 1280x900", "fold"] (src/lib/mdframes/index.ts:440-450). A trailing ⚠️ finding adds the frame’s finding badge (src/lib/mdframes/index.ts:451-457; source example at line 124).

CoverageCount
Wave A / B / C / D / E141 / 13 / 130 / 8 / 23 shots
Routes65
Clean / finding verdicts250 / 65
Deliberately absent shots5: two dead preview tabs, one notice without mutation control, one unreachable import notice, one nonexistent state (2026-08-20-feedback-tour.md:36-51)

Import call

export TNDM_TOKEN=$(cat /home/loca/dev/tandem-audit/.api-token)
curl -sX POST https://tndm.loca.zone/api/decks/import \
  -H "Authorization: Bearer $TNDM_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"md_path":"/home/loca/dev/wrdp/q5vault/audits/qwizz/2026-08-20-feedback-tour.md","copy_from":"/home/loca/dev/wrdp/q5vault/assets/qwizz-tour/2026-08-20","id":"qwizz-2026-08-20"}'

The bearer header is required since 2026-08-21. The original 2026-08-20 import was anonymous because no gate then accepted or required a credential; api owns the current contract.

copy_from makes the deck self-contained. Pointing at the published Quartz URLs would bind it to whichever frozen dist-<epoch> an explicit production build had installed. Import copied 315 shots and 5 boards into /home/loca/tndm/decks/qwizz-2026-08-20/assets/{shots,boards}: 320 files, matching the source count, 0 missing. Sources were rewritten to deck routes such as /api/decks/qwizz-2026-08-20/assets/shots/g01-d.png (src/lib/server/importer.ts:227-237; disk recounted 2026-08-20).

Resulting deck — measured 2026-08-20

Deck shapeValue
Sections76: 11 level 2, 65 level 3, no level 1
Frames323: 320 image, 2 table, 1 code
Images315 under /shots/, 5 under /boards/
Frame badges65 finding
Section callout findings139
Copied assets320, with 0 missing

The table frames are Coverage and Blocking defects found during capture; the code frame is the Tour yamlsum fence, tagged yaml (src/lib/mdframes/index.ts:386-388, src/lib/mdframes/mdframes.test.ts:93-98). Default parsing ignores paragraph prose, so 1618 source lines become 323 reviewable frames.

Why the fixture says 321 images

MeasureFrozen fixtureLive deck
IdentityMD5 a15fb4f0ffb6d07e8256402563193d15; 1619 linesRe-measured per import
Sections76; 65 at level 376; 65 at level 3
Image frames321320
Shot / board sources316 / 5315 / 5
finding badges6665
Image subNon-empty on all 321Non-empty on all 320

The frozen fixture still contains v01-d-p02-player-intro.png and its finding chip at src/lib/mdframes/fixtures/qwizz-tour.md:1480-1481. The live gallery withdrew it: cleared storage opens directly on question 1, so the claimed intro state does not exist (2026-08-20-feedback-tour.md:51,1493-1494). This one-frame difference is intentional. Fixture constants defend measured ground truth and are never re-baselined (src/lib/mdframes/mdframes.test.ts:1-10,54-84).

What the reviewer does

  1. Choose a route in the rail. H2 headings form groups; h3 headings form rows. Tour yamlsum, Coverage, Blocking defects found during capture and Boards hold the non-wave frames. Wave headings hold frames in child routes, so a group badge sums header and rows (src/lib/components/Rail.svelte).
  2. Focus a frame control, then assign keep, recapture or kill; add a note, task or scribble; mark a route done when appropriate. quickstart owns the full keymap and operator flow.
  3. Read counters honestly. Feedback log and Method & residue have no frames; seven sections are frame-empty and do not enter the progress ring (src/routes/s/[sessionId]/+page.svelte).
  4. Use stable targets. Section ids slug headings: g01 — landing/front page becomes g01--landingfront-page; its first frame is g01--landingfront-page/1. Because frame ids contain /, cards are resolved by element id rather than a CSS selector.

Both callout types enter section.findings[], so its 139 entries mix QA findings with _(awaiting tour notes)_ placeholders. Live route g01 contains only that placeholder. The QA signal is the 65 frame-level finding badges. Since the 2026-08-21 polish pass, the rail find column counts finding-badged frames and amber means outstanding unruled finding frames; section callout text remains available in the section details (src/lib/mdframes/index.ts:288-335).

Export and merge back

Review status on 2026-08-21: the first tandem audit is underway. Nothing has been merged back into the source gallery yet.

GET /api/sessions/<sessionId>/export.md returns text/markdown; charset=utf-8 as tandem-<sessionId>.md (src/routes/api/sessions/[id]/export.md/+server.ts:12-17). Its fenced YAML header records deck, title, session, generated, duration, events_reported, by_kind, frames_touched and sections_done; then ## Tandem audit <sessionId> — <date> introduces one line per decision (src/lib/server/export.ts:108-133).

Export behaviourResult
Decision bulletsnote, task, verdict, speech, scribble
Non-bulletsnav and status are transport chatter; say is agent speech; route_done contributes to sections_done.
Bullet shapeKind and frame or section target, then verdict, text and tags on one line (src/lib/server/export.ts:49-70).
ScribbleRelative [scribble](scribbles/<file>); copy the PNG from DATA_DIR/sessions/<id>/scribbles/ into the gallery asset tree and rewrite the link (src/lib/server/export.ts:56-59).
Human frame referenceExport target is a deck id such as g02--builder-craft/17; splice the shot filename from the frame’s first tag when needed.
Re-importImporting the same deck id refreshes frames and captions but leaves existing tandem sessions untouched (src/lib/server/importer.ts:255-261).

The merge target is ## Feedback log at source line 1601. On 2026-08-20 it held one bullet: - 2026-08-20 — gallery published, tour ready for the first pass. at line 1605. The gallery instructs reviewers to write per-route feedback first, then promote accepted or rejected notes and applied decisions as dated, one-bullet entries (2026-08-20-feedback-tour.md:12). That promotion has not happened yet.