Rebuild the presentation layer of generated plan pages and the gallery index — design tokens with a persisted dark theme, a mono-and-serif type system, one merged goal panel, an always-visible Verify line, a sidebar step rail that absorbs the progress bar, and gallery controls that surface in-flight work first — without changing the DOM contract that extractSections and the gallery generator read.
Generated plan pages are hard to scan — two stacked summaries, every section in an identical bordered box, the Verify line hidden behind a disclosure, three competing progress devices, no dark mode, and a tertiary text colour that fails WCAG at 2.5:1. This rebuilds the presentation shell around a mono-chrome-plus-serif-prose type system, a single step rail that replaces the progress bar and the table of contents, and a gallery that leads with the plans actually in flight. Done when a rendered plan passes contrast in both themes, the extractor round-trips every committed plan unchanged, and the three gallery indexes regenerate with in-flight cards first.
Read and implement all steps in the plan at docs/plans/refactor-plan-and-gallery-design.md — Rebuild the plan document and gallery design. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/refactor-plan-and-gallery-design.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
More ways to run this plan — goal & workflow prompts, file path
Achieve this goal: Rebuild the plan document and gallery design. The plan at docs/plans/refactor-plan-and-gallery-design.md describes one approach — use it as reference, but optimize for the outcome. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/refactor-plan-and-gallery-design.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
refactor-plan-and-gallery-design.html
docs/plans/refactor-plan-and-gallery-design.html
docs/plans/refactor-plan-and-gallery-design.md
Context
The story behind this plan — what prompted the work and why it matters now.
Every generated plan is styled by one 1930-line module, scripts/lib/plan-shell.mjs, whose CSS export has drifted into a generic default: system-ui at 15px, #2563eb blue, 4px radius, and a 1px solid #e5e7eb border on every container. A visual audit of a rendered plan, the 88-card gallery at docs/plans/index.html, and the hub at docs/index.html found six concrete failures, and two prototypes at docs/prototypes/plan-document-redesign.html and docs/prototypes/plans-site-redesign.html were built and verified against them.
The failures are structural, not cosmetic. objectiveCard and glanceBlock render as siblings, so a reader hits two abstracts before any content and cannot tell which is authoritative — plan-shell.mjs line 376 even carries a margin: -1.5rem 0 1.5rem hack to pull the second up against the first. stepCard buries the Verify: text in a <details class="step-verify-toggle">, which is the one line a reader needs while executing. Three separate devices report progress — the shimmer bar at progressBlock, the icon nav from nav(), and the .steps-list::before timeline — and none of them says which step you are on, because NAV_ENTRIES has a single steps entry rather than one per step. --subtle: #9ca3af on #ffffff measures 2.5:1 and is used for nav icons and step chips, so the page fails WCAG AA today. There is no dark mode at all.
Three test gates constrain how this can be done, and they shaped the step order.
The first is the nav regex at tests/plugins/test-build-plan-html.mjs:635: [...html.matchAll(/<a href="#([a-z-]+)">/g)] requires each section anchor's href to be its only attribute. Step links are therefore emitted with a leading class attribute and ids containing digits (#step-1), both of which that regex skips — the section-link deepEqual keeps passing without being loosened.
The second is the back-compat guard at tests/plugins/test-build-plan-html.mjs:928. It renders the sample spec through the renderer at origin/main and through the working copy, blanks the three prompt payloads and the <style> block, and asserts the two documents are byte-identical. Its stated purpose in the comment above it is narrower than what it does: presentation is expected to evolve, and what the guard protects is "the DOM contract the extractor and the gallery read". As written it fails on any intentional markup change, and the inline <script> is not blanked either, so it also fails on the theme-toggle and scroll-spy edits. It is narrowed in step 2, before the first markup change lands, rather than left red across the whole PR.
The third is the byte-identical mirror at tests/plugins/test-build-plan-html.mjs:951, which asserts the three files under kit/plugins/plan-agent/scripts/ match their repo-root counterparts under scripts/. lib/plan-spec.mjs is not edited by this plan, so only two files are re-copied.
Two hazards were designed around rather than accepted. scripts/merge-plans-index.mjs is the git merge driver for the generated gallery indexes; it splices the union of <a class="gallery-card"> blocks over the region between the first and last card, so anything sitting between cards — a static month heading, for instance — is destroyed by a merge and stays destroyed until the next regeneration. Month grouping is therefore rendered client-side from a data-month attribute on each card rather than baked into the generated markup: the driver never sees a group header, CARD_RE is untouched, tests/plugins/test-merge-gallery-index.sh is untouched, and a filtered view re-groups correctly instead of leaving empty headings behind. The same technique carries the In-flight band, which is a client-side separator over cards the generator sorts first.
The second hazard is the theme toggle. plan-shell.mjs already selects on [data-status="…"] set on <html>, so the theme attribute is a second attribute on the same element (data-theme) rather than a class, and the two compose without either winning. The stored preference is read by a small inline script in <head> before first paint, because a plan opened from file:// has no server to set a class for it and a flash of the wrong theme on every page load is worse than no dark mode.
One redundancy is knowingly left in place. The prototype folds the progress bar into the sidebar rail so the page carries a single progress device; this plan does not, because relocating progressBlock would drop progress from NAV_ENTRIES and invalidate both nav deepEqual arrays for a purely cosmetic gain. The bar keeps its position and its ids, loses its shimmer animation in step 1, and the rail is a step index rather than a second progress readout. Merging the two is a follow-up, not a prerequisite.
Phases are deliberately not in scope. The step rail groups nothing today because parseSpecMarkdown returns steps as a flat { action, why, verify } list with no phase concept anywhere in the parser, the digest, the renderer, or the extractor — see docs/plans/add-plan-phase-checkpoints.md, which adds all four. That plan already edits nav()'s neighbours, so phase grouping is added there once the rail exists, rather than building a grouping hook here for data that does not exist yet.
workflow: never is set deliberately. Steps 1 through 6 are ordered edits to the same two files, scripts/lib/plan-shell.mjs and scripts/build-plan-html.mjs, so subagents fanning out would conflict on them. The renderer's own heuristic counts files and top-level directories and would otherwise license fan-out this plan cannot use.
Files that change
Every file this plan touches, and what happens to each one.
scripts/lib/plan-shell.mjsmodified tokens, dark palette, type roles, theme toggle, objective and step markup, step rail, scroll-spy fixscripts/build-plan-html.mjsmodified nest the glance in the objective card and pass the step list tonav(); theprogressBlockcall and theNAV_ENTRIESid list are left as they arekit/plugins/plan-agent/scripts/lib/plan-shell.mjsgenerated re-copied from the repo-root sourcekit/plugins/plan-agent/scripts/build-plan-html.mjsgenerated re-copied from the repo-root sourcekit/plugins/plan-agent/templates/plans-gallery.htmlmodified search plus status segmented control, type and effort disclosure, client-side In-flight and month groupingkit/plugins/plan-agent/hooks/build-index.shmodifieddata-monthattribute and in-progress-first sortscripts/build-plans-index.shmodified same edit, kept byte-identical- docs/plans/
build-index.shmodified same edit, kept byte-identicalindex.htmlgenerated regenerated by the build script
docs/artifacts/index.htmlgenerated regenerated by the build scriptdocs/prototypes/index.htmlgenerated regenerated by the build script- tests/plugins/
test-build-plan-html.mjsmodified narrow the back-compat guard; the two nav id arrays stay as they aretest-plan-redesign.mjsnew objective-verification smoke test
.claude-plugin/marketplace.jsonmodified plan-agent 7.4.4 to 7.5.0kit/plugins/plan-agent/CHANGELOG.mdmodified 7.5.0 entry
Steps
The step-by-step work, in order — each step says what to do, why it matters, and how to check it worked.
:root token block in plan-shell.mjs's CSS export with the prototype's palette and font stacks — --paper, --panel, --sunk, --ink, --ink-2, --ink-3, --rule, --rule-soft, --accent, --accent-soft, --accent-line, --moss, --signal, plus --mono, --ui, --prose — keeping every existing token name as an alias pointing at its replacement so no downstream selector breaks, then add the dark palette under both :root[data-theme="dark"] and @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) }, and retarget the existing chrome selectors (.plan-title, .plan-doc-type, .nav-heading, .plan-meta, .section-card h2, .step-number, .step-chip) to --mono, .section-card p to --prose, and .progress-bar-fill to a flat accent with the shimmer animation dropped.
<style> block, and it retires --subtle: #9ca3af, which measures 2.5:1 on white and is the page's current WCAG failure; aliasing rather than repointing keeps steps 1 through 5 safe against the old names, and step 6 removes both in one sweep.git diff touches no markup, that computed color on .plan-nav li a and .step-chip clears 4.5:1 against the page background in both themes, and that the literal string html { scroll-behavior: auto; } still appears verbatim.tests/plugins/test-build-plan-html.mjs:883 from a whole-document assert.equal(stripVolatile(after), stripVolatile(before)) to assert.deepEqual(extractSections(after), extractSections(before)) plus explicit assertions that the no-prototype render carries no plan-prototype meta tag and no prototype header link.
class="verify-body" in a scratch copy fails with the extractor contract named.data-theme-reading inline script at the top of <head> in page(), a labelled toggle button in header() beside the Save-as-PDF button with aria-pressed and a 44×44px hit area, and a handler in the SCRIPT block that flips documentElement.dataset.theme and writes the choice to localStorage.
<head> before first paint because a plan opened from file:// has no server to stamp the attribute and a wrong-theme flash on every load is worse than no toggle.aria-pressed, the choice survives a reload, a first visit with no stored value follows prefers-color-scheme, and the button is absent from the print stylesheet output.objectiveCard(objective) to objectiveCard(objective, glanceHtml = '') emitting the existing <section class="plan-glance"> inside the <div id="objective">, drop the separate main.push(shell.glanceBlock(…)) at build-plan-html.mjs:338, and replace stepCard's <details class="step-verify-toggle"><summary> wrapper with a plain labelled block that keeps <div class="verify-body"> intact.
Verify: is the line someone needs while executing a step rather than one they should have to open; both are safe because extractSections at plan-spec.mjs:219 already strips a nested .plan-glance and reads the verify text from class="verify-body" regardless of its wrapper.extractSections on the re-rendered sample returns an objective with no glance text in it, the glance still renders once, and the .plan-glance negative-margin rule is gone from the stylesheet..step-card an id="step-N" while keeping class="step-card completed" as a literal substring, and change nav(ids) to nav(ids, steps) so it emits the existing section links unchanged (<a href="#step-id">, no other attributes) plus a <ul class="rail-steps"> of <a class="rail-step" href="#step-N"> links, each carrying the step's action text and a visually-hidden state span reading step N of M, done or step N of M. Below 900px the rail's step list moves inside a closed <details> rather than being hidden, so a mobile reader keeps the jump targets. The progressBlock output and the NAV_ENTRIES id list are left exactly where they are.
steps entry, so a reader can neither see how far along the work is nor jump to a step; leaving the progress block and the nav id list untouched keeps the progress JavaScript at plan-shell.mjs:1438-1456, its two assertions, and both nav deepEqual arrays green, so this step adds markup without invalidating any existing test.deepEqual at test line 635 returns the unchanged id list, every #step-N anchor resolves to a card, each rail link exposes its state to an accessibility-tree dump, and at 375px wide the step list is reachable through the disclosure.plan-shell.mjs:1534-1545 only reacts to isIntersecting entries, so the last matched link stays highlighted forever — then delete the rules the redesign supersedes (.steps-list::before, .step-verify-toggle, and the .plan-glance sibling margin), remove the step 1 token aliases, and repoint every selector still naming an old token.
.active class, scrolling back restores exactly one, grepping the stylesheet for the three deleted selectors and for each retired token name returns nothing, and the rendered page is visually unchanged from the end of step 5.scripts/build-plan-html.mjs and scripts/lib/plan-shell.mjs over their counterparts under kit/plugins/plan-agent/scripts/, after steps 1 through 6 and before any test run.
tests/plugins/test-build-plan-html.mjs:951 asserts the bundled copies are byte-identical to the repo-root sources, so editing only one side both fails an existing test and ships the old design to anyone who installs the plugin.diff scripts/build-plan-html.mjs kit/plugins/plan-agent/scripts/build-plan-html.mjs and the same for lib/plan-shell.mjs each report no differences.kit/plugins/plan-agent/templates/plans-gallery.html — replace the three filter-chip rows with a search field, a status segmented control carrying per-status counts, and a <details> holding the type and effort chips, then extend the existing inline applyFilters() to insert an "In flight" separator before the in-progress run and a month separator between data-month values, rebuilding those separators on every filter change.
scripts/merge-plans-index.mjs splices, so neither the merge driver nor tests/plugins/test-merge-gallery-index.sh has to learn about non-card content.data-title, and the status control hides itself on the artifacts gallery, whose cards carry no status.data-month on each card and sort in-progress plans ahead of the date order in all three byte-identical copies of the build script — kit/plugins/plan-agent/hooks/build-index.sh, scripts/build-plans-index.sh, and docs/plans/build-index.sh — then regenerate docs/plans/index.html, docs/artifacts/index.html, and docs/prototypes/index.html.
md5sum of the three scripts match, every <a class="gallery-card" in the regenerated plans index carries a data-month, in-progress cards appear first, and the <p>N items</p> and <span>N items</span> strings the merge driver patches are unchanged.tests/plugins/test-plan-redesign.mjs asserting the objective end to end.
node tests/plugins/test-plan-redesign.mjs exits 0, and node tests/plugins/test-build-plan-html.mjs and node tests/plugins/test-extract-plan-spec.mjs both exit 0 with their nav id arrays unedited..claude-plugin/marketplace.json and add the matching kit/plugins/plan-agent/CHANGELOG.md entry covering the token set, the theme toggle, the merged goal panel, the step rail, and the gallery controls.
kit/plugins/ to ship a version exceeding the value on main, and a reworked presentation shell with a new user-facing control is a minor bump rather than a patch.BASE_REF=main node scripts/check-plugin-versions.mjs exits 0.Tests
The tests that prove the change does what it promises.
[data-theme] dark rule and a theme-toggle button, every text token in the stylesheet clears 4.5:1 against its background in both palettes, the glance renders inside id="objective" while extractSections returns an objective free of glance text, verify-body appears outside any <details>, one id="step-N" anchor exists per step with a matching a.rail-step link, and extractSections(renderPlanHtml(spec)) deep-equals the parsed spec sections; Run: node tests/plugins/test-plan-redesign.mjshref attribute, the id list is unchanged from before this plan, a spec with one step, a spec with twelve steps, step link ids matching the card ids, the visually-hidden state span present on every rail linkclass="step-card completed" as a literal substring, verify text preserved through the round tripdata-month, in-progress cards sort first, the count strings the merge driver patches are unchanged, a gallery whose cards carry no status hides the status controlDefinition of done
The plan counts as done when every statement below is true — check each one off as you verify it.
Final check
One last pass to confirm the whole change works end to end.
Render a committed plan end to end: node scripts/build-plan-html.mjs docs/plans/add-plan-phase-checkpoints.md -o /tmp/check.html and confirm exit 0. Open it and check the four things this plan is for — one goal panel rather than two stacked summaries, a visible Verify line under every step, a sidebar listing all twelve steps as jump targets, and a theme toggle that flips the page and survives a reload. Narrow the window to 375px and confirm the step list is still reachable through its disclosure, then dump the accessibility tree for the rail and confirm each link announces its step number and done state rather than relying on the tick glyph. Then measure rather than eyeball: read the computed color and background-color of .plan-nav li a, .step-chip, .plan-meta, and .section-card p in both themes and confirm each pair clears 4.5:1, since the current --subtle fails at 2.5:1 and a screenshot cannot show that.
Prove the DOM contract survived. Run node scripts/extract-plan-spec.mjs /tmp/check.html — the extractor ships only at the repo root, not in the plugin bundle — and confirm the printed spec matches docs/plans/add-plan-phase-checkpoints.md section for section, with the glance absent from the objective. Re-render that extracted spec and diff the two HTML files to confirm the cycle is stable. The real-corpus round trip inside tests/plugins/test-build-plan-html.mjs re-renders at least ten committed plans and is the broader version of this check.
Exercise the gallery. Run bash docs/plans/build-index.sh and open docs/plans/index.html: confirm the four in-flight plans appear above the month-grouped remainder, that the header and footer counts agree with the number of cards, that filtering to completed leaves no empty separator, and that search still matches on title. Open docs/artifacts/index.html and confirm the status control is hidden there, since those cards carry no status. Then confirm the merge driver is unaffected by running bash tests/plugins/test-merge-gallery-index.sh and node tests/plugins/test-index-card-count.mjs.
Finally run the full gate: node tests/plugins/test-plan-redesign.mjs, node tests/plugins/test-build-plan-html.mjs, node tests/plugins/test-extract-plan-spec.mjs, node tests/plugins/test-backfill-digest.mjs, and BASE_REF=main node scripts/check-plugin-versions.mjs.
Wrapping up
Three gates that must all pass before this plan is marked completed.
Completion Report
No items to report — all requirements met.