Skip to content

Commit d39b030

Browse files
Document prettier markdown non-idempotency bug found while updating docs
pre-commit is now actually installed on this machine (previously wasn't), so this landmine is live repo-wide for the next session that touches any of the several docs/*.md files with pre-existing prettier drift. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent efed17f commit d39b030

1 file changed

Lines changed: 38 additions & 12 deletions

File tree

docs/lessons.md

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ tasks, not a one-off narrative (those belong in `journal/` or a feature doc
77
under `docs/features/`).
88

99
## Trust CI history, not a matching local venv, once a change touches Django's model-import chain
10+
1011
A local mypy run can pass identically across many sessions and still not be
1112
what CI actually checks, if your venv has dependencies CI's isolated
1213
pre-commit hooks don't (e.g. `mypy_django_plugin` genuinely imports, not just
@@ -20,12 +21,11 @@ not just a local re-run — especially for anything on the models.py import
2021
chain.
2122

2223
## Concurrent worktree dev servers collide on port 3000
23-
Multiple Claude Code sessions/worktrees on this box can and do run `next
24-
dev` at the same time; Playwright's `webServer.reuseExistingServer: true`
24+
25+
Multiple Claude Code sessions/worktrees on this box can and do run `next dev` at the same time; Playwright's `webServer.reuseExistingServer: true`
2526
will happily attach to whatever's already listening on 3000, which may
2627
belong to a different worktree entirely — producing screenshots of stale
27-
code with no error. Before trusting a suspicious screenshot, check `ps aux
28-
| grep "next dev"` for a PID under a different `.claude/worktrees/*` path.
28+
code with no error. Before trusting a suspicious screenshot, check `ps aux | grep "next dev"` for a PID under a different `.claude/worktrees/*` path.
2929
Point your own run at a different port instead of killing another
3030
session's process, unless the user directly confirms it's abandoned — then
3131
verify the PID's cwd via `readlink -f /proc/<pid>/cwd` before killing.
@@ -34,6 +34,7 @@ it's a landmine for the next concurrent session, not something to leave
3434
running because it "seems harmless."
3535

3636
## Swap in a debug color to disambiguate same-colored overlapping elements
37+
3738
A pixel/computed-color check at one sample point can be genuinely ambiguous
3839
when two adjacent elements intentionally share a color (e.g. a themed
3940
overlay bleeding onto a neighboring placeholder using the same palette).
@@ -43,23 +44,25 @@ element to an unmistakable color never used elsewhere on the page (e.g.
4344
the real palette.
4445

4546
## Sample cyclic/periodic animations repeatedly, not once
47+
4648
A single before/after comparison of a value driven by a short repeating
4749
cycle (e.g. a 150ms x 5-frame animation loop) can coincidentally land on
4850
the same frame twice and falsely read as "static." Sample several times
4951
across at least one full cycle and count distinct values instead of
5052
trusting one pair.
5153

5254
## Verify cross-session "investigation reports" against git, don't take them as ground truth
55+
5356
A report relayed from a different Claude Code session (even one working on
5457
a related codebase) is a claim, not a fact — treat it exactly like any
5558
other unverified input. One such report claimed a fix commit was "still
5659
unmerged on a branch" and described unrelated file changes that didn't
57-
exist; `git show <sha> --stat` and `git merge-base --is-ancestor <sha>
58-
origin/master` disproved both claims in under a minute. Always check
60+
exist; `git show <sha> --stat` and `git merge-base --is-ancestor <sha> origin/master` disproved both claims in under a minute. Always check
5961
`git show`/`git merge-base`/`git ls-remote` before acting on a relayed
6062
finding.
6163

6264
## Elasticsearch index mapping can drift from the schema declared in code
65+
6366
`documents.py`'s declared field types (e.g. `KeywordField`) don't
6467
automatically stay in sync with the live index's actual `_mapping` — a
6568
field can silently end up `text`-analyzed instead, breaking exact-match
@@ -70,10 +73,10 @@ bug. Fix: `manage.py search_index --rebuild -f` inside the django
7073
container.
7174

7275
## `factory.Sequence` counters are process-global for the whole test run
76+
7377
Shared factories (`cardpicker/tests/factories.py`) increment a single
7478
sequence counter across every test file in a pytest session, and some
75-
snapshot assertions hardcode exact sequence-derived values (e.g. `"Artist
76-
0"`) that depend on total call count up to that point — so a brand-new,
79+
snapshot assertions hardcode exact sequence-derived values (e.g. `"Artist 0"`) that depend on total call count up to that point — so a brand-new,
7780
otherwise-unrelated test file can silently break unrelated snapshots just
7881
by sorting earlier in collection order and using the same factory. Fix
7982
pattern: an autouse fixture local to the new test file(s) only that
@@ -83,12 +86,13 @@ own increment) and again in teardown, leaving zero net drift. Don't touch
8386
`conftest.py` or existing test files to fix this.
8487

8588
## Use `du -sh path/.[!.]* path/*`, not a bare `path/*` glob, when sizing what's actually large
89+
8690
A plain shell glob silently skips dotfiles/dot-directories, which can dwarf
8791
everything else being measured (a hidden worktrees directory carrying
88-
several full `node_modules` copies was 4.7GB and invisible to a `du -sh
89-
repo/*` sanity check before excluding things from a Docker build context).
92+
several full `node_modules` copies was 4.7GB and invisible to a `du -sh repo/*` sanity check before excluding things from a Docker build context).
9093

