Archilyzer · Source

archilyzer

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

commit 1b45aae6c049a62ac9c3e0c3e2f2759be8010a5d
parent 78379b24091fe0fcb1e5ac64117d11edce3dad8f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  1 Oct 2026 15:34:24 -0400

umtool: a teaser entry is a card row named by its lines; the teaser's supporting lines a size up

The report page's timeline row and the export's fallback chapter name a
`teaser` by its lines joined with " — " (teaserTitle); the On-screen table
already does, through deckText. onscreen-fixture carries a teaser, and
onscreen.spec checks the table's placeholder, the preview's node and the
report page's row. The overline, second tier and kicker are drawn a size
larger, so the card holds the frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mumtool/e2e/fixtures/make-fixture.mjs | 8+++++++-
Mumtool/e2e/onscreen.spec.ts | 5+++++
Mumtool/lib/projects/report.mjs | 4+++-
Mumtool/lib/report/export.mjs | 5++++-
Mumtool/report-to-video/chrome-teaser.mjs | 10+++++-----
5 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/umtool/e2e/fixtures/make-fixture.mjs b/umtool/e2e/fixtures/make-fixture.mjs @@ -1483,13 +1483,19 @@ const deckManifest = (slug, title, timeline) => { // onscreen-fixture: the On-screen section and the bench's fields WRITE here -- // the switch, the settings, the table, a stale token. Never built: its // schedule is the estimate, which is the state a report is in when titles are -// first written. A card, because a row is any entry and not only a clip. +// first written. A card, because a row is any entry and not only a clip; a +// teaser last, because the page and the table must name one by its lines. const ONSCREEN = writeProject( "onscreen-fixture", deckManifest("onscreen-fixture", "The On-screen Fixture", [ { type: "clip", id: "c01", video: "vid1", start: 3.0, end: 6.0, cite: 3, section: 0, lock: true, quote: "and because" }, { type: "clip", id: "c02", video: "vid1", start: 9.0, end: 12.0, cite: 9, section: 0, lock: true, quote: "another whole sentence" }, { type: "card", id: "k01", style: "chapter", seconds: 3, heading: "A card" }, + { + type: "teaser", id: "t01", seconds: 6, + lines: ["Next Season", { text: "The Big Build in the Valley", break: "in the Valley" }, "Spring 2027"], + tail: "?", + }, ]), ); diff --git a/umtool/e2e/onscreen.spec.ts b/umtool/e2e/onscreen.spec.ts @@ -268,6 +268,11 @@ test("the preview is the composition: it reports ready, and typing reaches it be const cardAuto = await row(page, "k01").getByTestId("onscreen-title").getAttribute("placeholder"); expect(cardAuto).toBe("A card"); await expect(frame.locator('[data-seg="k01"] .deck-title')).toHaveText(cardAuto!); + // A teaser is a card row named by its lines, here and on the report page's timeline. + const teaserTitle = "Next Season — The Big Build in the Valley — Spring 2027 ?"; + await expect(frame.locator('[data-seg="t01"]')).toHaveCount(1); + expect(await row(page, "t01").getByTestId("onscreen-title").getAttribute("placeholder")).toBe(teaserTitle); + await expect(page.locator('li[data-entry="t01"][data-kind="teaser"]')).toContainText(teaserTitle); // Typing is patched into the frame by postMessage: nothing is saved. await fillSure(row(page, "c02").getByTestId("onscreen-title"), "Typed, not saved"); diff --git a/umtool/lib/projects/report.mjs b/umtool/lib/projects/report.mjs @@ -11,6 +11,7 @@ import { DEFAULT_VARIANT, cachedWindowsFor } from "umtool-report-to-video/build- import { rawCacheOf } from "../report/raw-cache.mjs"; import { CHANNELS_DIR } from "../paths.mjs"; import { channelName, cleanTitle } from "umtool-report-to-video/attribution"; +import { teaserTitle } from "umtool-report-to-video/deck"; /** * Where a build's per-entry segments live. @@ -1110,7 +1111,8 @@ export async function readClipDetail(dir, { manifest = null } = {}) { // and 500'd the whole project page. Every other real manifest is clips only, // which is exactly why this survived testing. if (e.type !== "clip") { - entries.push({ ...e, kind: e.type ?? "entry" }); + // A teaser has no heading or title of its own: its row names its lines. + entries.push({ ...e, kind: e.type ?? "entry", ...(e.type === "teaser" ? { label: teaserTitle(e) } : {}) }); continue; } const chan = channelFor(m, e); diff --git a/umtool/lib/report/export.mjs b/umtool/lib/report/export.mjs @@ -18,6 +18,7 @@ import { readFile, readdir, stat } from "node:fs/promises"; import path from "node:path"; import { DEFAULT_VARIANT, selectVariant, segmentOffsets } from "umtool-report-to-video/build-video"; import { citeUrlFor, channelFor, readAvailability, readManifest } from "../projects/report.mjs"; +import { teaserTitle } from "umtool-report-to-video/deck"; export const EXPORT_FORMATS = ["toc-bbcode", "toc-markdown", "description", "chapters"]; @@ -60,7 +61,9 @@ export function parseFfmeta(text) { } /** The chapter title the build would print: `chapter`, else the entry's own words. */ -const titleOf = (e, i) => e.chapter ?? (e.type === "clip" ? `${i + 1}. ${e.video}` : e.title ?? e.heading ?? `Card ${i + 1}`); +const titleOf = (e, i) => + e.chapter ?? + (e.type === "clip" ? `${i + 1}. ${e.video}` : e.type === "teaser" ? teaserTitle(e) : e.title ?? e.heading ?? `Card ${i + 1}`); /** * Deliverable-second offsets for every entry of the chosen variant. diff --git a/umtool/report-to-video/chrome-teaser.mjs b/umtool/report-to-video/chrome-teaser.mjs @@ -177,10 +177,10 @@ export function teaserCues({ lines, tail = "", seconds, motion = TEASER_MOTION } /** Each role's type: size (px, the most it may be), weight, width (%), tracking (em), and the fit floor. */ export const TEASER_TYPE = Object.freeze({ - overline: Object.freeze({ size: 30, weight: 600, stretch: 125, tracking: 0.48, floor: 18 }), + overline: Object.freeze({ size: 34, weight: 600, stretch: 125, tracking: 0.48, floor: 18 }), title: Object.freeze({ size: 148, weight: 900, stretch: 112, tracking: -0.006, floor: 56 }), - sub: Object.freeze({ size: 34, weight: 600, stretch: 125, tracking: 0.4, floor: 18 }), - kicker: Object.freeze({ size: 76, weight: 800, stretch: 118, tracking: 0.04, floor: 32 }), + sub: Object.freeze({ size: 38, weight: 600, stretch: 125, tracking: 0.4, floor: 18 }), + kicker: Object.freeze({ size: 84, weight: 800, stretch: 118, tracking: 0.04, floor: 32 }), }); /** @@ -294,10 +294,10 @@ export function teaserHtml(entry, render, opts = {}) { ${typeCss(".kicker > .pop > .row", ty.kicker)} .kicker > .pop > .row { line-height: 1.1; } .kicker .txt { color: ${pal.fg}; } - .overline { margin-bottom: 34px; } + .overline { margin-bottom: 38px; } .title + .title { margin-top: 10px; } .title .sub { margin-top: 14px; } - .kicker { margin-top: 64px; } + .kicker { margin-top: 70px; } /* An overline between two hairline rules in the accent. */ .rules { position: absolute; left: -132px; right: -132px; top: 50%; height: 2px; transform-origin: 50% 50%; } .rule { position: absolute; top: 0; width: 96px; height: 2px; border-radius: 1px; }