Conversation
Runs prettier over staged files and, when a manifest moves, the two repo-wide checks CI already runs (pmndrs.json metadata + syncpack). examples/* are left alone: each is a vendored sandbox with its own prettier config that it isn't formatted to, so reformatting one on commit would bury the change under a whole-file diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
Rather than calling eslint directly: which workspaces have a linter stays a property of the task graph, and turbo's cache makes the check ~0.4s when nothing lint-relevant moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
It came from the CodeSandbox template and nothing ran it (the example has no hook of its own). With a real lint-staged at the root it stopped being inert: a `lint-staged` key makes that directory its own config root, so staged files under it would be formatted by the example's sandbox prettier settings instead of being left alone like every other example. Removing the devDependency also undoes the version bump it forced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
They are root scripts, so `//#lint:metadata` and `//#lint:versions` with explicit inputs -- turbo then knows a docs-only commit needs neither, and the pre-commit hook drops the manifest glob it was using as a poor man's cache. `pnpm lint` becomes the one entry point for every linter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
The file is schema'd as plain JSON; the rationale for the `//#` tasks and their explicit inputs lives in the previous commit message instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
pnpm 11 keeps `overrides` there (this repo pins postprocessing through it) and syncpack reads them, so a change to that file has to invalidate the check -- without it turbo replayed a cache hit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
The hook was skipping all of `examples/`, including the 544 .ts/.tsx/.md that `pnpm format` does maintain there (3 of them non-conformant, so they really are kept in shape). Only the vendored .js/.jsx/.css need the exemption. `.prettierignore` is the wrong lever for this: it would take those 544 away from `pnpm format` too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
Same behaviour, named after what it actually is: the format script's glob, as a predicate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
The narrower rule restated the format script's glob in a second place with nothing keeping the two in sync -- widen `format` and the hook silently stays behind. What it bought was small: 3 of the 544 .ts/.tsx/.md under examples/ drift today, and no CI job checks formatting anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
Formatting is the hook's job to guarantee; `pnpm format` was never invoked by anything. The exemption moves from a filter over `examples/` to `.prettierignore`, restricted to what a measurement (max line length) shows to be compiled: minified draco, the vendored realism-effects bundles, and `.gltf` scenes -- prettier parses those as json/babel, so --ignore-unknown does not cover them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
One-off catch-up, so the hook starts from zero drift: from here on it keeps every commit formatted. Each example is formatted to its own `.prettierrc`, so a sandbox stays in the style it was written in. 94% of the added lines are two three.js font atlases stored as a single 800k-character line; the rest is ~350 sources at a handful of lines each. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
The examples carried 160 `.prettierrc` (plus two `prettier` keys in a package.json) for 23 distinct variants of the same intent -- and among them a `printWidth` of 16078 and a `fluid` option prettier has never had. They are replaced by a single `examples/**` override in `prettier.config.mjs`, holding the majority values of that set, so the demos keep the style they were written in and there is one place left to change it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
The override only ever bought a look -- 160 columns, no semicolons, single quotes -- and once every example had been reformatted anyway, byte-identity with the sandboxes they were copied from was gone regardless. One style is one less thing to know. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
The hook formats what it is given, which leaves `git commit -n` and anything that wrote outside a commit. `prettier --check .` covers that, but it reads every file, so any commit would invalidate it -- at ~9s it does not belong in the hook. It becomes `//#format:check`, and CI runs `pnpm check` (the four tasks) instead of the two it ran directly. `$TURBO_DEFAULT$` for the inputs, not `**`: an explicit `**` also hashes what git ignores, so `.turbo/` logs written by the run itself changed the hash every time -- a task that never hit its own cache, and took ~26s hashing node_modules on the way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
A task whose `inputs` are too wide re-runs on every commit; one that is too narrow replays a stale pass over code that did change. Both are silent, and the second is the dangerous one. The suite pins, per task, which file must make it re-run and which must not -- build for one example, eslint, the metadata and syncpack checks, and format:check -- plus the hash-stability case that `inputs: ["**"]` used to break. `turbo --dry` throughout, so asserting on a build costs a hash rather than a vite build: 10 cases in 12s. Sabotaging an inputs list in turbo.json fails exactly the cases that name it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
12s, and turbo cannot cache it -- it asserts on cache behaviour -- so it runs only when `turbo.json`, the suite, or its config is staged, which is the only way its meaning changes. CI keeps running it against everything else. `pnpm test` stays out: 326 playwright tasks that a repo-wide change invalidates wholesale (266s in CI for the reformat commits, against 93s cached), and snapshots that are not time-deterministic would block commits at random. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
`pnpm check` reads the whole repo -- `format:check` alone is ~10s -- so it cannot go in the pre-commit hook, where every commit would invalidate it and where lint-staged fixes the staged files rather than refusing them. Once per push is where it costs what it is worth: the same four tasks CI runs, before CI runs them, and FULL TURBO when nothing moved since the last push. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
The comment still gave the reason from before `prettier.config.mjs` became the only config: that each example carries its own `.prettierrc` and so is kept in the style it was written in. It is the one comment a reader consults to learn why `examples/` is not excluded, and it named a rule this branch deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
`out/` and `.turbo/` are in `.gitignore`, and prettier reads that too, so neither entry ever fired -- and no tracked file lives under either. Verified by checking a deliberately unformatted probe in both with the entries removed: still skipped. What is left needs its own reason, so it gets one: `pnpm-lock.yaml` is generated, and `patches/` is there because `format:check` runs without `--ignore-unknown`, where a `.patch` errors rather than being skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
husky+lint-staged, so formatting and linting are guaranteed at commit time rather than left to scripts nobody runs.What runs on commit
prettier --write --ignore-unknownpnpm lintturbo.json,test/**,vitest.config.tspnpm test:turboAnd on push,
pnpm check— the four tasks CI gates on, before CI runs them. It reads the whole repo (format:checkalone is ~10s), so it is worth paying once per push and not once per commit; nothing new since the last push is FULL TURBO (~0.1s).pnpm testdeliberately runs in neither:turbo testis 326 playwright tasks that a repo-wide change invalidates wholesale (266s in CI for the reformat commits, against 93s cached), and the snapshots are not time-deterministic — a hook that runs them refuses commits at random, and the habit it teaches isgit commit -n.pnpm lintis now the one entry point for every linter —turbo lint lint:metadata lint:versions. The two root scripts become//#tasks inturbo.jsonwith explicitinputs, so what needs re-running is a question turbo answers from the task graph rather than one the hook guesses with globs of its own: a commit that moved nothing lint-relevant is ~0.5s (FULL TURBO) instead of ~4.5s, and touching onepmndrs.jsonre-runs the metadata check while syncpack replays from cache.Verified on the cache: a
pmndrs.jsonedit misses metadata and hits syncpack; a.syncpackrc.jsonorpnpm-workspace.yamledit does the opposite; docs-only touches hit everything. Failures are never cached — a brokenpmndrs.jsonre-executes and fails on every run until fixed.--concurrent falsein the hook, plus one ordered task array:pnpm lintreads the files prettier may have just rewritten.What CI gates on
CI ran
lint:metadataandlint:versionsdirectly. It now runspnpm check— the same four tasks — which brings eslint and a repo-wideprettier --checkunder CI for the first time. The hook is the fast path in front of that gate, not the only thing enforcing it. CI also runspnpm test:turbo.format:checkbecomes//#format:check, with$TURBO_DEFAULT$for itsinputsrather than an explicit**:**also hashes what git ignores, so the.turbo/logs written by the run itself changed the hash every time — a task that never hit its own cache, and took ~26s hashingnode_moduleson the way.One config, one style
The examples carried 160
.prettierrc(plus twoprettierkeys in apackage.json) for 23 distinct variants of the same intent — among them aprintWidthof16078and afluidoption prettier has never had. All of them are gone:prettier.config.mjsis now the only prettier config in the repo, and every file in it is formatted the same way.That is 805 files reformatted to the repo style. Keeping the sandbox look (160 cols, no semicolons, single quotes) as an
examples/**override would have cost 428 files instead — but it only ever bought a look, and once every example had been reformatted anyway, byte-identity with the sandboxes they were copied from was gone regardless.One file,
examples/svg-maps-with-html-annotations/src/index.jsx, needs two prettier passes to converge; it is committed converged.What prettier must not touch
.prettierignoreis new. The compiled payloads in it are picked by a measurement — max line length — because prettier claims a parser for all of them and--ignore-unknowntherefore does not help:Three further entries are there for reasons of their own:
.glb,.blob,.svgand.splinecodeneed no entry — prettier has no parser for them. Build outputs and.turbo/need none either: they are gitignored, and prettier reads.gitignoreas well. Font atlases are deliberately not ignored: they are json, and 94% of the catch-up diff is two of them being expanded from a single 800k-character line.A test for the cache
test/turbo-cache.test.ts(vitest, 10 cases, 12s) pins which file makes which task re-run: build for one example, eslint, the metadata and syncpack checks,format:check. It runs turbo with--dry, so asserting on a build costs a hash rather than a vite build. Sabotaging aninputslist inturbo.jsonfails exactly the cases that name it — including the hash-stability case, which is there becauseinputs: ["**"]hashed gitignored files (the run's own.turbo/logs) and the task could never hit its own cache.Catch-up
Three commits format the repo so the hook starts from zero drift: 349 files to each example's own
.prettierrc, then 428 and 805 more as those 160 configs were unified away.prettier --checkover every tracked file now passes.Also here
examples/lulaby-city/package.json: drops a vestigiallint-stagedblock + devDependency inherited from its CodeSandbox template. Nothing ran it, but alint-stagedkey makes that directory its own config root — staged files under it would have escaped the root config entirely.prepare: huskyinstalls the hook onpnpm install. Bypass withgit commit -n.🤖 Generated with Claude Code
https://claude.ai/code/session_01Vtf9zptihDq57ThnNMVfUK