9194
## `position: sticky` and `overflow` interact in two non-obvious, easy-to-get-backwards ways
95+
9296
(1) A sticky element always paints in front of ordinary in-flow siblings
9397
regardless of DOM order or a descendant's own z-index — `position: sticky`
9498
unconditionally establishes a stacking context, so anything positioned
@@ -105,6 +109,7 @@ measuring `getBoundingClientRect()` at multiple offsets — a static
105109
screenshot at one scroll position won't reveal a broken sticky context.
106110

107111
## A new wrapper placed around an existing effect can silently fight that effect's own CSS
112+
108113
When component B is later wrapped around component A, check whether B's own
109114
CSS (especially `overflow`) contradicts something A was deliberately built
110115
without. A hover-zoom effect was built with no `overflow: hidden` on its own
@@ -114,9 +119,9 @@ component added two rounds later wrapped around it out of habit with
114119
already contained its image), silently re-clipping the hover-zoom it wrapped.
115120

116121
## Verify a deploy against real evidence before assuming the code is wrong
122+
117123
A user report of "none of these changes seem to have taken effect" should
118-
first be checked against the deploy itself — `gh run list`/`gh run view
119-
--log` for the right commit SHA, the live bundle content via `curl`, and
124+
first be checked against the deploy itself — `gh run list`/`gh run view --log` for the right commit SHA, the live bundle content via `curl`, and
120125
response headers (`Last-Modified` matching the deploy timestamp,
121126
`cf-cache-status` not edge-cached) — before assuming the code is broken.
122127
One such report turned out to be a real deploy that genuinely shipped the
@@ -125,8 +130,29 @@ visible once a live Playwright pass (not just curl) was used to drive the
125130
page.
126131

127132
## Check for existing `data-testid` collisions before reusing a naming convention
133+
128134
Before giving a new component a testid that follows an existing naming
129135
pattern (e.g. `<feature>-queue`), grep for whether a sibling component
130136
already uses that exact string — especially one that stays mounted (hidden)
131137
after its tab loses focus, which can produce two simultaneously-mounted
132138
elements sharing one testid the instant a user switches tabs.
139+
140+
## prettier@2.7.1's markdown formatter can silently corrupt text on a second pass
141+
142+
Running prettier on an already-prettier-formatted `.md` file is not
143+
guaranteed to be a no-op: a real non-idempotency bug turns bare
144+
`node_modules`-style intraword-underscore text into `node*modules`, and
145+
`_italic_` emphasis into a broken `\_italic*`, with no error — it just
146+
writes wrong content. Reproduced deterministically (not flaky) by adding
147+
new prose to `docs/infrastructure.md` and running `pre-commit run prettier`/`npx prettier --write` twice in a row. Fix was to reword the two
148+
trip points (wrap the bare `node_modules` mention in backticks, swap
149+
`_hidden_` for `**hidden**`) rather than fight the formatter, then verify
150+
by running the hook an extra time and confirming zero further diff before
151+
trusting it as a stable fixed point. Most `docs/*.md` files in this repo
152+
still have pre-existing prettier drift (predates this bug, out of scope to
153+
mass-fix) — the pre-commit hook is now actually installed on this machine
154+
(`pip install --user pre-commit && pre-commit install`, written into the
155+
shared `.git/hooks/pre-commit` so it applies across every worktree of this
156+
repo), so the next session that touches one of those drifted files should
157+
diff prettier's output for corruption like this rather than committing it
158+
blindly.

0 commit comments

Comments
 (0)