# /sites — every site's articles Every report on every site under `transcripts/sites/` — published and drafts — read in place: no private site built, no port served. umtool **reads** the sites; the one file it writes there is a report's `notes.json` (docs/notes.md). | page | what | |---|---| | `/sites` | every site (private first), every article: status, updated, citations, open notes, poster, video project, source draft. Filters are links: `?site=`, `?status=published\|draft`, `?notes=open` | | `/sites/` | its articles, its report videos (playable), the umtool projects they were cut in (takes, like/maybe/no), the workspace files they were written from (`?ws=&rel=` opens one) | | `/sites//` | the article with its notes (below) | | `/sites//?tab=source` | the article's workspace files, its own draft opened | | `/sites///evidence` | every citation, one per screen (`?c=`) | ## Where an article comes from - **Site and reports**: common's `listSites`/`getSite` (site.json, the editor's defaults), `listReportDirs` (the enumerator the editor's Reports tab uses), each `report.json` through the report validator. **Published** = named in site.json `reports`, in that order; every other report directory is a **draft**. - **Source** (`lib/articles/sources.mjs`): the draft under `~/reports//polemics/drafts/` whose `id` or file name is the report id, `polemic-` allowed on either side, preferring a workspace whose generator names the site; the generator is the `*.py`/`*.mts` under `/polemics` or `/site` that names the site or the report. Shown with its reason; written into `notes.json` `source` on the first note. **Edit the draft, not report.json** — the generator overwrites report.json. - **Video project** (`lib/articles/links.mjs`): a report-video manifest with a top-level `"article": "/"` is linked to that article and nothing else (build-video.mjs ignores the key). Otherwise a manifest `slug` equal to the report id (± `polemic-`) in the draft's workspace, when exactly one matches; several are listed as "possible". ## Reading and noting The article renders in a ~70ch column with its notes in a rail (a drawer below 1100px). Its video sits under the title (`components/articles/ArticleVideo.tsx`) and takes timed notes: `n` with the video focused, or **Mark**, notes the playhead as a `moment` anchor on the article's notes ([report-video.md](report-video.md)). | to note | do | |---|---| | a passage | select it → **Note** (or `n`) | | a section | **+ note** on its heading | | a citation | click it → the evidence panel → **+ note on this citation** | | the article | **+ whole article** | A passage note keeps its quote and 32 characters either side; it is found again in the text as it is now (`lib/annotations/anchor.mjs`: exact, then whitespace/quote/case-normalised, then by its surviving context) and marked. One whose quote is gone is listed **orphaned**. Keys, when not typing: `j`/`k` move, `r` resolves, `e` edits, `n` notes, `Esc` closes. `?status=` and `?note=` open the rail on a filter or a note (the decisions inbox links to the note). **Copy agent brief** copies `/api/notes/context` — the `umtool notes` digest. Open notes are `open-note` rows in `/browse/decisions` and on the dashboard. ## Evidence A citation's panel: its quote, speaker, date, label and original link; ±6 cues of its record's `transcript.cues.json`, the cited ones marked (click one to seek); and media, best first: 1. the site's **prepared** clip (`.export-index/sites//report-media/`); 2. a clip **window** the editor fetched (`data//clips/`); 3. a **saved** video or audio file in `data//` (through its media-tier link); 4. otherwise the `fetch_clip` MCP line to fetch it through the editor. umtool never runs yt-dlp, and its own fetch client needs a project manifest. A post shows its text and its capture screenshot. The walk (`/evidence`) is the same panel one citation at a time: `j`/`k` (or ←/→), `space` plays, `n` notes. **Add to video** (in the panel, when the article has a linked video project) puts the cited span at the end of that project's timeline as a clip (`/api/report/timeline` insert): the manifest is snapshotted first, so the project page's **Undo** takes it back, and on a generated manifest an `edit` note tells the agent to port it into the generator's inputs. ## Routes (read-only) | route | serves | |---|---| | `/api/sites/media?site&report&file` | a file in the report dir (video.mp4, poster.jpg, stills/…), byte ranges | | `/api/sites/media?site&moment` | the prepared clip of a moment, via report-media/index.json | | `/api/sites/media?corpus=` | a media file lexically under CHANNELS_DIR | | `/api/sites/evidence?site&report&cite` | one citation's evidence (JSON) | | `/api/sites/workspace?ws&rel` | a listed workspace file; HTML under a CSP sandbox | `SITES_DIR` (else `TRANSCRIPTS_DIR/sites`, else the checkout's `transcripts/sites`) is where all of it is read; the e2e suite points it at its fixture (`e2e/fixtures/sites-fixture.mjs`).