Skip to content

Add cards from a set by collector number (spec 026) - #114

Merged
plainprogrammer merged 24 commits into
mainfrom
026-typed-entry
Oct 16, 2026
Merged

plainprogrammer merged 24 commits into
mainfrom
026-typed-entry

Conversation

@plainprogrammer

@plainprogrammer plainprogrammer commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Closes #43.

What it does

A collector adds cards from one set by collector number, at /collection/number_entry ("Add by number", spec 026, roadmap item 8). It's linked from the collection page head, the empty collection's sentence and More (after "Export collection").

  • Set and language. Choose a set by code or from the suggestions. The language is EN when the set has English cards, otherwise its most common one. The page's URL holds the type, set and language, plus the finish once a line is added.
  • Lines. Each line is a number, a quantity and a finish, and it adds at once (Lot.add!).
    • The finish defaults to the type's first (Nonfoil), so typed and scanned copies share lots, and it carries over between lines.
    • A number matches exactly first, then ignoring case and leading zeros. When more than one card matches, you pick one.
    • A rejected line keeps your text, focused and selected.
    • Lines can be entered from the keyboard alone (number, Tab, quantity, Enter).
  • Undo and Done. Lines go into a per-account list (NumberEntry, NumberEntry::Line), idempotent per line key. Undo takes a line's copies back out of its lot; Done clears the list.
  • Scope and export. Everything is scoped to Current.account; another account's line answers 404. The list is working state, so the export's Coverage lists both tables as "Left out" (ruling 19); the format version is unchanged.
  • Without JavaScript. It all works through full page loads. Stimulus only manages focus; the page asks for Turbo's replace refresh so autofocus works (the layout's morph stays the default).

Spec: spec.md (v1.2.2, with a Supersedes section for spec 004 AC-11.6, the collection page head and spec 009 AC-8.2, spec 022 decision 6, and ADR 0015's app-wide morph). Plan: plan.md. Verification: verification.md.

Rulings

  • Maintainer: finish defaults to the type's first and carries over (D3), each line adds at once (D4), no live preview (D7). "Add by number" goes after "Export collection" on More and isn't in the account menu. Earlier specs stay frozen; supersessions are recorded in spec 026 and in docs/specs/README.md.
  • Execution: each deviation from the plan is recorded in its commit body and in verification.md.
    • CSS specificity fixes for the mono inputs and Add's alignment.
    • Non-string parameters count as absent.
    • The development timing used a synthetic catalog of full-English size: the first add took 66.5 ms (it opens the list and the lot), later adds 14–18 ms.

Review and verification

  • The implementation review (sdd-review Mode B) found it SPEC-ALIGNED. Its two minor test notes were added in 633db96.
  • bin/ci is green: RuboCop, Brakeman, bundler-audit, importmap audit, and RSpec with 1595 examples, 0 failures and 1 pending (the opt-in art agreement), on the merged tree.
  • origin/main is merged in, not rebased. The branch adds spec 026's index row and a distillation record: the collection and data portability areas have no area doc yet, so the plan's retirement is owed after the merge.

Screenshots

Light Dark
Set field, 1280
Rejected line, 1280
Rejected line, 390
Choose between matches, 1280
Choose between matches, 390

Focus ring in tab order (AC-2.13): number, quantity, finish, Add.

🤖 Generated with Claude Code

plainprogrammer and others added 22 commits October 15, 2026 10:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rulings (maintainer, 2026-10-15): the finish defaults to the type's first
finish and carries over; each line adds at once; no live preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 1.2.1 (patch): a Supersedes section for spec 004 AC-11.6, the
collection page head, spec 022 decision 6 (pending a ruling) and ADR
0015's app-wide morph. Plan: matches moves to a private method (next in
begin/end is a syntax error), a forged type parameter is ignored, Undo
writes through the account's lots, class selectors for the data inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling (maintainer, 2026-10-15) on the Fable review's I1: the row goes
after Export collection; spec/requests/shell_spec.rb keeps Export
collection second. Spec 1.2.2; plan Phase 4 and the MorePage doc follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 1: The list and its lines (spec 026, FR-3, FR-4, FR-5).

NumberEntry is the account's list of number entry adds (one per account,
opened by the first add, deleted with its lines by Done); NumberEntry::Line
records each add's entry, quantity, finish, lot (nulled when the lot goes)
and line key, with undone_at so a replayed key never adds again after an
Undo. Undo takes the line's copies out of its lot, removing a lot left
empty, and refuses once the lot holds fewer copies or is gone. Both tables
are Left out of the export's Coverage tables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2: Choosing the set and the language

