Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
1910c6c
docs(specs): draft spec 088, the scanner selection pane
plainprogrammer Nov 14, 2026
3c4b2ee
docs(specs): record the maintainer rulings in spec 088 (0.2.0)
plainprogrammer Nov 14, 2026
fc18d5e
docs(specs): revise spec 088 after the first review (0.3.0)
plainprogrammer Nov 14, 2026
3883f36
docs(specs): revise spec 088 after the second review (0.3.1)
plainprogrammer Nov 14, 2026
234bf5c
docs(specs): approve spec 088 at Gate A (1.0.0)
plainprogrammer Nov 14, 2026
6e77384
docs(specs): draft the plan for spec 088
plainprogrammer Nov 14, 2026
68a58d9
docs(specs): revise the plan for spec 088 after its first review
plainprogrammer Nov 14, 2026
a6bb3fe
docs(specs): revise the plan for spec 088 after its second review
plainprogrammer Nov 14, 2026
a289c85
docs(specs): record Gate B for the spec 088 plan
plainprogrammer Nov 14, 2026
a7651cb
feat(scanner): name the evidence that makes the scanner sure of a car…
plainprogrammer Nov 14, 2026
0646efb
feat(scanner): the selection a reading's answer is built from (spec 088)
plainprogrammer Nov 14, 2026
5853986
test(scanner): specify the selection pane's answers (spec 088)
plainprogrammer Nov 14, 2026
d8cf8db
feat(scanner): the selection's partials, helpers and icons (spec 088)
plainprogrammer Nov 14, 2026
434a18b
feat(scanner): answer a reading with the selection frame (spec 088)
plainprogrammer Nov 14, 2026
2793d8f
feat(scanner): the pane's skeleton, strip and empty-band styles (spec…
plainprogrammer Nov 14, 2026
ffe8ec6
feat(scanner): the selection controller notes the centred card (spec …
plainprogrammer Nov 14, 2026
b5737b5
feat(scanner): the reader writes the strip and the opening sentence (…
plainprogrammer Nov 14, 2026
25f0c93
test(scanner): the result spec follows the selection pane (spec 088)
plainprogrammer Nov 14, 2026
7b6dc99
test(scanner): the adding spec follows the selection pane (spec 088)
plainprogrammer Nov 14, 2026
7ba10b0
test(scanner): the art spec follows the selection pane (spec 088)
plainprogrammer Nov 14, 2026
83c0308
test(scanner): the detection spec follows the selection pane (spec 088)
plainprogrammer Nov 14, 2026
0a219a3
test(scanner): the timing spec names the selection frame (spec 088)
plainprogrammer Nov 14, 2026
92f9bc1
test(scanner): the sitting spec follows the selection pane (spec 088)
plainprogrammer Nov 14, 2026
a37957c
test(scanner): the scanner spec follows the selection pane (spec 088)
plainprogrammer Nov 14, 2026
dd3a135
test(scanner): the measurement spec follows the selection pane (spec …
plainprogrammer Nov 14, 2026
500099b
feat(scanner): fit the pane to the three phone sizes (spec 088 decisi…
plainprogrammer Nov 14, 2026
e83a861
docs(design-system): the pane's new floors (spec 088 decision 13)
plainprogrammer Nov 14, 2026
a385905
docs(specs): correct AC-5.2 to the measured 6px scroll (spec 088 1.0.2)
plainprogrammer Nov 14, 2026
bdb39b4
test(scanner): the pane's controls meet the touch floor and its files…
plainprogrammer Nov 14, 2026
aa3105e
refactor(scanner): remove what the selection pane replaced (spec 088)
plainprogrammer Nov 14, 2026
68ebe1d
docs(scanner): the selection pane in the area doc and the design syst…
plainprogrammer Nov 14, 2026
8b91216
fix(scanner): hide the label of the compact capture strip (spec 088 d…
plainprogrammer Nov 14, 2026
a8b9285
docs(scanner): the compact strip hides its label (spec 088 1.0.3)
plainprogrammer Nov 14, 2026
5f07f40
docs(specs): screenshots and the verification record for spec 088
plainprogrammer Nov 14, 2026
fe912ed
fix(scanner): a row at the 200 cap ends there with no more item (spec…
plainprogrammer Nov 14, 2026
beab3aa
fix(scanner): Send again keeps the strip's texts and the pane open (s…
plainprogrammer Nov 14, 2026
315dce3
fix(scanner): a successful retry drops the failed-load alert and anno…
plainprogrammer Nov 14, 2026
b0ecad2
test(scanner): the not-sure pane's parts do not overlap (spec 088 AC-…
plainprogrammer Nov 14, 2026
ade4a10
test(scanner): three possible cards and Add etched are exercised (spe…
plainprogrammer Nov 14, 2026
5ca5187
test(scanner): four acceptance criteria the system examples did not a…
plainprogrammer Nov 14, 2026
606cdd9
chore(scanner): remove the dead .c-scanpane__result > .c-status__mess…
plainprogrammer Nov 14, 2026
95c57fd
docs(specs): record the implementation review for spec 088
plainprogrammer Nov 14, 2026
fc8fef5
chore(memory): capture session learnings from spec 088
plainprogrammer Nov 14, 2026
658a7e9
docs(architecture): distil spec 088 into the scanner area doc
plainprogrammer Nov 14, 2026
d669601
docs(specs): mark spec 088 frozen by the scanner area doc (spec 027 D4)
plainprogrammer Nov 14, 2026
4cc00f5
docs(specs): record the distillation of spec 088
plainprogrammer Nov 14, 2026
1aed3d8
Merge origin/main into 088-scanner-selection-pane
plainprogrammer Nov 14, 2026
a846a3c
chore(licences): list the selection pane's two new icons in the manifest
plainprogrammer Nov 14, 2026
5750417
Merge origin/main into 088-scanner-selection-pane
plainprogrammer Nov 14, 2026
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
2 changes: 2 additions & 0 deletions .claude/memory/pr-screenshots-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ metadata:
- **Tab bar in narrow-frame shots (spec 077, 2026-11-11):** the fixed tab bar sits at the frame's bottom: a frame taller than the window (e.g. `open_in_narrow_frame` with `height: 1500`) keeps it out of an element shot of a panel; a full-page phone shot needs a frame no taller than the window's viewport to show it (spec 074).

**A frame taller than the window's viewport loses the fixed tab bar (spec 074, 2026-11-11):** a narrow frame is clipped to the window, and the phone tab bar is fixed to the frame's bottom, so the picture shows no bar. Reproduced: an 815px frame in a 1280×800 window (a 715px viewport) gave an 815px shot with no bar, in a 1280×900 window (815px viewport) the same shot had it, and a 1700px frame in this suite's 1400×1400 window (1315px viewport) gave a 1315px shot with no bar. Spec 071's shots 03, 04, 05 and others look like this; its window can't be read back, so the cause is reproduced, not confirmed. Make the window taller than the frame (`page.current_window.resize_to(…)`, and the after hook restores it) or shoot in a frame no taller than the viewport, and look for the bar in every phone shot.

**Scanner pane shots (spec 088, 2026-11-13):** from a throwaway system spec with the synthetic camera, as above. Seed an applied `Catalog::RefreshRun` or the "catalog not loaded" notice overlays the camera in every shot. Park the pointer off the controls before each shot: "Scan again" otherwise shows its hover colour in some shots. An empty live region takes no space, so the driver reports it not displayed: assert it with `visible: :all`. Wait for the pane at rest (`document.getAnimations()` idle and the hidden "Done." sentence). Never `rm` between runs (the harness prompts the maintainer): write each re-run to a fresh literal folder.
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Ruby 4.0.7, Rails 8.1.4, SQLite in every environment (including production), Hot
- **Admin catalog and jobs (specs 015, 024, 055; ADRs 0014, 0015):** the refresh and art build lifecycles, stall and interrupt rules, rate-limit retries, "Retry now" for a waiting retry and the `/admin/catalog` and `/admin/jobs` pages are in [`docs/architecture/catalog-operations.md`](docs/architecture/catalog-operations.md). In specs, tag an example `:solid_queue` to queue through Solid Queue (no worker runs) and move jobs with `queue_job`, `claim_job`, `fail_job`, `finish_job`.
- **Footer and open catalog (specs 018, 023, 084, 087):** the footer's lines come from each source's `.content_notice`, the licence line is the core's last line, and core code never names a collectible (`spec/footer_core_spec.rb`, `spec/licences_core_spec.rb`). A new gem, pinned OCR package, importmap pin outside `app/javascript`, font or icon needs its entry in `config/third_party_licences.yml` (or, for a gem, a licence), or the suite fails. `Catalog.public?` is the one test of `COLLECTOR_PUBLIC_CATALOG`, never read from the environment in tests (tag an example `:public_catalog`), and specs set the build facts with `build_facts(...)`, never ENV; views use `collector?`, never the setting. Rules: [`catalog-core.md`](docs/architecture/catalog-core.md).
- **Active Storage variants (spec 014, ADR 0013)** use libvips through `ruby-vips`, which is bundled with `require: false` so the app boots without libvips (it then logs "requires the ruby-vips gem"). Development needs libvips (`vips` on Fedora, `libvips` on Debian/Ubuntu): `bin/setup` prints a hint when it's missing (`Collector::LibvipsCheck`), CI installs it, and `bin/image-smoke` fails an image whose variant transformer isn't vips. The only Active Storage upload is the import preview's stored file (`ImportUpload`, spec 028, ADR 0022), which meets ADR 0013's owed validation for itself (content type by content, size; no pixels, it is no image); pixel limits stay with the first image upload. Active Storage's routes are gated app-wide by `ActiveStorageGate` (`config/initializers/active_storage_gate.rb`): sign-in, ownership of original blobs only, and 404 for direct uploads and the disk service's upload PUT until a feature needs them; the first image feature extends ownership to variants and previews (ADR 0022).
- **Card scanner (specs 005, 007–011, 040, 042, 047, 082):** `/scanner`, a one-screen focused page (spec 082), linked from the navigation ("Scan") and the collection page. How it reads a card, ranks candidates (with the optional art match), adds a copy, keeps the sitting with its condition and place, and records findings is in [`docs/architecture/scanner.md`](docs/architecture/scanner.md). Before you look: the OCR engine is fetched and checksum-verified into the ignored `vendor/ocr/v7.0.0/` by `bin/fetch-ocr-engine` (run by `bin/setup` and the Dockerfile), and scanner specs fail until it's installed; art matching (opt-in, `COLLECTOR_MTG_ART_MATCHING`) needs ImageMagick (`magick` or `convert`) for its build and specs; only scanner pages send a Content-Security-Policy (`ScannerPage` concern); the sitting's files name no collectible (`spec/scanner_sitting_core_spec.rb`); `script/scanner/detector_parity.rb` checks the detector (`app/javascript/scanner/detector.js`) against the spike's; measurement mode (`/scanner/measurement`, with the `scanner:` rake tasks and `script/scanner/` for the findings) is development-only (`config.x.scanner_measurement`; `COLLECTOR_SCANNER_MANIFEST`, `COLLECTOR_SCANNER_RUN_DIR`, `COLLECTOR_SCANNER_KEEP_FRAMES=1` keeps live frames) and writes outside the repo; the spikes are in `spikes/card_scanner/`. Phones need HTTPS: `bin/dev-certificate` makes a self-signed certificate outside the repo and prints the `bin/dev -b "ssl://…"` bind (alternative: a tunnel with `RAILS_DEVELOPMENT_HOSTS` + `COLLECTOR_HTTPS=true`).
- **Card scanner (specs 005, 007–011, 040, 042, 047, 082, 088):** `/scanner`, a one-screen focused page (spec 082), linked from the navigation ("Scan") and the collection page; a result opens in its selection pane (spec 088, `Scanner::Selection`, `scanners/_selection`): the capture's strip, a status band, a swipe row of printings or possible cards and one add button per finish. How it reads a card, ranks candidates (with the optional art match), adds a copy, keeps the sitting with its condition and place, and records findings is in [`docs/architecture/scanner.md`](docs/architecture/scanner.md). Before you look: the OCR engine is fetched and checksum-verified into the ignored `vendor/ocr/v7.0.0/` by `bin/fetch-ocr-engine` (run by `bin/setup` and the Dockerfile), and scanner specs fail until it's installed; art matching (opt-in, `COLLECTOR_MTG_ART_MATCHING`) needs ImageMagick (`magick` or `convert`) for its build and specs; only scanner pages send a Content-Security-Policy (`ScannerPage` concern); the sitting's files name no collectible (`spec/scanner_sitting_core_spec.rb`); `script/scanner/detector_parity.rb` checks the detector (`app/javascript/scanner/detector.js`) against the spike's; measurement mode (`/scanner/measurement`, with the `scanner:` rake tasks and `script/scanner/` for the findings) is development-only (`config.x.scanner_measurement`; `COLLECTOR_SCANNER_MANIFEST`, `COLLECTOR_SCANNER_RUN_DIR`, `COLLECTOR_SCANNER_KEEP_FRAMES=1` keeps live frames) and writes outside the repo; the spikes are in `spikes/card_scanner/`. Phones need HTTPS: `bin/dev-certificate` makes a self-signed certificate outside the repo and prints the `bin/dev -b "ssl://…"` bind (alternative: a tunnel with `RAILS_DEVELOPMENT_HOSTS` + `COLLECTOR_HTTPS=true`).
- **Export format and import (specs 017, 022, 025, 028, 032, 062, 068; ADRs 0020–0023, 0025, 0028):** the current rules are in [`docs/architecture/data-portability.md`](docs/architecture/data-portability.md); the format is defined by `docs/export-format/README.md`. Version 4 is current (`ExportFormat::Rules::VERSIONS` is `[1, 2, 3, 4]`); a new table with `account_id` or `user_id`, or a new column on `accounts`, `lots`, `users` or `locations`, fails the suite until the document's Coverage tables list it; a new field of collector-owned data raises the version.
- **Import preview and apply (specs 025, 028):** area doc above. The stored upload's retention and sweep (`ImportUpload`, `ImportUpload::SweepJob`, ADR 0022) and the 40 MB request body caps (Puma, `MAX_REQUEST_BODY` in the `Dockerfile`, `config/deploy.yml`) are in [`docs/architecture/release-and-deploy.md`](docs/architecture/release-and-deploy.md). `Location.upsert_all` and `CollectionImport::Apply#upsert_lots!` (a bound multi-row `INSERT .. ON CONFLICT`) skip validations on purpose (the reader already checked each row).
- **Collection and lots (specs 004, 006, 021, 026, 029, 032–034, 041, 059, 062, 064–068; ADRs 0019, 0025, 0028):** a lot's identity (printing, finish, condition, price paid and place, `lot_key`), the 9,999 cap, merging, Move copies, the grid and table, bulk mode and its Undo, number entry, locations and their names, and decks, their zones and the label "Deck › Zone" are in [`docs/architecture/collection-and-lots.md`](docs/architecture/collection-and-lots.md); how the export, preview and apply cover lots, locations and decks is in [`docs/architecture/data-portability.md`](docs/architecture/data-portability.md). Guards an agent trips first: `Location::Name` is the one definition of a name (`spec/models/location/name_parity_spec.rb`); five places build a `lot_key` and all must carry the place; `Account` declares `lots` before `locations` because the restrict foreign key needs lots deleted first (`spec/models/account_spec.rb`); a location has a kind, so a list says which one it means (every picker and the collection filter offer places and zones through `Location::Choices`, and a write names its location through `Location.find_placeable!`; the Locations page and the export stay places only; [`collection-and-lots.md`](docs/architecture/collection-and-lots.md)), and the deck files (the deck page's `Catalog::DeckCards`, `Catalog::DeckShape` and `Catalog::DeckChecks` seams among them) name no collectible, format or zone key but `considering` (`spec/deck_core_spec.rb`); number entry and the location files name no collectible type (`spec/number_entry_core_spec.rb`, `spec/import_apply_core_spec.rb`).
Expand Down
Loading
Loading