Implementation Plan

Retrofit responsive CSS into every HTML plan

completed
2026-06-12 agentics fix

Eliminate horizontal overflow in every HTML plan — retrofit all 55 shipped files in docs/plans/ with a versioned 8-line CSS block via an idempotent script, and bake the same block into the plan skeleton so every future plan is born responsive.

Implement Read and implement all steps in the plan at docs/plans/retrofit-responsive-plan-css.html — Retrofit responsive CSS into every HTML plan and harden the skeleton
Run as workflow — launch parallel subagents
Run a workflow to implement the plan at docs/plans/retrofit-responsive-plan-css.html — Retrofit responsive CSS into every HTML plan and harden the skeleton
File retrofit-responsive-plan-css.html
Path docs/plans/retrofit-responsive-plan-css.html
Acceptance criteria 8 / 8 done

Context

A 390px-viewport browser audit found 16 of the 54 HTML files in docs/plans/ overflow horizontally — the worst (add-multi-host-deploy-targets.html) by 294px of sideways scroll. Current-generation, digest-era plans are affected too: the defect is content-dependent, not template-generation-dependent.

Three root causes combine:

  • Unbreakable tokens with no wrap rule. File paths and commands like kit/plugins/docs-publisher/reference/.gitlab-ci.yml inside inline <code>, list items, and bare divs have no overflow-wrap anywhere in the template — a token wider than a phone screen simply cannot break.
  • Grid min-content propagation. Grid items default to min-width: auto, so one unwrappable token in main inflates the shared single-column track and stretches the sidebar with it. overflow-wrap: break-word wraps visually but does not reduce min-content size — only anywhere does, which is why both min-width: 0 and inherited overflow-wrap: anywhere are needed.
  • Component stragglers. The three-column .compare-grid does not collapse below 600px in some template generations, and table cells have no wrap rule.

The fix is proven: injecting the block below into all 54 rendered files in a width-controlled iframe harness took the overflow count from 16 to 0, with no effect on already-clean files (anywhere only activates when a single word exceeds the line, so normal prose is untouched).

/* plan-responsive-fix v1 — injected by scripts/retrofit-responsive-plans.mjs */
body { overflow-wrap: anywhere; }
main, nav, aside { min-width: 0; }
.layout > *, .wrap > *, .compare-grid > * { min-width: 0; }
pre { white-space: pre-wrap; max-width: 100%; }
table { max-width: 100%; }
img, video { max-width: 100%; height: auto; }
@media (max-width: 600px) { .compare-grid { grid-template-columns: 1fr; } }

