From a19662486a9da635260c369c6a3866fc70f88157 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 21:01:09 +0000 Subject: [PATCH 1/5] Spec page composition: relations and features in columns, facts and revisions in a panel beside them The spec pages read as one long list. Relations and features now take the wide column and pack into columns; a sticky panel beside them holds the figures (count-up, M3) and the revision timeline (rail draw-in, M13). A spec with no recorded features says so in the panel instead of showing a zero. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01HjyoQhmK85j5jqTFVYFDEh --- docs/design.md | 4 +- specs/016-visual-redesign/tasks.md | 1 + src/components/RelationList.astro | 61 +++-- src/pages/accessibility.astro | 4 +- src/pages/standards/[id].astro | 349 +++++++++++++++++++++-------- tests/e2e/spec-page.spec.ts | 29 +++ 6 files changed, 336 insertions(+), 112 deletions(-) diff --git a/docs/design.md b/docs/design.md index 70bb905..a52af49 100644 --- a/docs/design.md +++ b/docs/design.md @@ -249,7 +249,7 @@ package of the redesign that builds it. Anything not in this table does not anim | --- | --- | --- | --- | --- | --- | | M1 | Every page | Cross-document view transition, root crossfade 220ms | continuity | none (as today) | WP2 | | M2 | Home → `/mcp/`, `/standards/` → spec, `/explorer/` → scenario | Shared `view-transition-name` on the data view (matrix plate from `MatrixThumb.astro` as `matrix`, map; on the explorer, the scenario title into the page h1) so it morphs instead of crossfading | "this is the same object, larger" | none | WP3, WP5, WP6 | -| M3 | Home facts, server page facts (`Facts.astro`) | Count-up from 0 to the value, 600ms, once, when 50% in view, tabular figures | "these are live numbers" | value rendered at once | WP3 | +| M3 | Home facts, server page facts, spec page facts (`Facts.astro`) | Count-up from 0 to the value, 600ms, once, when 50% in view, tabular figures | "these are live numbers" | value rendered at once | WP3 | | M4 | Standards map, decorative plate | Highlighted path draws in (`stroke-dashoffset`), 1200ms, halo fades after, once on load | "follow this path" | path shown complete | WP5, WP8 | | M5 | Explorer chain | Per step: the sender and receiver light up in the actor head, the current arrow draws in (300ms, ease-out), the body fades up 8px (220ms); earlier steps keep a quieter arrow and later ones a hairline, in colour, not opacity, so text keeps its contrast | "you are here" | arrow and body shown, highlight and dimming applied without transition | WP6, R-08 | | M6 | Explorer chain | One scene clock (a Web Animations API timeline) holds the playhead; Play, Pause, Previous, Next, the scrub bar, a click on a step or a log line and the arrow keys all move it. Steps advance every 2.5 s. Auto-advance (amended by review follow-up 8): armed once per page, it starts when the chain has sat in view for 1 s with no input, and stops for good on hover, touch, focus, scroll or any key; it never restarts by itself and never starts with a step in the URL. Play, once pressed, pauses on any click, key or scroll | guided reading | Play button hidden, no auto-advance; the scrub bar and the step buttons still seek | WP6, R-08 | @@ -259,7 +259,7 @@ package of the redesign that builds it. Anything not in this table does not anim | M10 | Matrix | Cell dialog: backdrop fades, panel moves up 8px, 220ms, using `@starting-style` | "a detail opened" | appears | WP4 | | M11 | Matrix filters | Count text changes with no animation; hidden rows collapse instantly (today's behaviour) | n/a | n/a | WP4 | | M12 | Scanner report | The request ladder prints line by line, 60ms apart, when the section scrolls into view, once; the grade appears last | "this is what the scanner did, in order" | all lines shown | WP7 | -| M13 | Breach page | Timeline rail draws in top to bottom, 480ms, dates fade in behind it | "events in sequence" | shown | WP6 | +| M13 | Breach page, spec page revisions | Timeline rail draws in top to bottom, 480ms, dates fade in behind it | "events in sequence" | shown | WP6 | | M14 | Long-form pages (`Document.astro`) | Reading progress bar, 2px `--signal`, `animation-timeline: scroll(root)` behind `@supports` | place in the text | hidden | WP2 | | M15 | Controls | Colour and border transitions on hover/focus, 120ms | affordance | instant | WP2 | | M16 | Theme toggle | Icon swap only; no rotation | n/a | n/a | WP2 | diff --git a/specs/016-visual-redesign/tasks.md b/specs/016-visual-redesign/tasks.md index adb1f03..611c63a 100644 --- a/specs/016-visual-redesign/tasks.md +++ b/specs/016-visual-redesign/tasks.md @@ -204,3 +204,4 @@ Items from the redesign review, one pull request each, in order. - [x] R-08 The explorer as a scene: one Web Animations API timeline (a target-less scene clock) holds the playhead for Play, Pause, Previous, Next, a scrub bar in the stepper, the arrow keys, a click on a step and the decision log (M6). Auto-advance starts once the chain has sat in view for 1 s with no input, stops for good on hover, touch, focus, scroll or a key, never restarts and never runs under reduced motion; M6 in the plan is amended. Per step the sender and receiver light up in the actor head, the message draws in over 300ms, earlier steps keep a quieter arrow and later ones a hairline (colour, not opacity, so axe passes while stepping) (M5). The changed claims get their rail 40ms apart once the message lands (M7), now through the Web Animations API instead of an `is-fresh` class. A toggle swaps the chain in place through a same-document view transition of 220ms and pushes its URL; back and forward swap again, the analytics count the swap as a pageview (M17). A quiet log under the chain gains one line per step (M18). Durations come from the motion tokens (`--dur-message`, `--stagger` and `--dur-step` are new) through `durationToken()` in `src/lib/motion.ts`. Baselines through the `refresh-screenshots` label - [x] R-09 Content pass (WP9 above): section-root headlines under 24 characters, the title suffix and footer statement, an accessibility statement that lists every motion with its reduced-motion form, `check:prose` as a gate, and the about, contribute, made-in-Europe, legal and 404 pages in plain English - [x] R-10 Decorative plate (WP8 above): a deterministic miniature delegation chain in the head of the explorer and breach indexes and every spec page, beside the title on wide screens, drawn in once (M4) and complete under reduced motion, with social cards for the two section roots from the same generator +- [x] R-11 Spec page composition: under the head, relations and features take the wide column and a panel beside them holds the figures (revisions, relations, features, counting up as M3) and the revision timeline (drawing in as M13), sticky as on a breach page; below 60rem the panel follows the relations. Relation groups and feature topics pack into columns (`columns: 19rem`) instead of one long list; a group heading is the verb phrase alone ("Builds on", "Is built on by") and the source link follows the note. Features group by topic under a bar of how many carry each level, named in words beside it. A spec with no recorded features shows a sentence in the panel, not a zero and not an empty section. The withdrawn or expired notice moves into the main column. Baselines through the `refresh-screenshots` label diff --git a/src/components/RelationList.astro b/src/components/RelationList.astro index 609b2d1..decc8df 100644 --- a/src/components/RelationList.astro +++ b/src/components/RelationList.astro @@ -6,11 +6,9 @@ import MaturityTag from "./MaturityTag.astro"; interface Props { groups: RelationGroups; - /** The spec the groups belong to, for phrasing. */ - specName: string; } -const { groups, specName } = Astro.props; +const { groups } = Astro.props; interface Section { id: string; @@ -18,13 +16,16 @@ interface Section { entries: RelationEntry[]; } +// the page's own name is the subject, so the group heading is the verb phrase alone +const sentence = (phrase: string) => phrase.charAt(0).toUpperCase() + phrase.slice(1); + const sections: Section[] = []; for (const type of RELATION_TYPES) { const out = groups.outgoing.get(type); if (out?.length) { sections.push({ id: `out-${type}`, - heading: `${specName} ${RELATION_LABEL[type].forward}`, + heading: sentence(RELATION_LABEL[type].forward), entries: out, }); } @@ -34,7 +35,7 @@ for (const type of RELATION_TYPES) { if (rel?.length) { sections.push({ id: `rel-${type}`, - heading: `${specName} ${RELATION_LABEL[type].forward}`, + heading: sentence(RELATION_LABEL[type].forward), entries: rel, }); } @@ -44,7 +45,7 @@ for (const type of RELATION_TYPES) { if (inc?.length) { sections.push({ id: `in-${type}`, - heading: `${specName} ${RELATION_LABEL[type].backward}`, + heading: sentence(RELATION_LABEL[type].backward), entries: inc, }); } @@ -62,18 +63,25 @@ const typeOf = (id: string) => id.replace(/^(out|rel|in)-/, "") as RelationType;

{s.heading} {s.entries.length}

-