From 22fdf06096b2b17d5fdce64473c165bb23548e91 Mon Sep 17 00:00:00 2001 From: Fabian Gander Date: Wed, 5 Aug 2026 16:47:47 +0200 Subject: [PATCH 1/2] docs: design for the repo-wide prettier format sweep --- ...2026-08-05-prettier-format-sweep-design.md | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-05-prettier-format-sweep-design.md diff --git a/docs/superpowers/specs/2026-08-05-prettier-format-sweep-design.md b/docs/superpowers/specs/2026-08-05-prettier-format-sweep-design.md new file mode 100644 index 0000000..41cf95f --- /dev/null +++ b/docs/superpowers/specs/2026-08-05-prettier-format-sweep-design.md @@ -0,0 +1,163 @@ +# Design — repo-wide Prettier format sweep + +**Date:** 2026-08-05 +**Status:** approved for implementation + +## Problem + +A `.prettierrc` (`tabWidth: 2`, `useTabs: false`, `printWidth: 120`) has been checked in for a +long time, but Prettier itself was never installed — not at the repo root, not in any workspace. +The config was therefore only ever honoured by whatever editor happened to have the Prettier +extension enabled, which is why formatting drifted file by file and why `.vscode/settings.json` +(4-space) and `.prettierrc` (2-space) disagree with each other. + +The goal is to make the checked-in config real: pin Prettier, give it an ignore list, make line +endings deterministic, format the repo once, and then keep it formatted in CI. + +## Scope + +Measured on `origin/main` at `eebd7db` with the `.prettierignore` below in place and an LF working +tree, `npm run format:check` reports **108 files**: + +| Type | Count | +| --------------------- | ----- | +| `.ts` | 65 | +| `.md` | 17 | +| `.json` | 9 | +| `.tsx` | 7 | +| `.css` | 4 | +| `.js` | 2 | +| `.yml` | 1 | +| `.html` | 1 | +| `.prettierrc` | 1 | +| `.markdownlint.jsonc` | 1 | +| **Total** | 108 | + +The exact number will move as feature work lands; it is a snapshot, not a contract. What matters +is that the set is "every checked-in file Prettier understands, minus the exclusions below". + +### Provenance of the earlier "180 / 173 files" figures + +An earlier pass quoted 180 unformatted files before the ignore list and 173 after it. Both numbers +are reproducible, but not on this branch point: + +- They were measured at commit `e06f6c2` (tip of `claude/prepare-format-120-files-f8497b`), two + commits behind `origin/main`. +- More importantly, they were measured on a **CRLF working tree**. Git for Windows ships + `core.autocrlf=true` in its system config, so a fresh Windows checkout gets CRLF working files + while the index stays LF. Prettier's `endOfLine` default is `"lf"`, so on such a checkout it + flags _every_ file it can parse, whether or not anything else about it is wrong. That is why the + old breakdown listed all 101 `.ts` files of that commit rather than a subset. + +At `e06f6c2` there are exactly 182 files Prettier can match; two of them fail to parse, leaving +180 warnings and exit code 2 — the reported baseline, to the file. The `.gitattributes` added in +Phase A is what removes this whole class of confusion: with `* text=auto eol=lf` a Windows checkout +gets LF working files, and local `format:check` finally agrees with CI. + +### Excluded + +| File | Reason | +| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `package-lock.json` | npm rewrites it on every install with its own formatting, so a formatted version would make the check permanently red | +| `CHANGELOG.md` | regenerated wholesale by git-cliff / release-please; a formatted version does not survive a release and conflicts with the Release PR, which stays open by project rule | +| `packages/chrome-extension/test/fixtures/bexio/*.html` | captured bexio pages; the jsdom and Playwright tests read DOM and text out of them, and `kb_invoice-show.html` + `monitoring-list.html` do not parse as HTML at all | +| `packages/chrome-extension/test/support/__inline__/tiny.html` | hand-written DOM probe for HTML entities, same reasoning as the fixtures | + +The two unparseable fixtures are also what made the un-ignored run exit with code **2** rather than +**1**: Prettier reports a parse error separately from a formatting difference. Ignoring them brings +the exit code back to a plain 1, so a future CI step fails for the one reason it is meant to. + +Build output (`unpacked/`, `dist/`) and test output (`coverage/`, `test-results/`, +`playwright-report/`) are ignored as well. Prettier 3 does **not** read `.gitignore`, so on a +machine that has built the project those directories would otherwise be formatted. + +### Deliberately not excluded + +`packages/chrome-extension/public/manifest.json` and `.release-please-manifest.json` are generated +in part, but `updateManifest.js` edits them (and `package.json`) by regex replacement rather than +by `JSON.parse` → `JSON.stringify`. It therefore preserves surrounding formatting, and a formatted +version of these files survives a release untouched. They stay in the sweep. + +## Tooling (Phase A) + +1. **Pin Prettier.** `npm install -D prettier@3.9.6` at the repo root. `.npmrc` sets + `save-exact=true`, so the entry lands as `"prettier": "3.9.6"` with no caret — matching the + repo's rule that every dependency version is pinned. This is the one intended change to + `package-lock.json`; CLAUDE.md's "use `npm ci`, not `npm i`" rule is about worktrees not + perturbing the lockfile, and here the lockfile change is the point. + +2. **Scripts.** `format` (`prettier --write .`) and `format:check` (`prettier --check .`) in the + root `package.json`, immediately after `typecheck`. + +3. **`.prettierignore`.** The exclusions above plus the build/test output directories. + +4. **`.gitattributes`.** `* text=auto eol=lf`, plus `binary` for the image/font/zip assets. The + index is already 100% LF (`git ls-files --eol`), so adding it produces zero content diff — it + only fixes what a _checkout_ writes to disk. There are no `.bat`/`.cmd`/`.sh` files in the repo, + so nothing needs CRLF; the two PowerShell scripts are fine with LF. + +5. **Editor wiring.** `editor.formatOnSave` plus a per-language `editor.defaultFormatter` of + `esbenp.prettier-vscode` for `typescript`, `typescriptreact`, `javascript`, `json`, `jsonc`, + `markdown`, `yaml`, `html`, `css` and `scss`, and a `.vscode/extensions.json` recommending the + extension. The formatter is set **per language on purpose**: a global + `editor.defaultFormatter` would hijack `.ps1` files, and this repo has `Build.ps1` and + `CreateRelease.ps1`, which Prettier cannot format. + +`eslint-config-prettier` is **not** added. The only ESLint config in the repo +(`packages/sidePanel-import/eslint.config.js`) contains no stylistic rules, so there is nothing for +it to turn off. No pre-commit hook framework is added either — the repo has none today, and CI plus +format-on-save covers the same ground without a new dependency in everyone's commit path. + +## Sequencing + +The work is split into two phases so that the mechanical 100+-file diff never sits in the same +commit as anything reviewable. + +**Phase A — tooling only.** Everything under "Tooling" above, in two commits: + +- `docs: design for the repo-wide prettier format sweep` +- `build: add prettier tooling and formatting config` + +No file is reformatted and no CI step is added, so Phase A has zero conflict risk and can merge at +any time. `npm run format:check` fails by design after Phase A; that is the point of Phase B. + +**Phase B — the sweep, once the tree is quiet.** Two further commits: + +- `style: format repo with prettier` — the output of a single `npm run format`, nothing else. A + formatting-only commit, so it can be reviewed by confirming the diff is whitespace and that the + test suite still passes. +- `ci: enforce prettier formatting` — adds a `npm run format:check` step to + `.github/workflows/node.js.yml` (next to `Typecheck`) and records the sweep's SHA in + `.git-blame-ignore-revs`, so `git blame` skips straight past it. + +`.git-blame-ignore-revs` cannot be written before Phase B, because it needs the sweep commit's own +SHA. + +`style:` and `ci:` are both configured as `hidden` in `release-please-config.json`, so a +100+-file cosmetic commit and the CI step stay out of the generated release notes. + +### Why Phase B waits + +A repo-wide reformat conflicts with every open branch that touches a formatted file. At time of +writing the unmerged work is `claude/bexio-extension-issue-12-4ad9db` — three commits on top of +what already landed via #127, all of them inside +`packages/sidePanel-import/src/components/ImportEntries/`, which is squarely in the sweep's path. + +Phase B is therefore gated on that work landing on `main`. The resolution for any branch that is +still open when the sweep lands is always the same: rebase onto `main` and re-run `npm run format`, +which turns a whitespace conflict back into a mechanical no-op. + +## Verification (Phase B) + +Run in order, on a checkout that has had `.gitattributes` applied: + +1. `npm run format:check` — must exit 0. +2. `npm run typecheck` — must pass. +3. `npm test` — the Vitest suite, including the slow build smoke test that shells out to + `Build.ps1`. +4. `npm run test:e2e` — the Playwright extension smoke + behaviour specs. + +Steps 3 and 4 are the actual evidence that the sweep was semantically neutral. A formatting diff is +easy to eyeball but tedious to prove correct by reading; the DOM-dependent tests and the real +Chromium run are what confirm that nothing in the fragile `trigger*` / selector layer changed +meaning. From 35e89df217a7efd5fc989667e34b0f7e8c14c9e4 Mon Sep 17 00:00:00 2001 From: Fabian Gander Date: Wed, 5 Aug 2026 16:48:40 +0200 Subject: [PATCH 2/2] build: add prettier tooling and formatting config Phase A of the prettier rollout: the tooling only. No file is reformatted and no CI check is added, so this cannot turn CI red and cannot conflict with open branches. - pin prettier 3.9.6 as an exact root devDependency - add `format` / `format:check` scripts - add .prettierignore (build output, package-lock.json, CHANGELOG.md, the captured bexio HTML fixtures) - add .gitattributes so a Windows checkout gets LF working files; without it core.autocrlf=true makes format:check flag every file locally - wire up format-on-save with prettier scoped per language, so the default formatter does not claim the .ps1 build scripts See docs/superpowers/specs/2026-08-05-prettier-format-sweep-design.md. --- .gitattributes | 15 +++++++++++++++ .prettierignore | 19 +++++++++++++++++++ .vscode/extensions.json | 3 +++ .vscode/settings.json | 35 ++++++++++++++++++++++++++++++++++- package-lock.json | 17 +++++++++++++++++ package.json | 5 ++++- 6 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 .gitattributes create mode 100644 .prettierignore create mode 100644 .vscode/extensions.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..96542ae --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Prettier's endOfLine default is "lf" and CI runs on Linux. Git for Windows +# ships core.autocrlf=true in its system config, so without this a fresh Windows +# clone gets CRLF working files and `npm run format:check` flags every file. +* text=auto eol=lf + +# Binary assets: never touch. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.zip binary +*.woff binary +*.woff2 binary +*.ttf binary diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..eb1c798 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,19 @@ +# Prettier does not read .gitignore, so the build output is repeated here. +unpacked/ +dist/ +coverage/ +test-results/ +playwright-report/ + +# npm rewrites this on every install, with its own formatting. +package-lock.json + +# Regenerated wholesale by git-cliff / release-please on every release. +CHANGELOG.md + +# Captured bexio pages: the jsdom and Playwright tests read DOM and text out of +# these, and two of them do not parse as HTML at all. _raw holds unscrubbed +# captures (git-ignored). tiny.html is a hand-written entity/whitespace probe. +packages/chrome-extension/test/fixtures/bexio/*.html +packages/chrome-extension/test/fixtures/bexio/_raw/ +packages/chrome-extension/test/support/__inline__/tiny.html diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c83e263 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["esbenp.prettier-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 3200e2e..4d0acbb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,37 @@ { "editor.acceptSuggestionOnEnter": "on", - "typescript.tsdk": "node_modules\\typescript\\lib" + "typescript.tsdk": "node_modules\\typescript\\lib", + "editor.formatOnSave": true, + // Prettier is scoped per language on purpose. A global "editor.defaultFormatter" + // would also claim Build.ps1 / CreateRelease.ps1, which Prettier cannot format. + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[scss]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7079b06..97bb642 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "fs-extra": "11.4.0", "git-cliff": "2.13.1", "jsdom": "30.0.1", + "prettier": "3.9.6", "vite": "8.2.0", "vitest": "4.1.10" } @@ -5142,6 +5143,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", diff --git a/package.json b/package.json index 280b766..4280c98 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,9 @@ "test:fast": "vitest run --exclude \"**/*.slow.test.ts\"", "test:watch": "vitest", "test:e2e": "playwright test --config e2e/playwright.config.ts", - "typecheck": "npm run typecheck --workspaces --if-present" + "typecheck": "npm run typecheck --workspaces --if-present", + "format": "prettier --write .", + "format:check": "prettier --check ." }, "repository": { "type": "git", @@ -53,6 +55,7 @@ "fs-extra": "11.4.0", "git-cliff": "2.13.1", "jsdom": "30.0.1", + "prettier": "3.9.6", "vite": "8.2.0", "vitest": "4.1.10" },