Because every plan is a self-contained single-file HTML document (embedded CSS, no external assets — by design, so plans survive file:// opens, copies, and email), skeleton improvements never reach already-shipped plans. The retroactive mechanism is a versioned injector script, mirroring the scripts/backfill-plan-digests.mjs (2.3.0) and scripts/backfill-save-pdf.mjs (2.4.0) precedents — this is the third injector in that family. The block is wrapped as <style id="plan-responsive-fix" data-version="1"> and inserted immediately before </head>; the id + version pair is the idempotency key (current → skip, older → replace, missing → inject). docs/plans/index.html is always excluded — it is regenerated from scratch by docs/plans/build-index.sh via the rebuild hook and already renders clean at phone width.

Files to Modify

agentics/
  • scripts/retrofit-responsive-plans.mjs new idempotent injector with --check gate
  • docs/plans/*.html modified 55 files (incl. this plan) — v1 block injected before </head>
  • kit/plugins/plan-agent/
    • skills/implementation-plan/reference/SKELETON.html modified same v1 block embedded natively
    • CHANGELOG.md modified 2.4.1 entry
  • .claude-plugin/marketplace.json modified plan-agent 2.4.0 → 2.4.1
  • tests/plugins/test-responsive-retrofit.sh new corpus + fixtures + skeleton-sync guard

Diagram

Injector flow — scripts/retrofit-responsive-plans.mjs
Scan
docs/plans/*.html
non-recursive; index.html always excluded; archive only with --include-archive
Detect
id="plan-responsive-fix" + data-version
parse the existing block's version, if any
Apply
skip · replace · inject
current version → skip; older → replace whole block; missing → insert before </head>
Report
injected / updated / skipped
--dry-run and --check never write; files with no </head> reported as skips
Guard
--check → exit 1
tests/plugins/test-responsive-retrofit.sh runs it over the corpus on every test run
Retroactive delivery — approaches weighed
Inject versioned block — chosen
  • plans stay single-file, file:// portable
  • deterministic — no LLM pass
  • re-runnable: bump data-version for v2
  • mirrors the backfill-*.mjs precedents
Shared <link> stylesheet
  • breaks self-contained guarantee
  • copied/emailed plans lose styling
  • extra asset to deploy
Regenerate from digest
  • only 42/54 plans carry a digest
  • loses visuals + checkbox state
  • LLM cost, non-deterministic
Audit at 390px — plan files with horizontal overflow
CLI contract
Flags for scripts/retrofit-responsive-plans.mjs — the default invocation injects or replaces the block across docs/plans/.
FlagDefaultBehavior
(none)Inject or replace the v1 block in docs/plans/*.html; skips index.html and files already at v1
--checkoffNo writes; exit 1 listing files missing or outdated, exit 0 when the corpus is current
--dry-runoffPrint would-inject / would-replace actions without writing
--dir <path>docs/plansOperate on a different plans directory
--include-archiveoffAlso process docs/plans/archive/ recursively — explicitly opt-in

Steps

1
todo Create scripts/retrofit-responsive-plans.mjs — the idempotent injector
A deterministic, re-runnable script is the only fix that reaches already-shipped self-contained plans without an LLM pass. Mirror the backfill-plan-digests.mjs / backfill-save-pdf.mjs conventions: Node ≥ 18, zero dependencies, summary output. Contract: wrap the canonical CSS constant as <style id="plan-responsive-fix" data-version="1">; insert immediately before </head> (case-insensitive); skip files already at the current version; replace the whole block when an older version is found; always exclude index.html; report and skip files with no </head> rather than half-writing them. Flags: --check, --dry-run, --dir <path>, --include-archive.
Verify
node scripts/retrofit-responsive-plans.mjs --check exits 1 listing 55 files; --dry-run prints the same 55 as would-inject while git status stays clean; a synthetic fixture without </head> is reported as a skip, never modified.
2
todo Run the injector once over docs/plans/
This is the retroactive payload — all 55 plan files (every file except the hook-generated index.html, including this plan document itself, which was built from the pre-fix skeleton) pick up the v1 block in a single reviewable diff.
Verify
grep -L 'id="plan-responsive-fix"' docs/plans/*.html returns only index.html; an immediate second run reports 55 skipped and adds nothing to git diff (idempotency proven on the real corpus).
3
todo Embed the identical v1 block in SKELETON.html
New plans must be born responsive and pass --check natively — otherwise every freshly generated plan reintroduces the defect the retrofit just cleared. Insert the same <style id="plan-responsive-fix" data-version="1"> block immediately before </head> in kit/plugins/plan-agent/skills/implementation-plan/reference/SKELETON.html, byte-identical to the script's CSS constant (the script is the single source of truth; the skeleton carries a copy).
Verify
grep 'plan-responsive-fix' kit/plugins/plan-agent/skills/implementation-plan/reference/SKELETON.html finds the block before </head>; the CSS extracted from the skeleton block diffs empty against the script constant.
4
todo Add tests/plugins/test-responsive-retrofit.sh — corpus, fixtures, and sync guard
Turns the one-time migration into a standing invariant: the corpus check fails the moment a plan ships without the current block, and the sync assert stops the skeleton and the script from drifting apart. Cases: (a) --check exits 0 over the repo's docs/plans/; (b) fixture injection — missing block injected exactly once before </head>, second run byte-identical; (c) data-version="0" fixture replaced with v1, no duplicate block; (d) index.html and no-</head> fixtures untouched; (e) skeleton block CSS byte-identical to the script constant; (f) head-only guarantee — the injector's output is byte-identical to its input from <body onward, so the retrofit can never alter markup, ARIA, or semantics.
Verify
bash tests/plugins/test-responsive-retrofit.sh exits 0; stripping the block from a tmp corpus copy makes the --check assert exit 1 naming the stripped file.
5
todo Bump plan-agent to 2.4.1 and write the CHANGELOG entry
The skeleton is plugin-shipped behaviour — .claude/rules/marketplace.md mandates a manual semver bump (patch for a fix) in .claude-plugin/marketplace.json plus a kit/plugins/plan-agent/CHANGELOG.md entry with every plugin change.
Verify
marketplace.json shows plan-agent 2.4.1 (higher than main's 2.4.0) and passes the settings-hook JSON validation; CHANGELOG.md opens with a ## 2.4.1 entry dated 2026-06-12 describing the responsive block and the injector.
6
todo Re-audit all 55 plans at 390px and 320px in a real browser
Measured scrollWidth in a width-controlled harness is the ground truth the investigation used — the retrofit is done when the overflow count is zero, not merely when markers are present. The 320px pass grounds the result in WCAG 2.1 AA reflow (1.4.10), which requires no horizontal scrolling at 320px CSS width.
Verify
Serve docs/ locally (bash scripts/serve-docs.sh or python3 -m http.server --directory docs), load every plan in a 390px-wide and then a 320px-wide iframe, and assert documentElement.scrollWidth ≤ iframe width for all 55 files at both widths; spot-open add-multi-host-deploy-targets.html (previously +294px) and confirm no horizontal scrollbar.

Tests

Tier 1 — Code-touching plan
Objective No plan ships without the v1 responsive block

File: tests/plugins/test-responsive-retrofit.sh

Type: smoke test (runs against the real corpus)

Asserts: every docs/plans/*.html except index.html carries <style id="plan-responsive-fix" data-version="1">, and the skeleton's embedded block is byte-identical to the script's CSS constant — i.e. every shipped plan and every future plan is responsive-hardened, which is this plan's stated objective.

Run: bash tests/plugins/test-responsive-retrofit.sh

Unit Injector behaviour on synthetic fixtures

File: tests/plugins/test-responsive-retrofit.sh (fixture section)

Targets: marker detection, version comparison, insertion point, and exclusions in scripts/retrofit-responsive-plans.mjs

Key cases: missing block → injected exactly once, immediately before </head>; data-version="0" → replaced with v1 without duplicating the block; current v1 → skipped byte-identical; index.html and a fixture with no </head> → untouched and reported; head-only guarantee → output byte-identical from <body onward (no markup, ARIA, or semantics changes ever).

Integration CLI end-to-end over a corpus copy

File: tests/plugins/test-responsive-retrofit.sh (corpus section)

Targets: the full CLI — default run, --check, and --dry-run — against a tmp copy of docs/plans/

Key cases: first run injects every non-index file (55 at the time of writing); second run no-ops (tree byte-identical); --check exits 0 after the run and 1 when one file is stripped; --dry-run writes nothing.

Acceptance Criteria

Verification

Confirm the whole change end-to-end, in this order:

  1. Run bash tests/plugins/test-responsive-retrofit.sh — exit 0 covers the corpus check, fixture cases, and the skeleton/script sync assert.
  2. Serve docs/ locally and re-run the width-controlled iframe audit at 390px and 320px across all 55 plan files: load each file in an iframe of each width and compare documentElement.scrollWidth against the iframe width. Expected overflow count: 0 at both widths (it was 16 at 390px before the retrofit; 320px grounds WCAG 1.4.10 reflow).
  3. Visually open add-multi-host-deploy-targets.html (worst offender, previously +294px) and persist-checkbox-state-in-html-attributes-review.html (table-heavy review file) at phone width — no horizontal scrollbar, long paths wrap, tables fit.
  4. Run the injector a second time — it reports all files skipped and git diff gains nothing.
  5. Confirm .claude-plugin/marketplace.json shows plan-agent 2.4.1 with a matching CHANGELOG.md entry, and that the JSON passed the settings-hook validation on save.

Completion Checklist

Required

Completion Report

No items to report — all requirements met.

Next Steps

Patch the review-card templates at their source

Paste this prompt into Claude to execute this follow-up:

In the agentics repo: the *-review.html files in docs/plans/ are generated from review-output templates and currently ship without the responsive block. Locate the HTML templates that produce review documents (search kit/plugins/plan-agent and kit/plugins/product-plans for review skeleton/template HTML), insert the same <style id="plan-responsive-fix" data-version="1"> block used by scripts/retrofit-responsive-plans.mjs immediately before </head> in each template, bump each owning plugin's version (patch) in .claude-plugin/marketplace.json with CHANGELOG entries, and verify a freshly generated review file passes node scripts/retrofit-responsive-plans.mjs --check.
Retrofit the archived plans

Paste this prompt into Claude to execute this follow-up:

In the agentics repo, run node scripts/retrofit-responsive-plans.mjs --include-archive to inject the v1 responsive block into docs/plans/archive/**/*.html as well. Review the diff, run the command a second time to confirm idempotency (no further changes), and commit as a docs-only change.
Audit the remaining docs surfaces at phone width

Paste this prompt into Claude to execute this follow-up:

In the agentics repo, serve docs/ locally (bash scripts/serve-docs.sh) and audit docs/index.html, docs/guides/*.html, and docs/media/social/index.html at 390px and 320px for horizontal overflow using a width-controlled iframe harness: load each page in an iframe of that width and compare documentElement.scrollWidth to the iframe width. Report each offender with the leaf element causing the overflow, and propose minimal CSS fixes per file.
Wish List
CI guard on every plan-touching PR Wish List

Speculative / blue-sky idea — not on the critical path. Paste into Claude when ready to explore:

In the agentics repo, add a GitHub Actions workflow (or extend an existing one) that runs node scripts/retrofit-responsive-plans.mjs --check and bash tests/plugins/test-responsive-retrofit.sh on pull requests touching docs/plans/** or kit/plugins/plan-agent/**, so a plan can never merge without the current responsive block.
Auto-retrofit hook on plan writes Wish List

Speculative / blue-sky idea — not on the critical path. Paste into Claude when ready to explore:

In the agentics repo, add a PostToolUse hook to kit/plugins/plan-agent/hooks/ (mirroring the existing plans-index rebuild hook) that runs node scripts/retrofit-responsive-plans.mjs --dir docs/plans whenever a Write or Edit touches an .html file under docs/plans/, so manually added plans receive the responsive block immediately. Bump plan-agent (minor — new hook) and document it in the CHANGELOG.