Replace the plans gallery's card grid with the row list, in-flight band, and sticky topbar specified by docs/prototypes/plans-site-redesign.html, and bring the artifacts, prototypes, and social galleries onto the same shell — without changing the <a class="gallery-card"> splice unit that scripts/merge-plans-index.mjs depends on.
PR #503 gave the plans gallery the prototype's colours but left its card grid in place, so the shipped page still does not look like the design that was signed off. This swaps the 2-up cards for the prototype's dense row list, gives in-flight plans a real step-progress bar derived from the plan HTML, and puts one sticky topbar across all four galleries — which also means dragging the prototypes and social galleries onto the shared token set, since they are still on the pre-503 palette and one of them fails WCAG today. Done when every gallery renders rows under the same shell in both themes, the merge driver and its two tests pass unedited, and no gallery stylesheet contains a retired token.
Read and implement all steps in the plan at docs/plans/refactor-gallery-rows-and-topbar.md — Give the galleries the row layout and shell the prototype specified. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/refactor-gallery-rows-and-topbar.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: Give the galleries the row layout and shell the prototype specified. The plan at docs/plans/refactor-gallery-rows-and-topbar.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-gallery-rows-and-topbar.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-gallery-rows-and-topbar.html
docs/plans/refactor-gallery-rows-and-topbar.html
docs/plans/refactor-gallery-rows-and-topbar.md
Context
The story behind this plan — what prompted the work and why it matters now.
Commit dd5c425 (PR #503) rebuilt the plan-document shell and adopted the prototype's design tokens in kit/plugins/plan-agent/templates/plans-gallery.html, but kept the gallery's layout. Its plan, docs/plans/refactor-plan-and-gallery-design.md, scoped step 8 to "Rebuild the controls" and listed only "search plus status segmented control, type and effort disclosure, client-side In-flight and month grouping" against that template. The layout was never attempted, so the shipped docs/plans/index.html is a 2-up card grid wearing the prototype's palette while the prototype specifies a single-column row list. This plan closes that gap.
Two objections recorded in the previous plan turn out not to hold, and the steps below depend on both being wrong.
The first is the missing data source for the in-flight band's step bar. build-index.sh already reads each plan's full rendered HTML to pull its meta tags, and since PR #503 every plan carries one class="step-card" per step and class="step-card completed" on each done step. Counting both gives done / total with no new spec field and no new parser — docs/plans/refactor-plan-and-gallery-design.html measures 11 of 11, docs/plans/add-plan-phase-checkpoints.html measures 0 of 12. The prototype's phase line ("Phase 2 of 3 — Renderer") stays out: phases genuinely do not exist yet, and docs/plans/add-plan-phase-checkpoints.md is the plan that adds them.
The second is the merge driver. scripts/merge-plans-index.mjs:35 is const CARD_RE = /<a class="gallery-card"[\s\S]*?<\/a>/g — non-greedy, terminating at the first </a>. A row anchor is still one <a class="gallery-card">…</a> with no nested anchor, so the driver keeps matching without an edit. What the driver does constrain is sharper than "do not touch the cards": <a class="gallery-card" must stay the leading attribute pair, no card may contain a nested <a>, and the counts the driver rewrites must keep their shape. That last one is looser than it looks — COUNT_RE at merge-plans-index.mjs:104 is /(<p>|<span>|·\s*)(\d+)(\s*(?:plans|items|artifacts)\b)/g, so what matters is a <p> or <span> opening immediately followed by digits and then the word items, plans, or artifacts. The header's <p>98 items — click any card to open it</p> matches on its prefix and the trailing prose is free text; the footer's <span>98 items</span> matches whole. Rewording the header for rows is safe, moving the number away from the opening tag is not. The driver also splices over everything between the first and last card, which rules out the prototype's <ul class="rows"><li> wrapper: <li> tags between cards would be destroyed by the first concurrent merge and stay destroyed until the next regeneration. Rows are therefore bare anchors laid out as grid items directly inside #galleryGrid, and the in-flight band is a style applied to [data-status="in-progress"] rows in place rather than a separate container the cards are moved into.
Card markup is emitted from six places that are kept in step by convention alone. docs/plans/build-index.sh, scripts/build-plans-index.sh, and kit/plugins/plan-agent/hooks/build-index.sh are byte-identical copies (204708e3e621b1e0a83f8d2254f95f8f today) with no test guarding the identity. kit/plugins/plan-agent/hooks/build-artifacts-index.sh renders through the same plans-gallery.html template, so the row layout lands on the artifacts gallery whether or not that was asked for; its cards carry data-status="" and no effort, so the glyph column has to degrade rather than render an empty cell. kit/plugins/plan-agent/hooks/build-prototypes-index.sh renders through its own template. And kit/plugins/plan-agent/skills/plans-library/SKILL.md carries a sixth copy of the card heredoc, which tests/plugins/test-index-card-count.mjs extracts and executes — so a change made in the build scripts but not in the skill drifts silently past the test suite.
Scope grew once the topbar was confirmed. prototypes-gallery.html and kit/plugins/social-media-tools/templates/gallery.html never received the PR #503 refresh: the first still declares --subtle: #9ca3af, the retired token that measures 2.5:1 on white and was the WCAG failure #503 removed from the plan shell, and the second is a hardcoded dark-only GitHub palette with no light mode and no theme toggle. A nav bar whose tabs lead to two pages that look like a different site is not shippable, so both templates move onto the shared token set in the same change. That is also why social-media-tools gets a version bump alongside plan-agent.
The docs hub at docs/index.html stays out. It is already a recorded follow-up on the previous plan, it is hand-maintained with no generator, and it needs a design (the prototype's collections list) rather than a port.
workflow: never is set deliberately. Three byte-identical shell scripts, one template shared by two galleries, and a sixth card copy inside a SKILL.md are exactly the shape subagents corrupt: the renderer's file-count heuristic would otherwise license fan-out this plan cannot use.
Files that change
Every file this plan touches, and what happens to each one.
- kit/plugins/plan-agent/templates/
plans-gallery.htmlmodified row layout, in-flight band, topbar; the view toggle deletedprototypes-gallery.htmlmodified shared token set, theme toggle, row layout, topbar
kit/plugins/social-media-tools/templates/gallery.htmlmodified shared token set, theme toggle, topbardocs/plans/build-index.shmodified row card markup, step counts, cross-gallery countsscripts/build-plans-index.shmodified same edit, kept byte-identical- kit/plugins/plan-agent/hooks/
build-index.shmodified same edit, kept byte-identicalbuild-artifacts-index.shmodified row card markup without a status glyph, cross-gallery countsbuild-prototypes-index.shmodified row card markup, cross-gallery counts
kit/plugins/plan-agent/skills/plans-library/SKILL.mdmodified card heredoc kept in step with the build scriptskit/plugins/social-media-tools/skills/media-library/SKILL.mdmodified topbar variables in its gallery generatordocs/plans/index.htmlgenerated regenerateddocs/artifacts/index.htmlgenerated regenerateddocs/prototypes/index.htmlgenerated regenerateddocs/media/social/index.htmlgenerated regenerated- tests/plugins/
test-gallery-row-layout.mjsnew objective-verification testtest-build-index-parity.mjsnew checksum guard for the three build-script copies
.claude-plugin/marketplace.jsonmodified plan-agent 7.5.0 to 7.6.0, social-media-tools 2.20.1 to 2.21.0kit/plugins/plan-agent/CHANGELOG.mdmodified 7.6.0 entrykit/plugins/social-media-tools/CHANGELOG.mdmodified 2.21.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.
tests/plugins/test-build-index-parity.mjs asserting the three build-script copies (docs/plans/build-index.sh, scripts/build-plans-index.sh, kit/plugins/plan-agent/hooks/build-index.sh) hash identically, and confirm it passes against the tree as it stands.
node tests/plugins/test-build-index-parity.mjs exits 0 now, and exits 1 with the offending path named after appending a comment line to one copy in a scratch checkout.plans-gallery.html — replace the .gallery-grid card rules with the prototype's row grid applied to .gallery-card itself (grid-template-columns: 1.25rem minmax(0,1fr) 9rem 3.5rem, align-items: baseline, border-bottom: 1px solid var(--rule-soft)), add .glyph, .r-title, .r-meta, .r-date and the .s-completed / .s-in-progress / .s-todo colour rules from the prototype, add an .sr-only clip utility for the status text step 4 emits, add the serif --prose subtitle under the h1, collapse to the prototype's 700px breakpoint, and delete .view-toggle, .view-btn, both toggle buttons, and the view-switching JavaScript outright.
view-btn or list-view selector and no repeat(auto-fill declaration, the regenerated page renders one row per plan, and grepping the template for --subtle, --grey-bg, and --border-mid returns nothing..gallery-card[data-status="in-progress"] with the prototype's border-left: 2px solid var(--signal), a bold title, and a .bar of <i> segments the inline script draws from data-steps-done and data-steps-total, leaving the N / M steps text server-rendered so it survives with JavaScript off.
<i> tags per card, while server-rendering the count keeps the information present when the script does not run. Why the bar is styled in place rather than moved into a .flight-list container: the merge driver splices over everything between the first and last card, so a second container would be destroyed by the first concurrent merge.data-steps-done, the same card still matches /<a class="gallery-card"[\s\S]*?<\/a>/, and disabling JavaScript leaves the N / M steps text visible.<span class="glyph" aria-hidden="true"> carrying ✓ for completed, ○ otherwise, immediately followed by <span class="sr-only">completed</span> (or in progress / todo), then .r-title, .r-meta (type · effort), and .r-date — and add data-steps-done / data-steps-total counted from the plan HTML the script already reads (class="step-card followed by a quote or a space for the total — matching on class="step-card" alone would exclude every completed step, since the renderer emits those as class="step-card completed" — and class="step-card completed" for done) plus the N / M steps span on in-progress plans only. Keep <a class="gallery-card" as the leading attribute pair, emit no nested <a> and no <li>, and keep both counts matching COUNT_RE — the number stays immediately after the <p> or <span> opening and immediately before the word items, though the header's trailing "click any card to open it" is free text and should be reworded for rows.
scripts/merge-plans-index.mjs and its two tests depend on. The glyph is aria-hidden with a visually-hidden text sibling rather than labelled directly, because the card layout it replaces carried a readable status pill and dropping to a glyph alone would lose that information for anyone not looking at the page — an aria-label on the anchor was rejected for the same reason it usually is, that it overrides the row's own text and makes the announced content diverge from the visible content.node tests/plugins/test-build-index-parity.mjs exits 0, the regenerated index has one data-steps-total per plan card, an accessibility-tree dump of three rows announces their status followed by the title, the counted values match grep -c on two spot-checked plan files, and bash tests/plugins/test-merge-gallery-index.sh passes unedited.kit/plugins/plan-agent/skills/plans-library/SKILL.md.
tests/plugins/test-index-card-count.mjs extracts the gallery-card heredoc from that file and executes it, so the skill carries a sixth copy of the card markup that drifts silently from the build scripts if it is not updated in the same step — and the skill is what runs when a user invokes /plan-agent:plans-library rather than the hook.node tests/plugins/test-index-card-count.mjs exits 0 unedited, and a diff of the skill's heredoc against the build script's cards.append block shows the same attributes and child spans in the same order.build-artifacts-index.sh to emit the row shape with no status glyph (its cards carry data-status="", and an empty first column is worse than a collapsed one) and build-prototypes-index.sh likewise, then replace the token block in prototypes-gallery.html with the shared palette, dark rules, and pre-paint theme script from plans-gallery.html and give it the same row CSS.
build-artifacts-index.sh renders through plans-gallery.html, so the artifacts gallery inherits step 2's layout whether or not its generator is updated — leaving it emitting card markup produces broken rows; and prototypes-gallery.html is still on the pre-503 palette including --subtle: #9ca3af, which measures 2.5:1 and is a live WCAG AA failure, not a cosmetic gap.color on .r-meta clears 4.5:1 against --paper in both themes on both pages.plans-gallery.html and prototypes-gallery.html — the prototype's .topbar / .topbar-in / .mark / .tabs with the opaque background: var(--paper) declared before the color-mix line, tabs for Home, Plans, Prototypes, Artifacts, and Social each carrying a <span class="n"> count, and aria-current="page" on the tab matching the page being generated. Feed the counts and the tab hrefs from template variables the generators substitute: counts from *.html minus index.html on disk in each collection directory, hrefs from os.path.relpath between each target index and the page being generated. Read the plans collection from the resolved plansDirectory rather than a fixed docs/plans, since a project that moves it would otherwise get a wrong Plans count and tab links that resolve from the wrong depth.
index.html files, because the four generators run in arbitrary order and a parse would read whichever indexes happened to be stale; the opaque background is declared first because a browser without color-mix drops that declaration and a transparent sticky bar over scrolled text is unreadable.ls on its directory, the active tab is the only element with aria-current="page", the bar stays legible over scrolled content with color-mix forced off in DevTools, and tabbing reaches every tab with a visible focus ring.kit/plugins/social-media-tools/templates/gallery.html with the shared token set, dark rules, and pre-paint theme script, add the theme toggle and the same topbar with aria-current="page" on Social, and update kit/plugins/social-media-tools/skills/media-library/SKILL.md to substitute the count variables.
plan-theme preference set on the plans gallery, and renders legibly in the light theme it has never had.tests/plugins/test-gallery-row-layout.mjs asserting the objective end to end, then regenerate all four indexes.
node tests/plugins/test-gallery-row-layout.mjs exits 0, and the four regenerated indexes each contain the topbar, no view-btn, and one row per item.plan-agent from 7.5.0 to 7.6.0 and social-media-tools from 2.20.1 to 2.21.0 in .claude-plugin/marketplace.json, and add the matching CHANGELOG entries covering the row layout, the in-flight step bar, the topbar, and the token adoption in the two stale templates.
kit/plugins/<name>/ to ship a version exceeding main's, both plugins are touched here, and a new user-facing shell across four galleries is a minor bump rather than a patch.BASE_REF=main node scripts/check-plugin-versions.mjs exits 0 with both new versions reported.Tests
The tests that prove the change does what it promises.
build-index.sh over a fixture plans directory produces an index where every card matches CARD_RE extracted from the source of scripts/merge-plans-index.mjs (the driver runs its CLI on import and exports nothing, so the test reads the literals out of the file rather than importing them — which also keeps the driver unedited, as required) and the match count equals the fixture plan count, no card contains a nested <a> or any <li>, <a class="gallery-card" is the leading attribute pair on every card, COUNT_RE extracted the same way matches exactly twice with both numbers equal to the card count, each in-progress card carries data-steps-done/data-steps-total matching the fixture's step-card counts, every plan card carries a visually-hidden status text sibling to its aria-hidden glyph, the stylesheet contains no view-btn selector and no retired token name, and the topbar renders five tabs with exactly one aria-current="page"; Run: node tests/plugins/test-gallery-row-layout.mjsstep-card counting block in build-index.sh; Key cases: a plan with no steps, a plan with zero completed, a plan fully completed, a plan whose HTML contains step-card-header (must not be counted as a step)Definition 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.
Regenerate everything and compare against the design it is supposed to match. Run bash docs/plans/build-index.sh, then the artifacts, prototypes, and media generators, and open docs/plans/index.html beside docs/prototypes/plans-site-redesign.html. The shipped page should now carry the same four things the prototype does: a sticky topbar with per-collection counts, an in-flight band whose rows show a step bar and N / M steps, a dense row list with a status glyph rather than a badge pill, and month separators with a rule. The phase line is the one prototype element deliberately absent.
Measure rather than eyeball — screenshots have come back blank in this repo. In each of the four galleries, in both themes, read the computed color and background-color of .r-title, .r-meta, .r-date, .glyph on all three status classes, and the topbar tab text, and confirm every pair clears 4.5:1. The glyph colours are the new risk: --moss and --signal were verified against --paper for chips with a tinted background, and a bare glyph sits on the page background instead. Then narrow to 375px and confirm each gallery collapses without horizontal scroll, and tab through the topbar confirming every tab takes a visible focus ring. Dump the accessibility tree for three plan rows — one per status — and confirm each announces its status as text before its title, since the glyph carrying that status visually is aria-hidden and the row would otherwise be silent about it.
The steps stay in order and ship together. The topbar spans four galleries, so any split leaves a nav bar pointing at pages that do not have it yet, and the two stale templates carry a live WCAG failure that should not outlive the PR that touches them.
Prove the merge contract survived. Run bash tests/plugins/test-merge-gallery-index.sh and node tests/plugins/test-index-card-count.mjs with scripts/merge-plans-index.mjs unedited, then simulate the real hazard: take the regenerated docs/plans/index.html, hand-build a second copy with one extra row card, and run the driver over both — the union must contain both card sets with no stray separators, <li> fragments, or duplicated topbars. Confirm COUNT_RE still matches exactly twice in the merged output and that both numbers were rewritten to the union's card count.
Spot-check the derived step counts against their sources. Pick two in-progress plans and compare each card's data-steps-done / data-steps-total with grep -c 'class="step-card completed"' and grep -c 'class="step-card"' on the plan's own HTML, and confirm step-card-header is not being counted as a step.
Finally run the full gate: node tests/plugins/test-gallery-row-layout.mjs, node tests/plugins/test-build-index-parity.mjs, node tests/plugins/test-index-card-count.mjs, bash tests/plugins/test-merge-gallery-index.sh, node tests/plugins/test-plan-redesign.mjs, node tests/plugins/test-build-plan-html.mjs, node tests/plugins/test-extract-plan-spec.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
- Status is selected off
[data-status], not the prototype's.s-completed/.s-in-progress/.s-todoclasses CARD_REmatches the class attribute with its closing quote, so a second class in there makes every card invisible to the merge driver. The attribute selectors are equivalent in effect. The same trap caught a template comment mid-implementation: a literal opening card tag written into the head comment became the "first card" for the splice, which would have eaten the whole page chrome on the first concurrent merge. The comment was reworded and the template now warns against ever spelling that tag out in the chrome.- The queued glyph drops the prototype's
opacity: .5 - it puts
--ink-3near 2.5:1 against the page, failing this plan's own contrast criterion. The glyph reads at full--ink-3instead: 5.06:1 light, 5.42:1 dark. - The current tab's count takes the accent colour
--ink-3on--accent-softmeasured 4.46:1, under AA by a hair. Every other text token cleared 4.5:1 unchanged.- The status segmented control wraps below 700px
- out of scope, but at 375px
.segclipped its fourth button ("Shipped") rather than scrolling, and the 375px criterion could not honestly be ticked with a control cut off. Oneflex-wrap: wrapin the existing media query. - The media gallery lost its colour transitions
- Chrome keeps painting a transitioned colour's pre-change value when only the custom property underneath it changed, so a theme toggle left the filter chips and the "View image" links in the light palette at 3.6:1 and 2.5:1. Measured after removal: 5.42:1 and 7.40:1.
- The media gallery's eight per-type badge hues became one neutral badge
- they were fixed to a dark background and half fell under 4.5:1 the moment the page gained a light theme. Type is still filterable in the toolbar above.
- The media gallery was regenerated by hand from its skill's documented steps
media-libraryis model-driven, not a script. Its index was also stale, 27 cards for 36 files on disk, so the rebuild picked up nine cards that had never been published.- The topbar reads the resolved
plansDirectory, not a fixeddocs/plans - raised in review of the plan. The first implementation counted and linked
docs/plansdirectly, so a project that configuresplansDirectoryelsewhere would have seen a wrong Plans count and tab links resolving from the wrong depth. Every tab href is nowos.path.relpath(target, output_dir), and the plans tab reports what the page actually rendered. The objective test's fixture now setsplansDirectory: docs/notes/plansso the default layout is never the only one exercised. - Landed as plan-agent 7.7.0, not 7.6.0
- PR #505 took 7.6.0 on main while this branch was open.
- Two verification steps were done by proxy
color-mixcould not be disabled in the browser, so the fallback was proven by deleting thecolor-mixdeclaration at runtime and reading the computed background back as opaque--paper; and the preview pane serves static snapshots that will not re-navigate, so the media gallery's pre-paint theme script was verified by re-executing the page's own script against the storedplan-themekey rather than by reloading. It applieddarkas written.