Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/memory/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- [PR screenshots workflow](pr-screenshots-workflow.md) — headless Firefox + magick/ffmpeg; embed in private-repo PRs via blob/<sha>?raw=true; pause 0.6s after a theme switch; set data-theme for light shots too (headless Firefox prefers dark); 422 states via noValidate + requestSubmit; seed with one runner script in its own call; CSS-fix "before" shots by injecting the old rule as a <style>
- [Background servers via task](background-servers-via-task.md) — run_in_background + TaskStop, not &/kill/pkill; 2-hour task limit, restart before device steps; TaskStop is a crash for running jobs
- [Branch naming convention](branch-naming-convention.md) — NNN-descriptive-slug per docs/git-convention.md; rename the worktree branch at the start, never use the plainprogrammer/ prefix
- [Headless Firefox narrow frame](headless-firefox-narrow-frame.md) — windows can't go below 500px; test/screenshot phone widths in a fixed-width iframe; not from a scanner page (frame-src none); short frame to force scrolling
- [Headless Firefox narrow frame](headless-firefox-narrow-frame.md) — windows can't go below 500px; test/screenshot phone widths in a fixed-width iframe; not from a scanner page (frame-src none); short frame to force scrolling; scrollbars pinned classic (#271)
- [Batch plan tasks per implementer](batch-plan-tasks-per-implementer.md) — in sdd-execute, one implementer takes 2–4 consecutive tasks, each its own TDD commit, reading plan.md by line range; the supervisor checks each batch's diff
- [Pre-commit hook staging](precommit-hook-staging.md) — hook reads the command text for `git add` / `commit -a`; stage separately to be safe
- [Worktree isolation command guard](worktree-isolation-command-guard.md) — isolated agents' Bash calls are refused when a chain mentions git or is too complex; one git op per call, edit with Edit, partial commits via a scratchpad copy
Expand All @@ -39,7 +39,7 @@
- [Local image builds duplicate OCR](local-image-builds-duplicate-ocr.md) — issue #31: set-up checkouts build images ~14 MB larger; measure paths with `du`, not total sizes
- [Subagent commit trailers](subagent-commit-trailers.md) — trailers aren't stable (own reminder, or a mid-run switch to Sonnet); name it in briefs, check the log per unit, never rewrite history; the maintainer keeps mixed trailers
- [SDD execute](sdd-execute-exact-plans.md) — one subagent per phase even for a small plan (maintainer, 2026-10-11); blocks are strong drafts, not pre-run (2026-10-15): red, then green, deviations recorded as rulings; prototype branches dropped
- [PR exists and conflicts check](pr-exists-and-conflicts-check.md) — `gh pr list --head` before creating; a conflicting PR gets no CI run, merge main and push; stacked PRs on an unmerged sibling
- [PR exists and conflicts check](pr-exists-and-conflicts-check.md) — `gh pr list --head` before creating; a conflicting PR gets no CI run, merge main and push; stacked PRs on an unmerged sibling; maintainer-pushed scratch branches built on the merged head, via `!`
- [Spec 015 follow-ups](spec-015-followups.md) — PR #32 draft, CI green; unchecked: graceful restart, art matching on, focus rings; later spec: "Running." after a crash, "Attempts 0"
- [CSS shell state by class](css-shell-state-by-class.md) — maintainer chose a layout-set class (`c-shell--footed`) over `:has()` (spec 018); a new body class breaks exact class-string assertions
- [Browser spec image and window gotchas](browser-spec-image-and-window-gotchas.md) — card images via 127.0.0.1:9 + data: pictures (`spec/support/card_images.rb`); decode() vs load; around runs before driven_by; saved collection view; float noise; downloads to tmp/downloads (two Firefox prefs, synchronize(10)); default Capybara wait is 2 s; no async script calls in helpers (#127)
Expand Down
1 change: 1 addition & 0 deletions .claude/memory/headless-firefox-narrow-frame.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Related: [[pr-screenshots-workflow]].
- An element screenshot of the frame (`find("#narrow").native.save_screenshot`) is cut off at the window's edge. Resize the window taller than the frame first (`page.current_window.resize_to(1280, 1000)` for an 844px frame).
- Measure related boxes in one script call. Two calls raced on the scanner page, which grows while the camera starts.
- **Keyboard-only selects (spec 041, 2026-11-06):** under WebDriver in headless Firefox, arrow keys don't change a closed `<select>`'s value (its `selectedIndex` stays 0 whether or not the list opens). A keyboard-only example chooses by type-ahead instead: focus the select and `send_keys` the option's first letter.
- **Scrollbars are pinned to classic (#271, 2026-11-10):** on a Mac, Firefox follows "Show scroll bars: Automatic" and draws overlay scrollbars (no width) when no mouse is connected, which made a 360px frame 15px wider than CI's and broke a wrap check locally only. `spec/support/system.rb` now sets `ui.useOverlayScrollbars = 0`. A driver registered under another name (the dark-theme ones) needs the same pref if it opens a narrow frame. See `docs/lessons/2026-11-10-a-local-only-failure-was-the-macs-scrollbar-setting.md`.
1 change: 1 addition & 0 deletions .claude/memory/pr-exists-and-conflicts-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ metadata:
- **After pushing:** run `gh pr view <n> --json mergeable,mergeStateStatus`. GitHub starts no CI run on a PR that conflicts with its base, so "no checks reported" a few minutes after a push can mean `main` moved. On 2026-10-10, PR #30 had merged and both branches added a bullet at the same place in `CLAUDE.md`.
- **Fix:** `git fetch origin main`, merge `origin/main` into the feature branch (branches 011 and 015 did this; my reading is that pushed branches here are merged, not rebased), keep both sides' bullets, run the whole suite, push. CI then starts.
- **Stacking on an unmerged sibling (spec 024, 2026-10-14):** when another in-flight branch changes the same files, the maintainer may say to stack on it. For #78, before the first push: `git fetch`, `git rebase origin/<sibling-branch>`, keep both behaviours, and bring your examples to the sibling's new wording (squashed into the commits that added them). Then run `bin/ci` again, push, and open the draft PR with `--base <sibling-branch>`, saying in the body that it is stacked and which PR merges first. The branch hadn't been pushed, so a rebase rewrote nothing shared. After the sibling merges, retarget with `gh pr edit <n> --base main` and run the mergeable check above. Expect the sibling's issue to be warned about at plan approval. The maintainer named #84 then, and the rebase came at the push approval.
- **A throwaway branch the maintainer pushes (spec 056 AC-4.2, 2026-11-10):** when a check has to be seen failing on GitHub, the probe commit can't go through Claude Code's pre-push hook, because `bin/ci` fails on it by design. The maintainer pushes it with a `!` command, which runs in their shell, skips the Claude Code hooks and needs no `--no-verify` (the repo installs no git pre-push hook; `.githooks/` has only `post-checkout`). Build the scratch branch on the worker's up-to-date branch, which has `origin/main` merged in, and check `git merge-tree --write-tree HEAD origin/main` exits 0. The first scratch branch sat on an older head, so #285 conflicted and GitHub ran no workflow until a merge was pushed on top. Give the maintainer `git -C <worktree> push origin <branch>`, so the command works from any folder.

Related: [[ghcr-and-actions-facts]], [[pr-screenshots-workflow]], [[adr-numbers-across-branches]], [[spec-024-followups]].
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
date: 2026-11-10
spec: 048 (AC-1.8), issues #271, #288
tags: [testing, system-specs, firefox, ci]
---

# Lesson: a local-only system-spec failure was the Mac's scrollbar setting

## Context

On 2026-11-10 every local `bin/ci` began failing `spec/system/collection_row_place_spec.rb:93` (spec 048 AC-1.8: a five-character set code wraps the table row's place line at 360px). GitHub CI stayed green on the same commits. The pre-push hook runs `bin/ci`, so every worker's push was blocked. The failure reproduced alone, in the full suite, and in two different worktrees, and nothing in the code, fonts, test data or Firefox (Selenium Manager's 159.0.1, last updated 5 November) had changed.

## What happened

Headless Firefox follows macOS's "Show scroll bars" setting, which defaults to "Automatically based on mouse or trackpad". While a mouse was connected, Firefox drew classic scrollbars, which take 15px of the frame. Once only the trackpad was attached, it drew overlay scrollbars, which take none. The narrow frame's page always scrolls (816px of content in an 800px frame), so the setting changes its content width:

| `ui.useOverlayScrollbars` | width | frame clientWidth | `.c-table__sub` height |
|---|---|---|---|
| 0 (classic) | 360 | 345 | 32 (wraps) |
| 1 (overlay) | 360 | 360 | 16 (one line) |

The line wraps by only about 5px with a classic scrollbar, so those 15px decided the result. CI's Linux Firefox draws classic scrollbars, which is why CI passed.

The failure screenshot looked like it showed a classic scrollbar. It was the thin overlay thumb.

## The fix

`spec/support/system.rb` pins `ui.useOverlayScrollbars = 0` next to the colour-scheme pin from #118 (#272), so local runs match CI whatever pointing device is connected.

## What to do next time

- When a browser spec fails locally but passes on CI, and nothing in the repo changed, compare the browser's environment with CI's before reading code. Check the OS appearance settings (scrollbars, dark mode, font smoothing), the attached devices, and the browser's prefs. Pin any that a layout depends on in `spec/support/system.rb`.
- Measure inside the frame (`clientWidth`, element heights) under both settings; a probe spec in the scratchpad did it in minutes.
- An assertion that passes by a few pixels is fragile. AC-1.8's wrap only holds at a 345px content width, which a real phone (overlay scrollbars) doesn't have; #288 restates it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
date: 2026-11-10
spec: 055 (FR-2), issue #283, PRs #277, #284
tags: [dependencies, dependabot, solid-queue, testing]
---

# Lesson: a stale Dependabot branch, and a patch release that rewrote job rows

## Context

Five Dependabot PRs were waiting on 2026-11-10. Four were green and merged. #277 (`solid_queue` 1.7.0 → 1.7.2) failed one example, `spec/models/catalog/operation/queue_spec.rb:123` (spec 055 FR-2: "Retry now" leaves a job's Active Job id and arguments as they were).

## What happened

**A patch release changed what is stored in a job's row.** solid_queue 1.7.2 gives a concurrency slot back only to the semaphore it came from (rails/solid_queue#831). To know which semaphore that is, it records it under `solid_queue_concurrency_lock` in the job's stored `arguments` when it dispatches a concurrency-limited job. `Catalog::RefreshJob` uses `limits_concurrency`, so `Catalog::Operation::Queue#run_now` now changed the column. The example compared the whole column. The job's own Active Job arguments and id hadn't changed. The maintainer ruled (#283, option A) that FR-2 means the Active Job id and arguments, and #284 narrowed the example and recorded the reading in `catalog-operations.md`.

**The Dependabot branch was stale.** By the time #277 was looked at, `main` had moved through several merges: four other bumps, specs 058 and 059, and plan retirements. Diffing `main` against the Dependabot branch showed 59 files and about 2,000 lines, and reverted all of that work. Its real change was two lines of `Gemfile.lock`: the gem's version and checksum. #284 applied just those two lines on a fresh branch from `origin/main`, and #277 was closed as replaced.

## What to do next time

- Before merging a Dependabot PR that `main` has passed, check `gh pr view N --json mergeable` and diff its branch against `origin/main`. If the branch is behind, take only its manifest and lockfile lines onto a fresh branch, or ask Dependabot to rebase (`@dependabot rebase`). Don't merge a stale branch's tree.
- Applying the lockfile lines by hand also avoids `bundle update`, which asks permission in this project.
- A spec that compares a whole stored column of a library's table (Solid Queue's `arguments`) depends on that library's bookkeeping, which can change in a patch release. Compare the fields the spec actually promises.
- When a bump fails a spec, read the release notes before changing either the code or the spec. Here they named the cause in one sentence.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
date: 2026-11-10
spec: 027 (the specs index and plan retirement), 035 (CI and the pre-push hook)
tags: [process, ci, parallel-workers, merging]
---

# Lesson: every merge made the open PRs conflict in the specs index

## Context

On 2026-11-10, four Orca workers built specs 056 to 059 in parallel (#264, #248, #250, #187) while the coordinator merged PRs as the maintainer approved them: four feature PRs, three plan-retirement PRs, two fixes and five dependency bumps. Every spec adds a row to `docs/specs/README.md`, and distillation and plan retirement change rows again.

## What happened

Almost every merge to `main` made the other open PRs conflict in `docs/specs/README.md`, sometimes in an area doc's distillation log as well:

- #279 conflicted after #280 merged.
- #282 conflicted after #281 merged; it had been green.
- #273 conflicted after #286 merged, right after its own green run; the coordinator had just merged the spec 057 plan retirement.

Each conflict is a one-line fix: keep every row. But all three branches carried code. The pre-push hook judges the whole branch against `origin/main`, so each conflict fix cost a full `bin/ci` (about 5 minutes) and a CI turn, plus a full GitHub run. #273 needed two extra pushes after its first green run. A conflicting PR also gets no GitHub CI at all (#285, the AC-4.2 scratch PR), so a conflict can look like CI that never started.

## What to do next time

- While a PR is in its last push (green CI, waiting to be marked ready), don't merge anything else into `main` unless the maintainer asks. Tell its worker that `main` is held.
- Merge docs-only PRs, plan retirements and distillation fixes after the code PRs they would conflict with, not between them. Retiring two plans in one PR (#281) also saves one conflict.
- Merge in the order PRs became ready, and tell every open worker after each merge (the coordinating skill's rule). Have them merge `origin/main` only when they push anyway or their PR shows a conflict.
- When a PR has "no checks reported" a few minutes after a push, check `gh pr view N --json mergeable` before waiting longer.
Loading