commit 31c4cfd2d150e2f89d7d3c6ef1d5289c0ac79f40
parent ce0dc5e1989551fb882224bf931f6283c520bb65
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 9 Oct 2026 14:49:26 -0400
plans: release 22 — Track E as shipped (E1–E5), the overview ruling confirmed; export changelog; PUBLISH.md's exports name the slides
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 121 insertions(+), 5 deletions(-)
diff --git a/PUBLISH.md b/PUBLISH.md
@@ -83,7 +83,12 @@ resolve it, to `.export-index/sites/<id>/report-exports/<report>/` as `report.ht
(one self-contained file — inline style, no script, its stills and post screenshots
inlined; clips linked on the site), `report.pdf` (that page printed by headless
Chromium; skipped with a note on a host without Playwright's browser), `report.md`
-(plain Markdown with numbered references) and `evidence-pack.zip` (the page with its
+(plain Markdown with numbered references), `slides.html` (the report as slides — the
+same slides the page's Slides view shows, `common/lib/report/slides.ts` — one
+self-contained file: the site's slide stylesheet in its accent, every picture inlined,
+a few lines of script for the keys), `slides.pdf` (those slides printed, one 1280 × 720
+page per slide; skipped like `report.pdf`; neither slides file for a report whose
+`slides.hide` says so) and `evidence-pack.zip` (the page and the slides with its
clips, stills and screenshots as files, so the clips play offline, plus the Markdown
and the citations; packed by the system `zip`, which a host must have), with an
`export.json` naming each file's size and checksum and the hash of the report.json it
@@ -106,7 +111,8 @@ and `export.json` records it.
The build's compose then writes the reports from what prepare left
(`common/publish/composeReports.ts`): each report's page, its citations as
`citations.json` and `citations.csv`, its exports (`report.html`, `report.pdf`,
-`report.md`, `evidence-pack.zip` — only those made from the report.json as it is now,
+`report.md`, `slides.html`, `slides.pdf`, `evidence-pack.zip` — only those made from the
+report.json as it is now,
and only a file of at most 24 MiB: a larger pack stays on the host; the page's
download line lists what was published), its cited stills (never a saved source copy),
one page per cited moment with the transcript lines around it, the prepared clips
@@ -283,7 +289,7 @@ the same lock.
| The source mirror alone | — (every homepage build runs it) | — | `source publish [--force] [--check] [--keep-scratch]`, `source audit [<git dir>]` |
| Fetch the clip windows a site's reports cite and the disk lacks | its **Reports** tab → *Fetch missing evidence* (*Preview missing evidence* lists them) | `fetch-windows` (`{"siteId", "dryRun"?}`) | — |
| A site's report evidence media (then its exports) | its **Reports** tab → *Prepare evidence media* | `reports-prepare` (`{"siteId"}`) | `reports prepare <id>` |
-| A site's report exports (HTML, PDF, Markdown, evidence pack) | its **Reports** tab → *Export reports* | `reports-export` (`{"siteId", "reportId"?, "formats"?}`) | `reports export <id> [--report <rid>] [--formats html,pdf,md,zip]` |
+| A site's report exports (HTML, PDF, Markdown, slides, evidence pack) | its **Reports** tab → *Export reports* | `reports-export` (`{"siteId", "reportId"?, "formats"?}`) | `reports export <id> [--report <rid>] [--formats html,pdf,md,slides,slides-pdf,zip]` |
| A report from a /sweep report, an /ask answer or a report-to-video manifest, and a starter manifest from a report | — | — | `reports convert <sweep\|ask\|manifest> <in> --out <report.json> [--channels-dir <dir>]`, `reports to-manifest <report.json> --out <manifest.json>` |
**The editor.** The /sites **Publish** panel has a row per site, then the hub and the
diff --git a/export/CHANGELOG.md b/export/CHANGELOG.md
@@ -1,6 +1,8 @@
# Changelog
## [Unreleased]
+- **A report reads as an article, as slides, or in an overview of both.** A report page's header has a switch, **Article · Slides · Overview**. **Slides** shows the report one 16:9 slide at a time, fitted to the screen in the site's accent and light or dark: a title slide, In brief, What the check found, one slide per section and per claim — its verdict, its finding and the evidence it rests on (a clip's still, a post's screenshot, the article's sentence, with its quote) — and a sources slide. ← → (and Page Up, Page Down, Home, End) or a swipe step through them; a citation's number opens its card; every slide has **Read this in the article**, and Esc goes back to the article where the slide stands. On a phone a slide reads top to bottom, its evidence under its words. **Overview** lays the article's parts and their slides side by side on one tilted plane, each part beside its slide: ↑ ↓ walk them, a part opens the article there and a slide opens the slides there; with reduced motion, on a narrow screen, arriving by keyboard or with **Flat**, the same pairs lie flat. Switching keeps your place: the part of the article on screen opens its slide, and back. The view and the place are in the address (`?rv=slides#s-4`, `?rv=iso#<part>`), so a shared link opens the same slide; the article itself is unchanged for search engines and readers without script. A report's author can shape its slides in `report.json` (`slides`, and a section's or claim's `slide`: its own title, up to five short points, which evidence to show, a layout, or hidden); a report without them still has slides, made from its text. Needs a rebuild and deploy of each site with reports.
+- **A report can be saved as slides.** The download line adds **Slides** (`slides.html`, one file that opens with no network: the slides with their pictures inside, the arrow keys stepping through them, each citation numbered to a reference list) and **Slides PDF** (one page per slide), and the evidence pack carries the slides too. Needs `reports export` (or prepare) and a rebuild and deploy of each site with reports.
- **Posts withdrawn from a site leave a stand-in, not a stale copy.** When an X channel's posts stop being published on a site — while X posts are private, say — the site's build ships an empty posts manifest and an empty page at every address they were served from, sent with `Cache-Control: no-store`, so a reader (or the hub) asking for them gets "no posts" instead of the copy Cloudflare's edge kept for up to a week. The hub does the same for every X channel a public site carries.
- **A quote with an editorial insertion is still a citation.** An inline citation whose quoted words hold brackets, `[“told [the mayor] so”](cite:id)`, is numbered, previews and links like any other, is listed in the reference list and its section's, and validation names its id when the report lacks it; it used to render as plain text. A label with a lone `]` is still not a link. The MCP's report reader counts it too. Needs `reports prepare` and a rebuild and deploy of each site with reports.
- **A report can carry a video.** `report.json` `video` (`{ "src": "video.mp4", "poster": "poster.jpg", "caption": "…" }`, files in the report's directory: an mp4, a png/jpg/webp poster, a one-line caption) plays at the head of the report's page, under its header. Composing the site refuses a report whose video or poster is missing, or whose video is over the 24 MiB publish limit. Needs a rebuild and deploy of the site.
diff --git a/plans/release-22.md b/plans/release-22.md
@@ -7,8 +7,8 @@ so a reader sees the same article both ways and enters whichever they prefer at
## Rulings (operator, 2026-10-09)
- Articles may be **built a bit differently** to make this work: `report.json` gains authoring fields for slides.
-- *Assumption, open to correction:* "isometric render" is the overview described here (a tilted plane pairing
- each article section with its slide, click either to enter that view), not a separate visual style.
+- **"Isometric render" is the overview described here** (operator confirmed 2026-10-09): a tilted plane pairing
+ each article section with its slide; click either to enter that view.
## What is there (verified)
@@ -111,6 +111,114 @@ when a site is rebuilt — nothing here rewrites a report.json in the corpus.
### Track E
+Branch `worktree-agent-a4ec71c7c593a80e5`, off `fdbdd370` (`r19/integration` with this plan). Not pushed, not merged.
+
+#### E1, as shipped — the slide fields and the builder
+
+`report.json` gains `slides` (`hide`, `title`, `points`, `closing` — `points` added to the plan's three: the
+plan's "the quick take: summary's first paragraph (or `points`)" needs a place for them) and a section's or
+claim's `slide` (`title`, `points`, `cite`, `layout`, `hide`); `version` stays 1. The limits are one pure module,
+`common/lib/report/slideRules.ts` (5 points, 140 visible characters each — a citation counts as its label —, 160
+for a line), with the report page's own anchor ids (`REPORT_PAGE_ANCHOR_IDS`: `report-head`, `in-brief`, `found`,
+`claims`, `references`, `downloads`, `report-end`). The validator checks lines (one line, not blank, not too
+long), points (count, length, inline cites that name a citation the report cites ELSEWHERE — a slide never brings
+evidence of its own, which would have no number), a `cite` of the slide's own section or claim, an
+`evidence`/`quote` layout with something to show, and refuses a section or claim id that is one of the page's own
+anchors (a counts-only scan of the 26 published `report.json` files: 0 of 322 ids collide). `lib/report/slides.ts`
+`buildReportSlides(view)` is the deck (title, In brief, What the check found, each section and claim, sources),
+each slide anchored to an id the article page carries (`reportPageAnchorIds`), plus `slideForAnchor`,
+`slideIndexForPlace`, `slideLint` (umtool) and the URL place (`parseReaderPlace`/`readerPlaceUrl`: `rv`, `#s-<n>`
+or an anchor). The page view carries the fields through `buildReportPageView` (compose) and umtool's lenient
+fallback (which also now gives a claim's `sourceQuote` as its id, as the strict view does). REPORT.md regenerated
+(a Slides section, the two key tables); the demo fixture report gains `slides`, a section's points and cite, a
+claim's title and points, a claim's quote layout.
+
+#### E2, as shipped — the slides view
+
+`common/components/report/slides/`: `Slide` (one slide per kind and layout; its left edge the colour of what it
+says — a claim's verdict, else the accent; "Read this in the article" to its anchor), `ReportSlides` (a 1280 × 720
+canvas scaled as one piece to its box, reflowed on a narrow portrait box; ← → PgUp PgDn Home End, Esc, swipe; a
+region with an `aria-live` "Slide n of N: <kind> — <title>" label; a rail), `ViewSwitch` (a `role="group"` of
+`aria-pressed` toggles), `SlidesHost` (what differs by host: markdown, a citation's marker, the way back, a badge),
+`useReaderPlace` (`rv` + hash; a switch pushes, a step replaces). Styles are plain CSS, `common/styles/slides.css`
+(`.rs-*`, unlayered so it wins over a shared component's utilities), coloured from the host's tokens through
+`--rs-*`. The export site's report header carries `ReportReader`: the other views are laid over the static page,
+built in the browser from `page.json` (fetched on demand); the article behind is `inert` and still; the place is
+kept both ways (the part in view opens its slide; Esc / "Read this" land on the anchor, scrolled to and focused); a
+citation number's click inside a slide opens its card instead of jumping to the reference list behind.
+`CitationCard`'s `heading` is exported as `citationHeading`.
+
+#### E3, as shipped — the overview
+
+`ReportOverview`: the pairs as one ordered list; tilted (`rotateX(55deg) rotateZ(-45deg)`, the plane's box sized
+from the tilt's geometry so it scrolls whole, the scroller alone moved to the pair in force — never the article
+behind) or flat (two columns; stacked under 780 px of box, a container query so umtool's column stacks too). Flat
+for reduced motion, a box under 900 px, a keyboard arrival, or the **Flat** toggle (localStorage, try/catch). ↑ ↓
+Home End walk, ← → block/card, Enter opens, Esc back to the article; roving tabindex (only the pair in force is
+tabbable); a thumbnail is the real slide, `inert`, its words as text. The e2e specs were split: `slides.spec.ts`,
+`overview.spec.ts`.
+
+#### E4, as shipped — umtool
+
+`ArticleReader` gets the switch over its lenient view; `ArticleShapes` renders the shared slides/overview in the
+reading column (the text stays mounted, hidden, its note marks with it; the notes rail beside). A slide's markdown
+is umtool's `Md` (a citation opens the evidence rail), its badge the open notes on its part (`openNotesBySlide`:
+a section's on its slide, title/subtitle on the title slide, a citation's on each slide showing it). The source
+tab lists slide lint and the validator's slide problems (`data-slide-lint`), each linked to its slide. umtool's
+globals.css imports slides.css, sources `common/components/report`, and maps `--rs-*` to its bench palette (`sel`
+the accent). The sites fixture's draft `polemic-beta` gains a long body (its slide lint's case).
+`umtool/e2e/article-slides.spec.ts` (4 tests).
+
+#### E5, as shipped — exports
+
+`slides` (`slides.html`) and `slides-pdf` (`slides.pdf`) join `REPORT_EXPORT_FORMATS` (order html, pdf, md,
+slides, slides-pdf, zip), so the downloads line, compose, the cited audit's allowlist (`REPORT_EXPORT_FILENAMES`),
+the revision commit's `exports.json`, the CLI and the ops job take them unchanged. `lib/report/exportSlidesHtml.ts`
+is the slides as one file (slides.css inline, the light base in the site's accent — `resolveAccent(site.accent)
+.light` —, every picture a data: URI, a few lines of script to fit and step, numbers to a reference list under the
+deck, each slide's way back to the site, the footer line on the sources slide). It is a STRING mirror of
+`Slide.tsx`: reportExports.ts is bundled into the editor's server (its "use server" export action), where
+`react-dom/server` is not imported; `exportSlides.test.ts` holds the two to the same `rs-` classes per slide and
+every class to a rule in the stylesheet. `slides.pdf` prints it at 1280 × 720 (`PdfPrinter.print(html, size?)`,
+`playwrightRuntime`'s `pdf` type gains width/height). A poster only the slides show is inlined or packed when local,
+never a problem. A report with `slides.hide` exports neither (a log line). CLI usage, ops usage and COMMANDS.md
+updated.
+
+#### E6 — records
+
+This section; `export/CHANGELOG.md` `[Unreleased]` (two bullets); PUBLISH.md's exports paragraphs and table row;
+the ruling line above reworded as on `r19/integration`. umtool has no CHANGELOG.md (none written); the editor is
+unchanged (no editor changelog).
+
+| commit | what |
+|---|---|
+| `2734b22b` | E1 schema, validator, slideRules, buildReportSlides + tests, REPORT.md, fixture |
+| `12691d03` | E2 shared slides components, slides.css, ReportReader, article anchors, slides.spec |
+| `75f0205d` | E3 ReportOverview, the switch's Overview, overview.spec |
+| `5d0b4fbc` | E4 umtool switch, ArticleShapes, note counts, source-tab slide lint, article-slides.spec |
+| `e9ea4cdc` | E5 slides.html / slides.pdf exports, downloads, pack, tests, CLI text |
+| (this) | E6 record, changelog, PUBLISH.md |
+
+**Gates.** `pnpm -r --no-bail --workspace-concurrency=1 exec tsc --noEmit` clean at E1 and at E5 (E2–E4 per
+package: common, export, umtool `tsc --noEmit` clean). Unit: `common/lib/report/*.test.ts` 111/111 (slides.test 26,
+exportSlides.test 6, report.test 17 with the new slide/anchor cases); `publish/composeReports.test.ts` +
+`reportMedia.test.ts` 23/23 (export expectations updated for the two new files and the second skipped-PDF note);
+`lib/builtExport.test.ts` + `bin/cli-docs.test.ts` + `bin/_cli.test.ts` + exportSlides 62/62;
+`pnpm --filter export test` 116/116; mcp 292/292 (at E1), `mcp/src/reports.test.ts` 5/5 (at E5);
+`scripts/archilyzer-ops.test.mjs` 46/46. **Not run — the operator's P1 hold (no e2e, no builds until the
+all-clear):** the export report suite (`e2e:report`, planned at E3 and E5: `slides.spec.ts` 10, `overview.spec.ts`
+3, the downloads/audit changes), umtool's article specs (`article-slides.spec.ts` + the three existing), the umtool
+capped build, and the full common suite. The views were checked visually instead, with no build: slides.html of the
+fixture rendered and screenshotted in headless Chromium (every slide kind), and the shared components bundled with
+esbuild over the fixture's page.json (slides at 1440 × 900 and 390 × 844, the overview tilted, flat under reduced
+motion, stacked on a phone, light and dark).
+
+**Found and left.** (1) A claim's slide shows its gist when it has no points; a claim with neither shows only its
+title and text. (2) The overview's thumbnails render every slide (fine at the fixture's 11; a report of 100+ claims
+would render 100+ small slides — not measured). (3) The keyboard-arrival heuristic is the last input before the
+overview mounted (a keydown anywhere counts). (4) `report.json`'s change summary (revision history) does not name a
+slide-field edit ("Other edits"). (5) slides.html's light base only (no dark variant), as report.html.
+
## Rollout
Rebuild and deploy each site with reports (the slides view is in the bundle, and `reports prepare` re-exports to