diff --git a/.claude/skills/execplans/SKILL.md b/.claude/skills/execplans/SKILL.md new file mode 100644 index 0000000..1b3dac0 --- /dev/null +++ b/.claude/skills/execplans/SKILL.md @@ -0,0 +1,53 @@ +--- +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, replacing the blocked Nightwatch e2e suite, unpicking the two ESLint configs, restoring CI, 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 + +An ExecPlan is a Markdown design document that lives on disk and carries a piece of work from +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 (restoring CI, unifying the two ESLint configs, replacing the +blocked Nightwatch suite) is exactly the kind that needs to. + +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 +final section, "Ensemble addendum", overrides the upstream text wherever the two disagree. The +upstream text's own opening instruction to keep plans outside the repository is one of the things +the addendum overrides — do not follow it. + +## When to use one + +- The work will not finish in one session. +- It touches several files, or crosses the container/view split, the Stylus pipeline, or the build + config. +- It is a migration or a tooling replacement. +- The approach has real unknowns worth proving with a throwaway milestone first. + +Skip it for a single-file fix, a copy change, or anything a commit title already describes. + +## Working sequence + +1. Confirm the branch. Work happens on `--kebab-case-summary`. Ask for the issue + number if the request doesn't carry one, and read + `https://github.com/mozilla/ensemble/issues/` before drafting — `AGENTS.md` requires both. +2. Read `references/PLANS.md`, then start from its skeleton. +3. Create `docs/execplans/YYYY-MM-DD-.md`, where `` is the branch's kebab summary. Cite + `.claude/skills/execplans/references/PLANS.md` in the plan's header. Write no triple backticks — + see the addendum. +4. Fill in real repository context before writing any code: actual paths, actual commands, and + acceptance a human can observe by running something. +5. Present the milestone breakdown to the user and get explicit confirmation before executing any + of them. This is a one-time gate at the plan/execution boundary, not a per-milestone check-in — + see the addendum's "Committing" section for the exact scope. +6. Commit the plan on its own, before the first implementation commit. Then implement, updating + `Progress`, `Surprises & Discoveries`, and `Decision Log` in the same commits as the work. +7. Write `Outcomes & Retrospective` when the work lands, and commit that too. + +## Resuming + +If `docs/execplans/` already holds a plan for this branch, read it and continue it. Never open a +second plan for the same work. A `Progress` section that disagrees with `git log` is a bug in the +plan — fix that before doing anything else. diff --git a/.claude/skills/execplans/references/PLANS.md b/.claude/skills/execplans/references/PLANS.md new file mode 100644 index 0000000..bfe9fde --- /dev/null +++ b/.claude/skills/execplans/references/PLANS.md @@ -0,0 +1,264 @@ +# Codex Execution Plans (ExecPlans): + +This document describes the requirements for an execution plan ("ExecPlan"), a design document that a coding agent can follow to deliver a working feature or system change. Treat the reader as a complete beginner to this repository: they have only the current working tree and the single ExecPlan file you provide. There is no memory of prior plans and no external context. + +## How to use ExecPlans and PLANS.md + +When authoring an executable specification (ExecPlan), follow PLANS.md _to the letter_. If it is not in your context, refresh your memory by reading the entire PLANS.md file. Be thorough in reading (and re-reading) source material to produce an accurate specification. When creating a spec, start from the skeleton and flesh it out as you do your research. + +After you have created the executable specification (ExecPlan) the first time and have clear milestones, prompt the user to review it. + +When implementing an executable specification (ExecPlan), do not prompt the user for "next steps"; simply proceed to the next milestone. Keep all sections up to date, add or split entries in the list at every stopping point to affirmatively state the progress made and next steps. Resolve ambiguities autonomously, and commit frequently. + +When discussing an executable specification (ExecPlan), record decisions in a log in the spec for posterity; it should be unambiguously clear why any change to the specification was made. ExecPlans are living documents, and it should always be possible to restart from _only_ the ExecPlan and no other work. + +When researching a design with challenging requirements or significant unknowns, use milestones to implement proof of concepts, "toy implementations", etc., that allow validating whether the user's proposal is feasible. Read the source code of libraries by finding or acquiring them, research deeply, and include prototypes to guide a fuller implementation. + +## Requirements + +NON-NEGOTIABLE REQUIREMENTS: + +* Every ExecPlan must be fully self-contained. Self-contained means that in its current form it contains all knowledge and instructions needed for a novice to succeed. +* Every ExecPlan is a living document. Contributors are required to revise it as progress is made, as discoveries occur, and as design decisions are finalized. Each revision must remain fully self-contained. +* Every ExecPlan must enable a complete novice to implement the feature end-to-end without prior knowledge of this repo. +* Every ExecPlan must produce a demonstrably working behavior, not merely code changes to "meet a definition". +* Every ExecPlan must define every term of art in plain language or do not use it. + +Purpose and intent come first. Begin by explaining, in a few sentences, why the work matters from a user's perspective: what someone can do after this change that they could not do before, and how to see it working. Then guide the reader through the exact steps to achieve that outcome, including what to edit, what to run, and what they should observe. + +The agent executing your plan can list files, read files, search, run the project, and run tests. It does not know any prior context and cannot infer what you meant from earlier milestones. Repeat any assumption you rely on. Do not point to external blogs or docs; if knowledge is required, embed it in the plan itself in your own words. If an ExecPlan builds upon a prior ExecPlan and that file is checked in, incorporate it by reference. If it is not, you must include all relevant context from that plan. + +## Formatting + +Format and envelope are simple and strict. Each ExecPlan must be one single fenced code block labeled as `md` that begins and ends with triple backticks. Do not nest additional triple-backtick code fences inside; when you need to show commands, transcripts, diffs, or code, present them as indented blocks within that single fence. Use indentation for clarity rather than code fences inside an ExecPlan to avoid prematurely closing the ExecPlan's code fence. Use two newlines after every heading, use # and ## and so on, and correct syntax for ordered and unordered lists. + +When writing an ExecPlan to a Markdown (.md) file where the content of the file *is only* the single ExecPlan, you should omit the triple backticks. + +Write in plain prose. Prefer sentences over lists. Avoid checklists, tables, and long enumerations unless brevity would obscure meaning. Checklists are permitted only in the `Progress` section, where they are mandatory. Narrative sections must remain prose-first. + +## Guidelines + +Self-containment and plain language are paramount. If you introduce a phrase that is not ordinary English ("daemon", "middleware", "RPC gateway", "filter graph"), define it immediately and remind the reader how it manifests in this repository (for example, by naming the files or commands where it appears). Do not say "as defined previously" or "according to the architecture doc." Include the needed explanation here, even if you repeat yourself. + +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. + +Anchor the plan with observable outcomes. State what the user can do after implementation, the commands to run, and the outputs they should see. Acceptance should be phrased as behavior a human can verify ("after starting the server, navigating to [http://localhost:8080/health](http://localhost:8080/health) returns HTTP 200 with body OK") rather than internal attributes ("added a HealthCheck struct"). If a change is internal, explain how its impact can still be demonstrated (for example, by running tests that fail before and pass after, and by showing a scenario that uses the new behavior). + +Specify repository context explicitly. Name files with full repository-relative paths, name functions and modules precisely, and describe where new files should be created. If touching multiple areas, include a short orientation paragraph that explains how those parts fit together so a novice can navigate confidently. When running commands, show the working directory and exact command line. When outcomes depend on environment, state the assumptions and provide alternatives when reasonable. + +Be idempotent and safe. Write the steps so they can be run multiple times without causing damage or drift. If a step can fail halfway, include how to retry or adapt. If a migration or destructive operation is necessary, spell out backups or safe fallbacks. Prefer additive, testable changes that can be validated as you go. + +Validation is not optional. Include instructions to run tests, to start the system if applicable, and to observe it doing something useful. Describe comprehensive testing for any new features or capabilities. Include expected outputs and error messages so a novice can tell success from failure. Where possible, show how to prove that the change is effective beyond compilation (for example, through a small end-to-end scenario, a CLI invocation, or an HTTP request/response transcript). State the exact test commands appropriate to the project’s toolchain and how to interpret their results. + +Capture evidence. When your steps produce terminal output, short diffs, or logs, include them inside the single fenced block as indented examples. Keep them concise and focused on what proves success. If you need to include a patch, prefer file-scoped diffs or small excerpts that a reader can recreate by following your instructions rather than pasting large blobs. + +## Milestones + +Milestones are narrative, not bureaucracy. If you break the work into milestones, introduce each with a brief paragraph that describes the scope, what will exist at the end of the milestone that did not exist before, the commands to run, and the acceptance you expect to observe. Keep it readable as a story: goal, work, result, proof. Progress and milestones are distinct: milestones tell the story, progress tracks granular work. Both must exist. Never abbreviate a milestone merely for the sake of brevity, do not leave out details that could be crucial to a future implementation. + +Each milestone must be independently verifiable and incrementally implement the overall goal of the execution plan. + +## Living plans and design decisions + +* ExecPlans are living documents. As you make key design decisions, update the plan to record both the decision and the thinking behind it. Record all decisions in the `Decision Log` section. +* ExecPlans must contain and maintain a `Progress` section, a `Surprises & Discoveries` section, a `Decision Log`, and an `Outcomes & Retrospective` section. These are not optional. +* When you discover optimizer behavior, performance tradeoffs, unexpected bugs, or inverse/unapply semantics that shaped your approach, capture those observations in the `Surprises & Discoveries` section with short evidence snippets (test output is ideal). +* If you change course mid-implementation, document why in the `Decision Log` and reflect the implications in `Progress`. Plans are guides for the next contributor as much as checklists for you. +* At completion of a major task or the full plan, write an `Outcomes & Retrospective` entry summarizing what was achieved, what remains, and lessons learned. + +# Prototyping milestones and parallel implementations + +It is acceptable—-and often encouraged—-to include explicit prototyping milestones when they de-risk a larger change. Examples: adding a low-level operator to a dependency to validate feasibility, or exploring two composition orders while measuring optimizer effects. Keep prototypes additive and testable. Clearly label the scope as “prototyping”; describe how to run and observe results; and state the criteria for promoting or discarding the prototype. + +Prefer additive code changes followed by subtractions that keep tests passing. Parallel implementations (e.g., keeping an adapter alongside an older path during migration) are fine when they reduce risk or enable tests to continue passing during a large migration. Describe how to validate both paths and how to retire one safely with tests. When working with multiple new libraries or feature areas, consider creating spikes that evaluate the feasibility of these features _independently_ of one another, proving that the external library performs as expected and implements the features we need in isolation. + +## Skeleton of a Good ExecPlan + + # + + This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds. + + If PLANS.md file is checked into the repo, reference the path to that file here from the repository root and note that this document must be maintained in accordance with PLANS.md. + + ## Purpose / Big Picture + + Explain in a few sentences what someone gains after this change and how they can see it working. State the user-visible behavior you will enable. + + ## Progress + + Use a list with checkboxes to summarize granular steps. Every stopping point must be documented here, even if it requires splitting a partially completed task into two (“done” vs. “remaining”). This section must always reflect the actual current state of the work. + + - [x] (2025-10-01 13:00Z) Example completed step. + - [ ] Example incomplete step. + - [ ] Example partially completed step (completed: X; remaining: Y). + + Use timestamps to measure rates of progress. + + ## Surprises & Discoveries + + Document unexpected behaviors, bugs, optimizations, or insights discovered during implementation. Provide concise evidence. + + - Observation: … + Evidence: … + + ## Decision Log + + Record every decision made while working on the plan in the format: + + - Decision: … + Rationale: … + Date/Author: … + + ## Outcomes & Retrospective + + Summarize outcomes, gaps, and lessons learned at major milestones or at completion. Compare the result against the original purpose. + + ## Context and Orientation + + Describe the current state relevant to this task as if the reader knows nothing. Name the key files and modules by full path. Define any non-obvious term you will use. Do not refer to prior plans. + + ## Plan of Work + + Describe, in prose, the sequence of edits and additions. For each edit, name the file and location (function, module) and what to insert or change. Keep it concrete and minimal. + + ## Concrete Steps + + State the exact commands to run and where to run them (working directory). When a command generates output, show a short expected transcript so the reader can compare. This section must be updated as work proceeds. + + ## Validation and Acceptance + + Describe how to start or exercise the system and what to observe. Phrase acceptance as behavior, with specific inputs and outputs. If tests are involved, say "run and expect passed; the new test fails before the change and passes after>". + + ## Idempotence and Recovery + + If steps can be repeated safely, say so. If a step is risky, provide a safe retry or rollback path. Keep the environment clean after completion. + + ## Artifacts and Notes + + Include the most important transcripts, diffs, or snippets as indented examples. Keep them concise and focused on what proves success. + + ## Interfaces and Dependencies + + Be prescriptive. Name the libraries, modules, and services to use and why. Specify the types, traits/interfaces, and function signatures that must exist at the end of the milestone. Prefer stable names and paths such as `crate::module::function` or `package.submodule.Interface`. E.g.: + + In crates/foo/planner.rs, define: + + pub trait Planner { + fn plan(&self, observed: &Observed) -> Vec; + } + +If you follow the guidance above, a single, stateless agent -- or a human novice -- can read your ExecPlan from top to bottom and produce a working, observable result. That is the bar: SELF-CONTAINED, SELF-SUFFICIENT, NOVICE-GUIDING, OUTCOME-FOCUSED. + +When you revise a plan, you must ensure your changes are comprehensively reflected across all sections, including the living document sections, and you must write a note at the bottom of the plan describing the change and the reason why. ExecPlans must describe not just the what but the why for almost everything. + +--- + +# Ensemble addendum + +Everything above is the upstream ExecPlan specification, published by OpenAI at +, reproduced verbatim. This section is +local to `mozilla/ensemble` and **overrides the text above wherever the two disagree.** The +repository's own conventions are in `AGENTS.md` at the repository root; read that too. + +## Where plans live + +Plans go in the repository, at `docs/execplans/YYYY-MM-DD-.md`, committed on the working +branch. There is nowhere else for them here, and a plan that is not in the pull request is not +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`. +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 +in the plan's header, as the skeleton above requires. + +## No code fences + +Upstream says an ExecPlan is "one single fenced code block labeled as `md`", then says to omit the +backticks when the file holds only the plan. Plans here always go to a file and never into a chat +message, so the rule is the simple one: **no triple backticks anywhere — not around the document, +not inside it.** Present commands, transcripts, and diffs as four-space indented blocks, the way the +skeleton above does. + +The cost is that GitHub renders an indented block without syntax highlighting. Take it. A rule with +an exception is what made this ambiguous upstream, and a fence-free plan can be dropped into a fence +later without rewriting it. + +Do not paste a plan into a chat message. Summarize it and name the file. + +## Validation commands that actually work + +`Concrete Steps` and `Validation and Acceptance` must name commands that run in this repository +today. These do: + + npm install --ignore-scripts # plain `npm install` fails on Apple Silicon + npm run build:css # required on a fresh clone; the CSS imports are gitignored + npm run lint # currently broken here — see below; still the command to run and report on + npm run test:jest + NODE_OPTIONS=--openssl-legacy-provider npm run build:app # CRA lints .jsx here + +A plan that changes application code validates with **both** `npm run lint` and `build:app` where +lint is runnable — the two linters cover disjoint file sets, so a green run of either proves nothing +about the other. + +**`npm run lint` currently fails outright** in a fresh install: the installed ESLint is v9.39.5, not +the v6 that `AGENTS.md` describes, and `lint:js-extra`'s eslintrc-format config +(`.eslintrc.extra.js`) is not valid under ESLint's flat-config system (`A config object is using the +"env" key, which is not supported in flat config system`). Do not treat `AGENTS.md`'s "confirmed +clean, no flags" as still true. A plan should run the command, report the actual result, and not +claim a pass it did not observe. Fixing this is its own piece of work, not something to fold +silently into an unrelated plan. + +**Never write `npm test`, `test:nightwatch:dev`, or any other Nightwatch command into a plan.** +`npm test` includes the Nightwatch leg, and the e2e suite cannot run at all — `chromedriver@84` does +not install on current hardware. A plan whose acceptance depends on Nightwatch is not executable. If +e2e coverage is the point of the work, say so in `Interfaces and Dependencies` and make unblocking +it an explicit milestone with its own acceptance. + +State the expected result, not just the command: "`npm run test:jest` — 2 suites, both passing" +beats "run the tests". + +## Committing + +Commit frequently, as upstream says, on the branch `AGENTS.md` describes +(`--kebab-case-summary`). Two local rules on top: + +* **Do not list the LLM as a co-author.** No `Co-Authored-By` trailer for Claude, Codex, or any + other model, on plan commits or code commits. See `AGENTS.md`, "LLM assistance". +* Commit the plan before the first implementation commit, and update it in the same commit as the + work it describes. A plan brought up to date in a trailing "update plan" commit has stopped being + a record of how the work went. + +Upstream also says not to prompt the user for next steps. That holds **between** milestones, once +execution is under way — do not stop after each one to ask whether to continue. It does not hold +for the transition from planning to executing: after drafting the plan and identifying its +milestones, present the milestone breakdown to the user and get explicit confirmation before +starting work on the first one. Treat that confirmation as a one-time gate, not something to repeat +before every later milestone. It also does not override `AGENTS.md`: still ask for the issue number +when it is not obvious, and still offer to run the tests when the work looks complete rather than +claiming a green run you could not produce. + +## Never cite the plan from the code + +`AGENTS.md` forbids a code comment that explains itself by pointing at a spec, a plan document, or a +ticket. That applies to ExecPlans with full force: no `// see docs/execplans/…`, no "per the plan", +no milestone numbers in comments. A comment has to make sense to someone holding only that one file. +The plan is where the reasoning lives; the code carries its own explanation or none. + +## Reading the issues + +Upstream says not to point at external documentation, and to embed what the reader needs. Follow +that, with one local exception: link the GitHub issue, because it is the tracker of record — then +restate its content in `Context and Orientation` in your own words. Most of these issues are five or +more years old and describe a codebase that has since moved. A plan that says only "see #409" is +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 +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 +description says what changed. diff --git a/.gitignore b/.gitignore index 7f8d679..014182f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Miscellaneous .DS_Store +.vscode +.claude/* +!.claude/skills/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..47a9f47 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,250 @@ +# Repository Guidelines + +## Project Structure & Module Organization + +`ensemble` (v1.2.1, `private: true`, MPL-2.0, `git@github.com:mozilla/ensemble.git`) is the React app behind — the **Firefox Public Data Report**. It is a **pure client-side create-react-app SPA and it contains no data of its own.** Every metric is fetched at runtime from **[ensemble-transposer](https://github.com/mozilla/ensemble-transposer)** (separate repo), which reformats Mozilla's public telemetry and serves JSON from the same `data.firefox.com` domain. If a number on the site looks wrong, the bug is usually not here. + +**`src/config.json` is the spine.** Two arrays, and almost every structural change starts by editing one of them: + +| Array | Shape | Drives | +|---|---|---| +| `dashboards` | `key`, `menuTitle`, `source`, `supportsRegions` | The route table in `src/components/views/Main.jsx` (`/dashboard/`), the nav in `src/components/views/Header.jsx`, and the dashboard's data `source` | +| `nextButtons` | `from`, `to`, `text` | The "Proceed to …" CTA via `src/components/decorators/withNextButton.jsx` | + +**Adding a dashboard is adding one entry to `dashboards`.** The `source` values are **absolute production URLs baked into the repo** — there is no env var for the data source, so you cannot point the app at a local transposer without editing `config.json`. + +**Data flow** (no state library, no hooks, no Redux — `this.state` in class containers, plus a `sessionStorage` key `preferredRegion` set in `DashboardContainer.jsx`): + +| Container | Fetches | Notes | +|---|---|---| +| `containers/DashboardContainer.jsx` | `${source}/index.json` | title, description, metaDescription, sections, dates, metrics, summaryMetrics, `categories` (= regions), defaultCategory | +| `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" | + +`react-refetch@3.0.1`'s `connect()` HOC **is the entire data layer** and is used in exactly those three files. 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). + +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. + +``` +src/ +├── index.jsx entry: ReactDOM.render + BrowserRouter +├── config.json the route/dashboard table + next-button flow +├── registerServiceWorker.js CRA helper; only unregister() is called (SW deliberately off) +├── setupTests.js enzyme adapter; puts React and shallow on global +├── components/ +│ ├── containers/ 6 stateful/data-fetching .jsx +│ ├── decorators/ withTracker.jsx (react-ga), withNextButton.jsx +│ └── views/ 19 presentational .jsx +│ ├── styl/ Stylus SOURCE (hand-edited); includes/lib.styl +│ ├── css/ GENERATED, GITIGNORED — holds only a README stub +│ ├── fonts/FiraSans/ 8 weights/styles of webfonts +│ └── img/ 3 assets +├── lib/ +│ ├── lazyLoad.jsx react-loadable wrapper (spinner + error fallback) +│ └── utils.js bumpSort, isFloat, prettifyNumber, getPageTitle +└── tests/ + ├── jest/ 2 unit tests + └── nightwatch/ 13 e2e specs (.js, CommonJS), incl. dashboards/ +``` + +There is no `pages/`, `store/`, `hooks/`, `api/`, `locales/`, or `data/`. `public/` is CRA's checked-in static template (`index.html` with `%PUBLIC_URL%` placeholders and a hardcoded `og:image`, `manifest.json`, `contribute.json`, a Search Console verification file, favicons). `build/` is gitignored output. Routes are exactly `/`, `/contact`, `/dashboard/`, and a catch-all `NotFound`. + +## Current state: read this before estimating anything + +MozMEAO is taking this repo over. 810 commits — 515 in 2018, 92 in 2020, **nothing in 2021–2024**, three in 2025. Last commit `f655559`, 2025-05-07. Assume nothing has been exercised recently. + +- **No CI/CD of any kind exists.** No `.github/`, `.circleci/`, `Dockerfile`, `docker-compose`, `Jenkinsfile`. This is not a quirk of your clone — it was all deliberately removed: CircleCI disabled 2018-02-22 (`433a465`), Docker/Dockerflow removed 2018-08-23 (`d3bb561`), `.github/dependabot.yml` deleted 2020-07-29 (`7c7006f`, "Disable non-security updates from Dependabot"). Issue #79 ("enable CI") is still open. +- **The deploy is a static build on Google Cloud Storage** (confirmed by `x-goog-*` headers on the live site). `https://data.firefox.com/version.json` reports `1.2.1` / commit `f655559`, so **the deployed site matches current `main`**. The mechanism that pushes `build/` to GCS is **not in this repo and is currently unknown** — finding and documenting it is takeover work, not something to guess at. +- **`npm install` fails outright on an Apple Silicon (arm64) Mac.** Confirmed: `chromedriver@84.0.1`'s postinstall exits with `Only Mac 64 bits supported` and takes the whole install down with it. Use `npm install --ignore-scripts` to get a working `node_modules` (you lose the chromedriver binary, which you can't use anyway — see the Nightwatch bullet below). This is a harder failure than "chromedriver's download URL changed"; it doesn't even try to download on this architecture. +- **Confirmed on Node v24.19.0:** `npm run build:app` (and therefore `npm run build`) fails with `ERR_OSSL_EVP_UNSUPPORTED` — webpack 4's chunk hashing uses MD4, which OpenSSL 3 (Node ≥17) rejects — and succeeds once prefixed with `NODE_OPTIONS=--openssl-legacy-provider`. `npm start` compiles through the same webpack pipeline, so expect to need the same prefix. **`npm run lint` and `npm run test:jest` both pass clean with no flag at all** — Jest never invokes webpack, so it never hits the MD4 path. The repo's only Node signal is `engines: node >=8`; there is **no `.nvmrc`**, and historical CI pinned Node 8. +- **`npm install` on npm ≥7 rewrites `package-lock.json` from lockfileVersion 1 to 3.** Confirmed: a ~22,000-line diff. Revert it after installing; don't let it ride along in an unrelated commit. +- **Nightwatch cannot run at all right now.** Its `chromedriver@84.0.1` dependency doesn't install (see above), so there is no chromedriver binary in `node_modules/.bin` to point Nightwatch at. Even setting that aside, Chrome 84 is mid-2020 and chromedriver must major-version-match the installed Chrome. Treat the whole e2e suite as blocked until this is deliberately fixed. +- **A fresh clone will not render styles until you compile Stylus.** `src/components/views/css/*` is gitignored and every component does a side-effect `import './css/Foo.css';` — run `npm run build:css` (confirmed working, no flags needed) or the app fails to resolve those imports. +- **Headline known bug: issue #409** — the hardware dashboard takes >10 s to re-render on resize/zoom and can trigger Firefox's slow-script dialog. Most user-visible defect; lives in the d3/metrics-graphics redraw path (`views/SummaryMetric.jsx`, `containers/ChartContainer.jsx`, `views/Chart.jsx`). +- ~40 open issues, oldest from 2018. Other notables: #313 (`MetricOverviewContainer` does not reject when metric data 404s — cited in an inline comment there), #395 (replace `request`), #333 (replace `@babel/polyfill`), #245 (React code-splitting), #46 (localize content). The remote also carries ~35 stale `dependabot/npm_and_yarn/*` branches and three `%archive-*` branches. +- Upstream data is **live and current** — transposer `dates` arrays are sorted newest-first and currently run to 2026-08-31. The app is unmaintained; the data is not. + +## Build, Test, and Development Commands + +```bash +npm install --ignore-scripts # plain `npm install` fails on Apple Silicon; see Current State + # expect package-lock.json to be rewritten v1 -> v3; revert it after + +NODE_OPTIONS=--openssl-legacy-provider npm start + # npm-run-all --parallel watch:css watch:app -> CRA dev server :3000 + # .env sets BROWSER=firefox, so this opens Firefox +npm run build:css # one-shot Stylus compile; confirmed working, no flags needed + +npm run lint # lint:js-extra (standalone ESLint) + lint:styl (stylint); confirmed clean, no flags +npm run test:jest # CI=true react-scripts test (Jest + enzyme, 2 files); confirmed passing, no flags +npm test # lint -> test:jest -> test:nightwatch:dev; the Nightwatch leg cannot run today + +NODE_OPTIONS=--openssl-legacy-provider npm run build:app # confirmed required on Node >=17; fails with ERR_OSSL_EVP_UNSUPPORTED otherwise +npm run build:version.json # writes build/version.json; must run after build:app, needs git on PATH +npm run size # source-map-explorer on build/static/js/main.* (needs a prior build) +``` + +Run one Jest file by passing a path through: `CI=true npx react-scripts test src/tests/jest/Dashboard.test.jsx` (confirmed working). + +**Environment:** `.env` **is checked into git** and holds only public build-time config — `BROWSER=firefox`, `NODE_ENV=development`, `REACT_APP_GA_TRACKING_ID='UA-00000000-0'` (placeholder), `REACT_APP_SITE_TITLE='Firefox Public Data Report'`, `REACT_APP_VALUE_DECIMAL_PLACES=3`. Consumed by `decorators/withTracker.jsx` and `lib/utils.js`. Override inline: `REACT_APP_SITE_TITLE='…' npm start`. + +## Coding Style & Naming Conventions + +- **`.jsx` for any file containing JSX** — including `src/index.jsx` and `*.test.jsx`. Plain `.js` only for non-JSX modules (`lib/utils.js`, `setupTests.js`, `registerServiceWorker.js`, all Nightwatch specs). +- **PascalCase filenames matching the default-exported component.** Decorators are camelCase with a `with` prefix; `lib/lazyLoad.jsx` is camelCase. +- **One component per file, always `export default`.** There are no named component exports anywhere in the repo; the only named exports at all are the four helpers in `lib/utils.js`. +- **The container/view split is the organizing principle.** `containers/X.jsx` holds state and fetching and renders `views/X.jsx` with formatted props (`ChartContainer`→`Chart`, `DataTableContainer`→`DataTable`). Keep new state out of `views/`. +- Function components are the default (17 of 19 views), written as anonymous default-exported arrows — `export default props => (...)` — with no `displayName` (hence `react/display-name: off`). Use a class only where state, refs, or lifecycle are genuinely needed: all 6 containers plus `SummaryMetric.jsx` (d3 + `React.createRef`), `Spinner.jsx`, `MetricOverview.jsx`, `Footer.jsx`. +- Leading underscore for private class-property handlers (`_onRegionChange`, `_drawChart`, `_setChartWidth`) — applied inconsistently (`setChartSize` in `ChartContainer.jsx`); prefer the underscore in new code. +- Locals named `maybeX` hold a JSX fragment **or `null`** (`maybeDescription`, `maybeSummaryMetrics`, `maybeRegion`, `maybeGraphURL`). Keep the idiom. +- Styles attach via a global CSS side-effect import of the **generated** file at the top of the component: `import './css/Dashboard.css';`. Some components import several (`Chart.jsx` pulls `metrics-graphics/dist/metricsgraphics.css`, `./css/Chart.css`, `./css/Metric.css`). `Metric.css` and `LabelledSelector.css` are shared partials with no component of their own. +- No TypeScript, no PropTypes, no CSS-in-JS or CSS Modules, no Prettier, no i18n. **No MPL license headers on source files** (unlike springfield/bedrock) — do not add them. +- `.editorconfig`: LF, final newline, trim trailing whitespace, 4-space indent for `py,yml,html,css,styl,js,json,md`. `.jsx` is missing from that list but 4-space is the de facto convention. No max line length is configured anywhere. +- Every dependency is **exact-pinned** — no `^` or `~` in `package.json`. Keep it that way. + +**ESLint — there are two of them, and neither sees everything.** `.eslintrc.extra.js` is a standalone config run only by `npm run lint:js-extra`, and that script is `npx eslint --config .eslintrc.extra.js --ext .js --ext .json .` — **`.jsx` is not in the `--ext` list, so the bulk of the application code is never linted by it.** Separately, `react-scripts` 3.4.1 lints during `start`/`build` using its own bundled `eslint-config-react-app`, which appears nowhere in this repo and has no `eslintConfig` key in `package.json`. **Consequence: a change can pass `npm run lint` and still fail `npm run build`, and vice versa.** Run both. + +`.eslintrc.extra.js` errors: `eqeqeq`, `no-var`, `prefer-const`, `no-console`, `no-global-assign`, `no-redeclare` and `no-shadow` — the latter two with `builtinGlobals: true`, so **do not name a variable `name`, `status`, `history`, `event`, etc.** Warnings: `semi: always`, `prefer-arrow-callback`, `comma-dangle` (trailing commas **required** for multiline arrays/objects/imports/exports, **never** for function args/params). Off: `react/prop-types`, `react/display-name`, `jsx-a11y/no-onchange`. `react/no-unescaped-entities` forbids only `>` and `}`. `jsx-a11y/recommended` is on, so a11y violations are errors. **Quote style is not enforced** (there is no `quotes` rule) — single quotes by convention. `.eslintignore` covers `build` and `package-lock.json`. + +## Styling: Stylus, outside webpack + +`src/components/views/styl/*.styl` → `src/components/views/css/*.css` via the `stylus` CLI (`build:css` / `watch:css`). Webpack never sees the Stylus. One `.styl` per component (PascalCase, mirroring the component), plus `Application.styl` (global reset, Fira Sans `@font-face`, body type, ~217 lines), `BrowserHacks.styl`, and shared partials `Metric.styl` and `LabelledSelector.styl`. + +Shared variables live in `src/components/views/styl/includes/lib.styl` and it is nearly empty — `$base-tablet`, `$base-desktop`, `$link-color-normal = #0070ff`, and a `// TODO: padding/margin spacing.`. Import it per-file with `@import 'includes/lib'`; it is not globally injected. **No mixins file; most colors are hardcoded hex inline** (stylint's `colors` check is off). + +**Not BEM.** ID selectors carry the page landmarks — `#application`, `#main-header`, `#main-navigation`, `#dashboard`, `#dashboard-sections`, `#summary-metrics`, `#region`, `#introduction` — with flat lowercase-dash classes for repeated pieces (`.metric-overview`, `.dashboard-section`, `.data-table-wrapper`, `.labelled-selector`, `.next-button`, `.striped`, `.highlighted`, `.bar-label`). Descendant nesting with `&` for states; responsive via `@media $base-tablet` / `@media $base-desktop` blocks at the bottom of each file. **Those element IDs are also the Nightwatch selectors — renaming one breaks the e2e tests.** + +stylint (`.stylintrc`) runs with `maxErrors: 0` and `maxWarnings: 0`, so any finding fails the lint. It enforces the **CSS-like** Stylus dialect, not the terse indented syntax: 4-space indent, single quotes, `brackets: always`, `colons: always`, `semicolons: always`. Also `noImportant: true`, `leadingZero: false` (`.5`, not `0.5`), `zeroUnits: never` (`0`, not `0px`), `universal: never` (no `*`), `prefixVarsWithDollar: always`, `namingConvention: lowercase-dash` with `namingConventionStrict: true`, `zIndexNormalize: 10`. Escape hatches are `// @stylint off` / `on` / `ignore` comments — see the `@font-face` block in `Application.styl`. + +## Writing code for the next reader + +Code is read far more often than it is written, and in this repo the next reader is arriving after a five-year gap. Optimise for them. + +- Use full, descriptive names. No single letters, no invented abbreviations. `activeRegion`, not `r`. +- Comments explain **what and why**, briefly, and only where the logic is non-obvious. Well-named code needs none. Delete comments that restate the code. +- **Comments must stand on their own.** Never reference a spec, a plan document, a requirement label, or a ticket ID *as* the explanation — the comment must make sense to someone who has only the file in front of them. A cross-reference alongside a self-contained explanation is fine; that is how the `metrics-graphics` workaround notes in `views/Chart.jsx` read. +- Never describe code that no longer exists. After a refactor, delete the comments it falsified — this repo already has several. Prefer deleting an obsolete comment, branch, or test over leaving it beside its replacement. + +## Testing Guidelines + +**Jest + enzyme**, through CRA's built-in Jest, with no `jest` config block. Only two files, both in `src/tests/jest/`, named `PascalCase.test.jsx` — **not colocated, no `__tests__` directories.** `src/setupTests.js` puts `React` and `shallow` on `global`, so **test files import neither**: they `import Dashboard from '../../components/views/Dashboard';` and call bare `shallow()`. Existing style is top-level `it(...)` with long descriptive sentences, a `beforeAll` that builds a `requiredProps` object, and assertions via `.find(selector).exists()` and `.html()).toContain(...)`. Shallow rendering only; no snapshots; `react-refetch` is never mocked; there are no container tests. + +**Nightwatch** specs in `src/tests/nightwatch/` are **lowercase `.js`, CommonJS**: `module.exports = { before: browser => …, 'Test name as a sentence': browser => … }`. `nightwatch.conf.js` reads `NIGHTWATCH_TARGET` (`dev` = localhost:3000, `stage` = data-ensemble.stage.mozaws.net, `prod` = data.firefox.com) and **throws if it is unset** — it destructures `undefined`. The `default` env is headless Chrome and excludes `utils.js` and `jsDisabled.js`; the `jsDisabled` env runs only `jsDisabled.js` and must be **non-headless** (JS cannot be disabled in headless Chrome), asserting on the `id="enable-javascript"` `