Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 40f37dd04fbbc7c4b8079406709e3a7f944a1c3a
parent 8e4a088be66a168b35c3f2a3af66f13972c89e83
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 11 Sep 2026 12:03:58 -0400

editor: the focus banner and the generated tree, asserted on a lane console

S4's spec. One document changes three things about /operations/<lane> at once
and this is what says so: the banner appears with the site's TITLE and a held
count, the ladder's rungs are the COMPILED ids (`prio-focus-*`, `prio-normal-*`,
`prio-all`) with the pending counts landing on them, and the tree editor is
read-only while the lane's own switches are not.

Clearing the document puts all three back, and that is the half that matters
most: every corpus has no priorities set until one does, so "nothing changed"
is the assertion a corpus that never sets one relies on.

The third case is the unresolvable focus — an unknown siteId compiles NO focus
group, deliberately, so a typo leaves the tree as it would be with no focus
rather than holding the whole corpus behind a site that does not exist. It also
pins the distinction the banner turns on: the document still says something, so
the tree is still generated and the editor still read-only; an unresolvable
focus is not an absent document.

THE SEEDED SETTINGS CARRY THE COMPILED ROOTS ALONGSIDE THE MODEL, because that
is what lands on disk — S3's one writer persists `channelPriority` and all four
`autoQueue[lane].root` trees in a single save, and seeding the model alone would
be a state no writer produces. The roots are spelled out here rather than
imported from the compiler: the ids and the group order ARE the contract this
page renders, so writing them asserts them a second time instead of re-deriving
them from the code under test.

The digest lane is the fixture lane: it is the one whose work list a spec can
seed from a transcript alone, and the one lane-runner.spec already drives.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Aeditor/e2e/focus-banner.spec.ts | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 326 insertions(+), 0 deletions(-)