NumberEntry::SetChoice offers every set with an active entry, resolves
typed text (code, name or "Name (CODE)" label in any case) to one of
them, and picks the language: the requested one if the set has it,
else EN, else the most common, ties to the first code.

Ruling: parameters that aren't strings count as absent (the plan's
draft called #to_s on them) — `{"a"=>"b"}.to_s` is a non-blank string,
so a forged hash type narrowed the candidates to none and the plan's own
"ignores a type ... that isn't a string" example failed — if wrong, a
non-string parameter is silently ignored instead of matching nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 3: Matching and checking a line

NumberEntry::LineForm matches a typed number among the active entries
of the chosen set and language (exact first, else ignoring case and
leading zeros), asks for a pick when several match, and checks the
quantity (1 to 9,999), the finish against the entry's own finishes,
and the line key. The finish defaults to the type's first finish; an
entry that lists no finishes adds with none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ection and More

Phase 4: The page, choosing a set, and the ways in

Adds the number entry page at /collection/number_entry: a set field with
the offered sets as suggestions, the chosen set's languages, the line
form, a choice between matches and the account's list. A typed set
redirects to the page's own link (type, code, language). The collection's
header and empty message and More link to it. The layout lets a page ask
for its refresh method; number entry asks for replace so autofocus works
after each answer, and every other page keeps morph.

Ruling: the signed-out examples create a user first — with no account at
all the app sends a first visitor to sign up, not sign in, so the plan's
draft failed for a reason unrelated to the feature — if wrong, nothing:
the first-run redirect is spec 004's and is covered elsewhere.

Ruling: spec/requests/admin/navigation_spec.rb now expects "Add by
number" after "Export collection" on More but not in the avatar menu —
spec 026 (v1.2.2, Supersedes: spec 022 decision 6) says More gains the
row and the account menu doesn't — if wrong, the avatar menu needs the
row too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 5: Adding lines

Collections::NumberEntries::LinesController#create checks the line
against the chosen set and language and adds its copies through
NumberEntry.add!, at most once per line key, then redirects back to the
page keeping the set, language and finish. A rejected line, an empty
number or a number with several matches answers 422 with the line as
typed and nothing added; the lot cap says "One lot can hold at most
9,999 copies."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 6: Undo, Done, and working without scripting

Undo takes a line's copies back out of its lot (removing a lot left
empty, which supersedes the session's bulk removal undo, as the
scanner's Undo does) and returns to the page with the set, language and
finish kept. A line whose copies changed answers 422 and changes
nothing; an undone line, another account's, or one Done cleared answers
404 with "That line is no longer in your list." Done deletes the list
and its lines, keeps the copies and returns to the collection.

Ruling: the AC-5.1 example's request-then-follow pairs go through a
small `loaded { ... }` helper — the plan's draft was 18 lines against
RuboCop's 15-line RSpec/ExampleLength limit; the requests and
assertions are unchanged — if wrong, only the example's shape differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 7: Keyboard, look and phone width. The number-entry Stimulus
controller selects a rejected number (AC-4.1) and lets Enter on the
finish select add the line (AC-2.4); the c-numentry styles lay a line's
fields in one row, two columns below 640px of the page; the NumberEntry
pattern, MorePage, EmptyState and the README record them. Screenshots at
1280px and 390px, light and dark, with a list, a rejected line and a
choice, plus each focused field, are under the spec's screenshots/.

The plan's browser-only doubts all held in headless Firefox: Escape then
Enter on the set field's suggestions submits the typed code; "f" on the
focused select picks Foil; Enter on the select reaches Stimulus; the
rejected number ends up focused and selected; 12 lines fit 360px.

