Repository navigation
040: Scan a Sitting into a Location - #172
Merged
Merged
Conversation
Spec 040 Phase 1 (AC-6.0, NFR Migration): scanner_sittings.location_id, nullable, indexed, REFERENCES locations ON DELETE SET NULL, added with a raw ADD COLUMN outside a transaction (the lots migration's pattern), so no table is copied and no entry is lost. Down drops the column whatever it holds: the place is working state. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec 040 Phase 2 (AC-1.3-1.6, AC-1.8, AC-4.3, FR-1, FR-2): the sitting belongs to an optional location, Sitting.add! reads it inside its transaction and adds through Lot.add!(location:), Sitting.place! opens, sets, clears or drops the account's sitting, #pointless? marks a sitting with no entries at all and no place (plan P1 as ruled), and kept_entries preloads each entry's lot and its location. Ruling: one commit per phase (test and implementation together) per the dispatch brief, instead of the plan's separate test/feat commits. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec 040 Phase 3 (Stories 1-5, FR-1): PATCH /scanner/sitting with sitting[location_id] opens, sets, clears or drops the account's sitting through Sitting.place!, answering a Turbo Stream that redraws the panel. The location is found through Current.account.locations; another account's, a removed one or a non-number is a 422 with the decision 7 alert, a missing key a 400. The picker partial renders in the panel, alone with no sitting open, and under the Done summary, for accounts with locations only. The add notice names the entry's lot's place, and the scanner drops a sitting with no entries and no place before render (plan P1 as ruled). Import apply specs now carry a placed sitting (AC-3.2's import case, AC-5.3). Ruling: a status message is cleared (no text) when clearing the place deletes the sitting, as the plan says no message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 040 Phase 4 (decisions 3, 4; AC-1.2, AC-2.3, Story 7): the heading reads "This sitting: N cards, in Blue binder" while a place is set, each entry names its lot's place as it is now (none for a changed entry), and an entry-less sitting says "Cards you scan now go into Blue binder." or, with no place, "Nothing added yet."; spec 009's all-undone copy stays. The Scanner component doc describes the control and the copy. Ruling (plan P2): the picker is the stacked Form (c-form, c-field, c-form__actions) with no new CSS; the 360px system check in Phase 5 and the screenshots confirm it fits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 040 Phase 5 (Stories 1, 2, 5, 7; AC-1.2, AC-1.7, AC-2.1): headless Firefox opens a sitting in a place without reloading the page or its camera, scans into it, changes the place mid-sitting and scans again, keeps the place on a reload, clears it with cards kept, ends with the control on No location, chooses a place by keyboard alone, and fits a 360px frame with a long location name. The 360px example found two overflows, fixed here: the sitting's heading kept c-section__title's nowrap (it now wraps, as .c-panel's title does), and .c-field select grew to its longest option (min-width:0 and max-width:100%, which also covers the lot form's location select). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 040 Phase 6: collection-and-lots says the scanner adds into its sitting's place and that removing a location clears a sitting's place (decisions 2, 4, 7; AC-1.3, AC-3.2), drops the #56 open item and cites spec 040; the roadmap marks item 21a built and records that it ran before the spikes by ruling (decision 1); the index records 040's supersessions on the 009 and 032 rows. Specs 009 and 032 are not edited. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Headless Firefox against the worktree's dev server, whose empty database was seeded by a scratch script (one collector, three printings, two locations, one applied refresh run; tiles show placeholders, not art): choosing Blue binder for the sitting, confirming a picked photo (the specs' synthetic card; no camera headless) and the lot in the collection; the panel at 390px from a 390px iframe on /collection (the scanner sends frame-src 'none') and at desktop width, light and dark. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er pages From spec 040's implementation review (minor findings): the scanner dropped a sitting with no entries and no place by checking and deleting in separate statements, so an add from another tab in between could lose its entry; Scanner::Sitting#drop_if_pointless! now reloads, checks and deletes in one write transaction. current_sitting moves into the ScannerSitting concern so measurement mode drops it too. A guard proves another account's locations stay out of the select (FR-1), and Form.md records that a select never widens past its form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per-AC evidence, the requirement and document checks, the screenshots, and the Mode B review (SPEC-ALIGNED) with how each finding was handled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 040 lesson: put the longest user-supplied name in every narrow-width check (a nowrap section title and a flex select overflowed at 360px), and read a destroyed record's associations only after checking it persisted. 0 memories written, 1 lesson written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rtability Collection and lots already carried spec 040's statements (Phase 6); it gains its distillation log row. Data portability's working-state invariant now names the sitting's place (decision 5, AC-5.3) and an apply that removes only a location (AC-3.2), with spec 040 in Sources and the log. The index marks 040 frozen in part by both docs, plan retirement owed. The scanner has no area doc, so the sitting's own rules stay in the spec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… portability area docs (spec 027 D4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Follow-ups filed by the coordinator: #182 (keep focus and announce after the sitting panel redraws), #178 (harness tmpdir race). For the maintainer: on a real phone, check the camera keeps running across Set. Note the app-wide CSS change: |
plainprogrammer
marked this pull request as ready for review
November 6, 2026 05:58
Brings in specs 037 (filter the collection by location) and 039 (a missing art index file isn't ready). Conflicts resolved by keeping both sides: db/schema.rb regenerated by migrating (main's bulk_selections column plus this branch's scanner_sittings.location_id, its index and foreign key); collection-and-lots.md keeps spec 037's and spec 040's Sources lines, both distillation log rows, #47 and #56 closed; the specs index takes main's 039 row and this branch's 040 row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 6, 2026
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.
Implements spec 040 (v0.2.2) following its plan. A collector picks one of their locations for the scanner sitting, and every card added afterwards goes into the lot of that printing and finish in that place. The place can change mid-sitting (later adds only), survives a reload, and goes when the sitting ends. An account with no locations sees the scanner exactly as before. Nothing changes in the export.
What each phase did
31a9a0ascanner_sittings.location_id, nullable and indexed,REFERENCES locations ON DELETE SET NULL. Added by a rawADD COLUMNoutside a transaction (the lots migration's pattern), so no table is copied and no entry is lost. A migration spec proves down/up keeps every sitting and entry (AC-6.0).e4bedffScanner::Sitting.add!reads the place inside its transaction and adds throughLot.add!(location:).Sitting.place!opens, sets, clears or drops the sitting.#pointless?implements P1's narrower rule.kept_entriespreloads each lot and its location.35b0339PATCH /scanner/sittingwithsitting[location_id]answers with a Turbo Stream that redraws the panel. The location is looked up throughCurrent.account.locations: a bad value is a 422 with decision 7's alert, a missing key a 400. The picker appears in the panel, alone when no sitting is open, and under the Done summary. The add notice names the entry's lot's place.43d462cScanner.mdis updated.7a6256ad259770e286815,f392321verification.mdwith per-AC evidence and the implementation review.27862c71c1ab63,9a4ded9,2649e4b,7aa45a1Rulings and deviations
test:andfeat:commits.sdd-executesays to use them. I implemented each phase directly with TDD (red, then green). Only the Mode B review ran as an agent.Formwith no new component. Two existing rules changed after the 360px check failed:.c-scanner__sitting .c-section__titlenow wraps, like.c-panel's title already does..c-field selectgetsmin-width:0; max-width:100%, so a long option can't widen it past its form. This is app-wide: it also covers the lot form's location select.Form.mdsays so.31a9a0aande4bedffsayCo-Authored-By: Claude Sonnet 5.5by mistake. This session ran on Opus 5.5, and every later commit says so. Pushed commits were not rewritten.Tests
7a6256a: 2467 examples, 0 failures, 1 pending (the art agreement example, which needs the spike's cache).bin/ciat7aa45a1; the scanner model, request and system specs re-run after the fixes: 152 examples, 0 failures.spec/models/scanner/sitting_spec.rb(+16),spec/requests/scanner/sittings_spec.rb(+22),sitting_entries_spec.rb(+5),measurements_spec.rb(+1),apply_spec.rb(2 examples extended),spec/system/scanner_sitting_spec.rb(+5).bin/ci: passed in full as the pre-push hook at7aa45a1(setup, migration guard, RuboCop, Brakeman, bundler-audit, importmap audit, RSpec). One earlier pre-push run failedUpgrade::Harness removes its temporary directory, because another worktree's suite was running at the same time (Flaky: Upgrade::Harness "removes its temporary directory" races other suites through the shared tmpdir #178). It passed on the retry.Implementation review
SPEC-ALIGNED (sdd-review Mode B, read-only, Opus). The coverage matrix and how each finding was handled are in
verification.md.verification.mdwas missing. It has been written.drop_if_pointless!now reloads, checks and deletes in one transaction.Form.mddocuments the select's width.place!writes the row twice (insert, then update) when it opens a sitting. This is harmless.Screenshots
Headless Firefox against this worktree's dev server. Its database was empty, so a scratch script seeded it: one collector, three printings, two locations and an applied refresh run. Tiles show placeholders, not card art.
The add uses a picked photo: the specs' synthetic card, because there is no camera headless. The 390px shots come from a 390px iframe on
/collection, because the scanner sendsframe-src 'none'. Headless Firefox reports a dark colour scheme, so the walkthrough is dark.Walkthrough: choose Blue binder, pick a photo, add the Foil copy, and the lot shows Blue binder in the collection.
Check by hand
Closes #56
🤖 Generated with Claude Code