diff --git a/editor/e2e/focus-banner.spec.ts b/editor/e2e/focus-banner.spec.ts @@ -0,0 +1,326 @@ +import { test, expect } from "@playwright/test"; +import type { Page } from "@playwright/test"; +import { + generateReport, + resetData, + writeChannelConfig, + writeDigestVideo, + writeSettings, + writeSite, +} from "./helpers"; + +// THE FOCUS BANNER AND THE GENERATED TREE, on a lane console. +// +// One document — `settings.channelPriority` — changes three things about +// /operations/<lane> at once, and this spec is what says so: +// +// 1. THE BANNER. A focus is a statement nothing else on the page can make: +// the ladder can be full and the count in the thousands while most of it is +// held behind a group at the top. "Focus: <name> (N channels) · <focus> +// pending in this lane · <rest> waiting behind it · M channels held". +// 2. THE LADDER IS THE COMPILED TREE. The status payload used to ship the +// STORED policy, and with a model set the lane does not dispatch from it — +// so the rungs are `prio-focus-*` / `prio-normal-*` / `prio-all`, keyed by +// the compiled ids, which is what makes the counts land on them. +// 3. THE EDITOR IS READ-ONLY. Editing a rung could not change dispatch (the +// compiler wins), so the controls that would rewrite the tree are disabled +// and the ones that would add or remove a node are gone. +// +// And clearing the document puts all three back exactly as they were, which is +// the half that matters most: every corpus has no priorities set until one does. +// +// THE SEEDED SETTINGS CARRY THE COMPILED ROOTS TOO, because that is what lands +// on disk — S3's one writer persists `channelPriority` and the four +// `autoQueue[lane].root` trees in a single save. Seeding the model alone would +// be a state no writer produces. + +const FOCUSED = "focus-chan"; +const OTHER = "other-chan"; +const SITE = "focusite"; +const SITE_TITLE = "Focus Site"; +const SLOW = 120_000; + +type Leaf = { + id: string; + match: { type: string; value?: string }; + weight: number; + maxWorkers: number | null; +}; +type Group = { + id: string; + mode: string; + weight: number; + maxWorkers: number | null; + children: (Group | Leaf)[]; +}; + +// `compileLaneRoot`'s output, spelled out rather than imported: the ids and the +// group order ARE the contract this page renders, so writing them here asserts +// them a second time instead of re-deriving them from the code under test. +function channelLeaf(tier: string, slug: string): Leaf { + return { + id: `prio-${tier}-${slug}`, + match: { type: "channel", value: slug }, + weight: 1, + maxWorkers: null, + }; +} +function tierGroup(tier: string, slugs: string[]): Group { + return { + id: `prio-${tier}`, + mode: "strict", + weight: 1, + maxWorkers: null, + children: slugs.map((slug) => channelLeaf(tier, slug)), + }; +} +function compiledRoot(lane: string, focus: string[], normal: string[]): Group { + const children: (Group | Leaf)[] = []; + if (focus.length > 0) children.push(tierGroup("focus", focus)); + if (normal.length > 0) children.push(tierGroup("normal", normal)); + children.push({ + id: "prio-all", + match: { type: "all" }, + weight: 1, + maxWorkers: null, + }); + return { + id: `${lane}-root`, + mode: "strict", + weight: 1, + maxWorkers: null, + children, + }; +} + +// One hand-authored catch-all, the shape a corpus with no priorities carries. +const CATCH_ALL: Group = { + id: "root", + mode: "strict", + weight: 1, + maxWorkers: null, + children: [ + { id: "all", match: { type: "all" }, weight: 1, maxWorkers: null }, + ], +}; + +// The digest lane is the one whose work list a spec can seed from a transcript +// alone (writeDigestVideo), and the one lane-runner.spec already drives. +function settingsDoc(over: Record<string, unknown> = {}) { + return { + adminTitle: "Test Admin", + maxTranscriptPageBytes: 8388608, + sleepBetweenDownloadsSeconds: 0, + minFreeDiskGB: 0, + verifyAvailabilityBeforeClean: false, + syncScheduler: { fullSweepIntervalMinutes: 0 }, + digest: { + localAppId: "ollama-direct", + remoteAppId: "claude-code", + sections: ["chapters"], + yieldToTranscription: false, + }, + ...over, + }; +} + +async function seed(page: Page) { + await resetData(null); + await writeChannelConfig(FOCUSED); + await writeChannelConfig(OTHER); + await writeDigestVideo({ channelSlug: FOCUSED, videoId: "focusvid0001" }); + await writeDigestVideo({ channelSlug: OTHER, videoId: "othervid0001" }); + await writeSite(SITE, { + siteTitle: SITE_TITLE, + channels: [{ slug: FOCUSED }], + }); + // The snapshot is the lane's WORK LIST — nothing is pending until it exists. + await writeSettings( + settingsDoc({ + autoQueue: { digest: { enabled: true, maxWorkers: 1, root: CATCH_ALL } }, + }), + ); + await generateReport(page, FOCUSED); + await generateReport(page, OTHER); +} + +// The lane's console, hydrated. Every assertion below reads state React put +// there, so waiting for the section rather than for a timeout is the difference +// between a spec and a race. +async function openDigestConsole(page: Page) { + await page.goto("/operations/digest"); + await expect(page.locator('section[data-lane="digest"]')).toHaveAttribute( + "data-hydrated", + "true", + { timeout: 30_000 }, + ); +} + +test("a site focus banners the lane, compiles the ladder and freezes the editor", async ({ + page, +}) => { + test.setTimeout(SLOW); + await seed(page); + + // No focus yet: the page is the page it has always been. + await openDigestConsole(page); + await expect(page.locator("[data-focus-banner]")).toHaveCount(0); + await expect( + page.getByRole("button", { name: "+ Channel rule" }).first(), + ).toBeVisible(); + + // THE DOCUMENT, AND THE TREES IT COMPILES TO, in one write — which is what + // S3's single writer puts on disk. + await writeSettings( + settingsDoc({ + channelPriority: { + focus: { kind: "site", siteId: SITE }, + channels: {}, + }, + autoQueue: { + digest: { + enabled: true, + maxWorkers: 1, + root: compiledRoot("digest", [FOCUSED], [OTHER]), + }, + }, + }), + ); + + await openDigestConsole(page); + + // (1) THE BANNER. The site's TITLE, not its id — the model stores a siteId and + // the name is resolved on the server. + const banner = page.locator('[data-focus-banner="digest"]'); + await expect(banner).toBeVisible(); + await expect(banner).toContainText(`Focus: ${SITE_TITLE} (1 channel)`); + await expect(banner).toContainText("1 pending in this lane"); + await expect(banner).toContainText("1 waiting behind it"); + // THE HELD COUNT. One non-focus channel has work it is not getting, because + // strict descent never reaches the group it is in while the focus group has + // anything. This is the display fact the banner exists for. + await expect(banner).toContainText("1 channel held"); + await expect(banner).toHaveAttribute("data-focus-holding", "true"); + + // (2) THE LADDER IS THE COMPILED TREE, keyed by the compiled ids. + await expect(page.locator('[data-node-id="prio-focus"]')).toHaveCount(1); + await expect( + page.locator(`[data-node-id="prio-focus-${FOCUSED}"]`), + ).toHaveCount(1); + await expect( + page.locator(`[data-node-id="prio-normal-${OTHER}"]`), + ).toHaveCount(1); + await expect(page.locator('[data-node-id="prio-all"]')).toHaveCount(1); + // The counts landed on the compiled leaves, which is the whole reason the + // payload had to stop shipping the stored tree: a leaf the runner does not + // have would read zero. + await expect( + page + .locator(`[data-node-id="prio-focus-${FOCUSED}"]`) + .getByRole("button", { name: /Show pending videos for rule/ }), + ).toHaveText(/1/); + + // (3) THE EDITOR IS READ-ONLY — the tree only. The rules cannot be rewritten, + // added to or removed from. + await expect( + page.getByRole("button", { name: "+ Channel rule" }), + ).toHaveCount(0); + await expect(page.getByRole("button", { name: "Remove" })).toHaveCount(0); + await expect(page.getByLabel("group mode").first()).toBeDisabled(); + await expect(page.getByLabel("rule match type").first()).toBeDisabled(); + await expect( + page.locator('[data-policy-compiled="true"]'), + ).toHaveCount(1); + // And only the tree: the lane's own switches still belong to this page. + await expect(page.getByLabel("video order for auto-digest")).toBeEnabled(); + + // THE PROSE FOLLOWS THE STATE. "How priority works" is a disclosure, so it is + // opened rather than read through it. The phrase asserted is the one only the + // disclosure carries: the ladder's own note opens with the same sentence by + // design — it states the same fact as a state rather than as a rule — so a + // shorter match resolves to both and fails strict mode. + await page.getByText("How priority works", { exact: false }).click(); + await expect( + page.getByText("All four lanes are compiled from that one document"), + ).toBeVisible(); +}); + +test("clearing the priority document puts the console back exactly as it was", async ({ + page, +}) => { + test.setTimeout(SLOW); + await seed(page); + + await writeSettings( + settingsDoc({ + channelPriority: { + focus: { kind: "site", siteId: SITE }, + channels: {}, + }, + autoQueue: { + digest: { + enabled: true, + maxWorkers: 1, + root: compiledRoot("digest", [FOCUSED], [OTHER]), + }, + }, + }), + ); + await openDigestConsole(page); + await expect(page.locator('[data-focus-banner="digest"]')).toBeVisible(); + + // Focus ended, priorities cleared, the hand-authored tree back. + await writeSettings( + settingsDoc({ + autoQueue: { digest: { enabled: true, maxWorkers: 1, root: CATCH_ALL } }, + }), + ); + await openDigestConsole(page); + + await expect(page.locator("[data-focus-banner]")).toHaveCount(0); + await expect(page.locator('[data-node-id="prio-focus"]')).toHaveCount(0); + await expect(page.locator('[data-node-id="all"]')).toHaveCount(1); + await expect( + page.getByRole("button", { name: "+ Channel rule" }).first(), + ).toBeVisible(); + await expect(page.getByLabel("rule match type").first()).toBeEnabled(); + await expect(page.locator('[data-policy-compiled="false"]')).toHaveCount(1); +}); + +test("a focus that resolves to nothing banners nothing and compiles no group", async ({ + page, +}) => { + test.setTimeout(SLOW); + await seed(page); + + // An unknown siteId resolves to no channels, which compiles NO focus group — + // deliberately, so a typo leaves the tree as it would be with no focus rather + // than holding the whole corpus behind a site that does not exist. + await writeSettings( + settingsDoc({ + channelPriority: { + focus: { kind: "site", siteId: "not-a-site" }, + channels: {}, + }, + autoQueue: { + digest: { + enabled: true, + maxWorkers: 1, + root: compiledRoot("digest", [], [FOCUSED, OTHER]), + }, + }, + }), + ); + await openDigestConsole(page); + + await expect(page.locator("[data-focus-banner]")).toHaveCount(0); + await expect(page.locator('[data-node-id="prio-focus"]')).toHaveCount(0); + // The document still says something, so the tree is still GENERATED and the + // editor still read-only — an unresolvable focus is not an absent document. + await expect( + page.locator(`[data-node-id="prio-normal-${FOCUSED}"]`), + ).toHaveCount(1); + await expect( + page.getByRole("button", { name: "+ Channel rule" }), + ).toHaveCount(0); +});