Ruling: Add's cell is padded calc(var(--space-4) + var(--space-1)), not
var(--space-6) — the label is a 16px line plus the field's 4px gap, so
24px put Add 4px below the inputs in the screenshots; there's no 20px
token — if wrong, Add sits a few pixels off the inputs.
Ruling: the data face on the number and quantity inputs is
`.c-input .c-numentry__data-input` — the plan's single-class rule lost
to the export's `.c-input input` font, so the inputs stayed in the sans
face — if wrong, nothing beyond the face.
Ruling: the choice's c-list loses its top border as well as its rows'
(`.c-numentry__choice .c-list { border:0 }`) — the plan removed only the
rows' borders, leaving a stray rule under the prompt — cosmetic only.
Ruling: a one-language set reads "Language: <span data>EN</span>", only
the code in the data face — the label in Plex Mono looked like data;
the request spec for AC-1.7 now reads the text of .c-numentry__set
instead of a raw HTML substring (same intent) — cosmetic only.
Ruling: the keyboard example's three add-and-check steps go through an
`add_line` helper — the plan's draft was 17 lines against RuboCop's
15-line RSpec/ExampleLength; the keys and expectations are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 8: The core boundary, documents and verification.
spec/number_entry_core_spec.rb holds the feature's 18 files to the core:
none names a collectible type, a source or a finish. CLAUDE.md gains the
Project Status sentence and a Non-obvious Facts bullet; roadmap item 8
is marked done (the PR link follows when the PR exists). verification.md
maps every AC to its example or screenshot, records bin/ci (1593
examples, 0 failures, 1 pending), the measurement, the hosted-door
rules and the rulings made during execution.

Ruling: the measurement times five adds against a synthetic catalog of
99,000 entries in 900 sets, inserted in a rolled-back transaction in
development, not against the real English catalog — this worktree's
development catalog is empty, so the plan's script finds no MH3 — if
wrong, the real catalog's index use could differ; the query-count
example still holds the count fixed.
Ruling: spec/number_entry_core_spec.rb joins .rubocop.yml's
RSpec/DescribeClass excludes, as the other core specs do — it checks a
feature's files, not a class; the first bin/ci failed on it — one
config line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the implementation review (Mode B, SPEC-ALIGNED, minor notes 2 and 3):
- AC-1.11: a request example for a linked set code whose entries are all
  retired; it shows AC-1.8's message and no line form.
- NFR Performance: a query-count example for a number matched only
  without its leading zeros ("007" for "7"), small set against large.
verification.md cites both and its request-spec line numbers follow.

Ruling: the AC-1.11 example also holds one active set, because with no
active entries at all the page shows "catalog not loaded" instead of the
set form; that is the empty-catalog state, not the case under test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…review

Spec 026's row (Approved, plan present) lists what it supersedes, and the
rows of specs 004, 009 and 022 name it under "Superseded by"; none of those
specs is edited. verification.md gains the "## Implementation review"
section (SPEC-ALIGNED) that distill-feature reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extends the shared test database memory with the stalled follow-up
subagent, and adds the lesson that a plan's CSS must be checked against
the export's selector specificity. Approved by the maintainer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 026's areas, collection and lots and data portability, have no area
doc, so distill-feature writes none; the index row's plan reads
"retirement owed" and verification.md records the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@plainprogrammer plainprogrammer self-assigned this Oct 15, 2026
@plainprogrammer
plainprogrammer marked this pull request as ready for review October 15, 2026 23:33
plainprogrammer and others added 2 commits October 15, 2026 19:45
Conflicts, each resolved keeping both features:
- app/views/mores/show.html.erb: More reads Export collection, Import
  collection (025), Add by number (026), then the admin links.
- spec/requests/admin/navigation_spec.rb: both examples expect that More
  order; the account menu holds Export and Import, not Add by number.
- .rubocop.yml: the DescribeClass comment names both core specs and the
  request body limit spec (the exclude list merged cleanly).
- CLAUDE.md: main's Project Status and Export format text, with 026's
  number entry sentence and Non-obvious Facts bullet (after Import preview).
- additions.css: both blocks, Import preview (025) then Number entry (026).
- docs/design-system/README.md and components/MorePage.md: both App
  additions lines; the markup and bullets give the new More order.
- docs/specs/README.md: the 025 and 026 rows in spec order; 022's
  "Superseded by" names both.

Ruling: Add by number sits after Import collection on More, per the
coordinator's merge instruction (025 decision 14 puts Import directly after
Export). Spec 026 goes to v1.2.3 to record it in its Supersedes list;
number_entries_spec.rb:34 now expects the row fourth (it failed on the
merge, finding Import collection third). Cost if wrong: one row's order.

None of 026's screenshots shows More or the account menu, so none is
regenerated. db/schema.rb is unchanged (spec 025 adds no tables).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@plainprogrammer
plainprogrammer merged commit 8e6741e into main Oct 16, 2026
4 checks passed
@plainprogrammer
plainprogrammer deleted the 026-typed-entry branch October 16, 2026 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enter cards from a set by collector number

1 participant