diff --git a/.claude/skills/execplans/SKILL.md b/.claude/skills/execplans/SKILL.md index 53c2e44..dc5025d 100644 --- a/.claude/skills/execplans/SKILL.md +++ b/.claude/skills/execplans/SKILL.md @@ -1,6 +1,6 @@ --- name: execplans -description: Write and maintain an ExecPlan — a self-contained, committed Markdown design document in docs/execplans/ — for work that spans more than one session or touches several files or subsystems. Use for a refactor crossing the container/view split, a tooling or dependency migration, adding CI or test coverage, #409's chart redraw path, or any change whose decisions and progress must survive a context reset. Also use when resuming or revising a plan already in docs/execplans/. Invoke with /execplans. +description: Write and maintain an ExecPlan — a self-contained, committed Markdown design document in docs/execplans/ — for work that spans more than one session or touches several files or subsystems. Use for a refactor crossing the container/view split, a tooling or dependency migration, adding CI or test coverage, or any change whose decisions and progress must survive a context reset. Also use when resuming or revising a plan already in docs/execplans/. Invoke with /execplans. --- # ExecPlans @@ -9,8 +9,7 @@ An ExecPlan is a Markdown design document that lives on disk and carries a piece design through implementation. It is the durable record: someone holding only the working tree and the plan file can pick the work up and finish it. Write one instead of relying on the built-in plan/todo tools whenever the work meets the threshold below — those tools don't survive a context -reset, and this repo's near-term work (adding CI, broadening test coverage, fixing #409's chart -redraw path) is exactly the kind that needs to. +reset. Plans in this repository go in `docs/execplans/` and are committed on the working branch. **Read `references/PLANS.md` in full before writing or revising one** — it is the specification, and its diff --git a/.claude/skills/execplans/references/PLANS.md b/.claude/skills/execplans/references/PLANS.md index 4ab1c19..14bc85e 100644 --- a/.claude/skills/execplans/references/PLANS.md +++ b/.claude/skills/execplans/references/PLANS.md @@ -39,7 +39,7 @@ Write in plain prose. Prefer sentences over lists. Avoid checklists, tables, and ## Guidelines -Self-containment and plain language are paramount. Assume the reader knows web development generally — npm scripts, React, CI, git — and define what is particular to this repository ("the container/view split", "react-refetch's `connect()`", "`flagForUpdate`", "the gitignored compiled CSS") the first time it appears, naming the files or commands where it shows up. Define each term once, in `Context and Orientation`, then use it. Do not say "according to the architecture doc"; put the needed explanation in the plan. +Self-containment and plain language are paramount. Assume the reader knows web development generally — npm scripts, React, CI, git — and define what is particular to this repository ("the container/view split", "`withFetch`'s `dataFetch` prop", "`flagForUpdate`", "the gitignored compiled CSS") the first time it appears, naming the files or commands where it shows up. Define each term once, in `Context and Orientation`, then use it. Do not say "according to the architecture doc"; put the needed explanation in the plan. Avoid common failure modes. Do not rely on undefined jargon. Do not describe "the letter of a feature" so narrowly that the resulting code compiles but does nothing meaningful. Do not outsource key decisions to the reader. When ambiguity exists, resolve it in the plan itself and explain why you chose that path. Err on the side of over-explaining user-visible effects and under-specifying incidental implementation details. @@ -171,7 +171,7 @@ branch. There is nowhere else for them here, and a plan that is not in the pull reviewable. `` is the kebab-case summary from the branch name, so the two line up: branch -`409--hardware-resize-performance`, plan `docs/execplans/2026-09-20-hardware-resize-performance.md`. +`409--hardware-resize-performance`, plan `docs/execplans/2026-10-28-hardware-resize-performance.md`. The date is the day the plan was started and does not change when the plan is revised. This specification is checked in at `.claude/skills/execplans/references/PLANS.md`. Cite that path @@ -200,17 +200,19 @@ today. These do: npm run build:css # required before Vitest on a fresh clone; the compiled CSS is gitignored npm run lint # ESLint flat config (.js and .jsx) plus stylint npm run test:jest # Vitest - npm run test:playwright # Playwright, against the Vite dev server and live data.firefox.com - npm run build # Vite production build plus version.json + npm run test:playwright # every Playwright project, against the Vite dev server + npm run test:playwright:recorded # the specs on the recorded fixtures (CI's required check) + npm run test:playwright:live # the same specs on live data.firefox.com + npm run build # Stylus compile, Vite production build, then version.json npm test # lint, then Vitest, then Playwright -The Playwright dashboard specs assert against live production data, so a failure there can come -from upstream data changing rather than from the commit. Re-run once before investigating, and say -which it was. +The recorded Playwright run is deterministic. Only `test:playwright:live` depends on upstream data, +so a failure there can come from the data changing rather than from the commit; it usually means the +recording is stale (see `AGENTS.md`). Say which it was. Run each command and report the actual result; never claim a pass you -did not observe. State the expected result, not just the command: "`npm run test:jest` — 2 files, 4 -tests passing" beats "run the tests". +did not observe. State the expected result, not just the command: "`npm run test:jest` — every file +passes, including the new `Foo.test.jsx`" beats "run the tests". ## Record what matters, not what happened @@ -264,7 +266,7 @@ neither self-contained nor, probably, correct. ## The plan and the pull request The plan file is part of the diff, so name it in the PR's **Significant changes and points to -review** as one plain bullet — "Adds `docs/execplans/2026-09-14-eslint-unification.md`" — and stop +review** as one plain bullet — "Adds `docs/execplans/2026-10-28-hardware-resize-performance.md`" — and stop there. The description keeps its four headings (One-line summary; Significant changes and points to review; Issue / Bugzilla link; Testing) and does not become a second copy of the plan. `AGENTS.md`'s ban on describing your own process still applies: the plan records how the work went, the diff --git a/.claude/skills/update-deps/SKILL.md b/.claude/skills/update-deps/SKILL.md index a0d7a4c..531ce50 100644 --- a/.claude/skills/update-deps/SKILL.md +++ b/.claude/skills/update-deps/SKILL.md @@ -2,14 +2,14 @@ name: update-deps description: Audit and update npm dependencies disable-model-invocation: true -argument-hint: "[all|sync-check|]" +argument-hint: "[all|]" allowed-tools: - Bash(npm outdated *) - Bash(npm update *) - Bash(npm install *) - Bash(npm run lint*) - Bash(npm test*) - - Bash(npm run test-build*) + - Bash(npm run build*) - Bash(npm view *) - Bash(npm audit --json*) - Bash(npm explain *) @@ -54,7 +54,7 @@ Indirect dependencies are packages in `package-lock.json` that aren't listed in - **No fix available**: report it and move on. 4. Ask the user once whether to also refresh indirect dependencies that have no security fix pending (everything `npm update` would change within the existing ranges). Default to no; this can touch hundreds of packages. -Never run `npm audit fix --force`. Its "fixes" with `isSemVerMajor: true` can be downgrades to old major versions of the parent (e.g. it suggests `@frctl/fractal@1.3.0`). +Never run `npm audit fix --force`. Its "fixes" with `isSemVerMajor: true` can be downgrades to old major versions of the parent. ### Open Dependabot PRs @@ -163,7 +163,7 @@ Record the following in `.claude/skills/update-deps/denied.md`: ## Phase 6: Execute Approved Updates -Run `npm install @` for pinned packages, or `npm update ` for range-pinned packages. Verify `package-lock.json` updated correctly. +Every direct dependency is exact-pinned, so run `npm install @`. Verify `package-lock.json` updated correctly. For indirect dependencies: @@ -180,8 +180,8 @@ After each step, check `git diff package-lock.json` for packages that changed an After executing updates, offer to run verification commands: - `npm run lint` — run JS/CSS linting -- `npm run test-build` — confirm the webpack build still succeeds -- `npm test` — run the Jasmine browser test suite +- `npm run build` — confirm the Stylus and Vite build still succeeds +- `npm test` — run lint, the Vitest unit tests, and the Playwright suite Ask the user which (if any) they want to run. Run selected checks and report results. diff --git a/AGENTS.md b/AGENTS.md index 09c2ca5..758ea97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,9 +21,9 @@ | `containers/MetricOverviewContainer.jsx` | `${dashboardSource}/${activeRegion}/${slug}/index.json` | title, description, `type: 'line'|'table'`, axes, columns, data, annotations | | `containers/SummaryMetricContainer.jsx` | same per-metric endpoint | buckets populations under 5% into "Other" | -`src/lib/withFetch.jsx` **is the entire data layer** and is used in exactly those three files: `withFetch(props => url)(Container)` passes a `dataFetch` prop (`pending`, `rejected`, `fulfilled`, `value`, `reason`), refetches when the URL changes (back to pending first), rejects on non-2xx, and drops a response that arrives after a newer request started. Each one handles `dataFetch.pending` / `.rejected` / `.fulfilled` itself and renders `views/Spinner.jsx` or `views/Error.jsx`. All reshaping is client-side in each container's `formatData` (`ChartContainer` sorts populations by max y and drops nulls; `DataTableContainer` sorts rows by value descending). +`src/lib/withFetch.jsx` **is the entire data layer** and is used in exactly those three files: `withFetch(props => url)(Container)` passes a `dataFetch` prop (`pending`, `rejected`, `fulfilled`, `value`, `reason`), refetches when the URL changes (back to pending first), rejects on non-2xx, and drops a response that arrives after a newer request started. Each one handles `dataFetch.pending` / `.rejected` / `.fulfilled` itself: `DashboardContainer` renders `views/Spinner.jsx` while pending, the two metric containers render nothing (the chart's own spinner shows instead), and all three render `views/Error.jsx` on rejection. All reshaping is client-side, in the `formatData` of `ChartContainer`, `DataTableContainer`, and `SummaryMetricContainer` (`ChartContainer` sorts populations by max y and drops nulls; `DataTableContainer` sorts rows by value descending). -Metric `description` strings are rendered as inline Markdown through `markdown-it('zero')` with only `link`/`entity`/`emphasis`/`sup` enabled, via `dangerouslySetInnerHTML`. `src/tests/jest/MetricOverview.test.jsx` exists **specifically to guard that sanitization** — never widen the enabled-rules list without extending that test. +Metric `description` strings are rendered as inline Markdown through `markdown-it('zero')` with only `link` and `entity` enabled plus the `markdown-it-sup` plugin, via `dangerouslySetInnerHTML`. The dashboard description in `views/Dashboard.jsx` enables only `emphasis`. `src/tests/jest/MetricOverview.test.jsx` exists **specifically to guard that sanitization** — never widen the enabled-rules list without extending that test. ``` src/ @@ -47,10 +47,10 @@ src/ └── tests/ └── jest/ unit tests, run by Vitest (the directory name predates Vitest) tests/playwright/ -├── specs/ e2e specs (.spec.js, CommonJS), incl. dashboards/ and fixtures/; run on the recording and on live data +├── specs/ e2e specs (.spec.js, CommonJS), incl. dashboards/ and fixtures/; run on the recording, and all but fixtures/ and jsDisabled also on live data ├── fixtures/ recorded transposer JSON, written by `npm run record:fixtures` ├── fixtureData.js `test` that serves fixtures/ (or live data, with liveData) and fails on page/console errors; readFixture -└── utils.js shared helpers: linkWorks, linksWork, flagForUpdate, metricTitleIsCorrect +└── utils.js shared helpers: formatPercent, linkWorks, linksWork, flagForUpdate, metricTitleIsCorrect, changeRegionAndWaitForMetrics ``` There is no `pages/`, `store/`, `hooks/`, `api/`, `locales/`, or `data/`. `index.html` at the repository root is Vite's entry point (with a hardcoded `og:image` and the `