Skip to content

feat: design system, accounts, adding cards and the collection grid (004) - #5

Merged
plainprogrammer merged 66 commits into
mainfrom
feat/004-design-system-collection
Sep 30, 2026
Merged

plainprogrammer merged 66 commits into
mainfrom
feat/004-design-system-collection

Conversation

@plainprogrammer

@plainprogrammer plainprogrammer commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements spec 004 Design System, Accounts, Adding Cards and the Collection Grid (v2.1.3) per its plan and data model.

  • Design system installed. Tokens, c-* components, self-hosted fonts, logos, docs and previews from the Collector design-system export are in app/assets/stylesheets/collector/, app/assets/fonts/collector/, app/assets/images/collector/ and docs/design-system/, plus the collector-design-system Claude Code skill and the CLAUDE.md UI section.
    • App-specific patterns live in collector/additions.css, so a future re-export can replace the export's files wholesale.
    • Each app pattern has a doc in docs/design-system/components/ (16 new: StatusMessage, Form, AuthPage, TileAdd, FinishBadge, …).
  • App shell. On desktop the header has Collection and Search, plus an avatar menu. On phones a tab bar shows Collection, Search and More. Detail pages (the card page, and the lot add/edit/remove pages) use a back link. The theme follows the OS, and the logo swaps in dark mode.
  • Accounts and access.
    • Users, accounts (one each) and sessions are hand-written in the Rails 8 authentication pattern, with bcrypt and no mailer.
    • Every page requires sign-in except sign-in, sign-up and /up.
    • Sign-in is rate-limited per email + client address (COLLECTOR_TRUSTED_PROXIES). The session cookie is secure over HTTPS (COLLECTOR_HTTPS).
  • First run and administration.
    • On an empty instance the first visitor becomes admin. After that, sign-up is closed by default and admins can open it.
    • Admins manage users: add, edit, reset passwords, and delete with a confirmation page. The last admin can't be demoted.
    • bin/rails "collector:user[email]" creates an admin or resets a password.
    • The README warns operators to secure an instance before exposing or upgrading it.
  • Collection model.
    • Lot (core, tenant-scoped by account_id) merges copies with the same printing, finish, condition and price paid. A unique lot_key index enforces this, and a lot holds at most 9,999 copies.
    • The finish and condition vocabulary comes from MTG::Collecting through a Catalog.collecting registry, so the core stays collectible-agnostic.
    • Price paid is stored in minor units. The currency comes from COLLECTOR_CURRENCY (default USD), and an unknown code fails boot.
  • Search. Results are grouped as tiles showing owned counts, with printings you don't own faded. Quick Add updates the page in place through a Turbo morph refresh, with no reload and the scroll position kept. It works without JavaScript through a 303 redirect. All 002 search behaviours are kept except the ones AC-6.5 supersedes.
  • Card page (ItemPage).
    • Details, Owned, Your copies (edit and remove each lot), Printings (Shown and Retired tags, quick add) and a paper-format legality list.
    • Mana costs use Collector's own pips with spoken labels.
    • Add a copy takes quantity, finish, condition and price.
  • Collection grid. One tile per printing, with the total quantity and a single finish badge. It filters by name in place and shows "N of M items". It's paginated at 120 and shows empty states.

Screenshots

Captured from a development server against the full English Scryfall catalog (106,636 printings) and a seeded collection, using headless Firefox. Phone shots are 390px: they're rendered in a fixed-width frame because headless Firefox won't open a window narrower than 500px.

Collection grid Collection filtered ("bolt")
Collection Filtered
Search: owned counts, faded unowned, quick add After quick add: in-place status
Search Quick added
Card page (desktop) Card page (dark)
Card page Card page dark
Phone: collection Phone: search Phone: card page Phone: More
Phone collection Phone search Phone card Phone more
Add a copy Users and sign-up Sign in Collection (dark)
Add a copy Admin Sign in Dark collection

Verification

  • bin/ci passed (it's the pre-push hook): RuboCop, Brakeman (0 warnings), bundler-audit, importmap audit, and RSpec with 329 examples, 0 failures. The system specs were stable over repeated runs.
  • The spec-alignment review (SDD Mode B, 3 rounds) ended SPEC-ALIGNED, with no unresolved acceptance criteria.
  • Migrations roll back and re-apply cleanly against a 002 database, and schema.rb is unchanged afterwards. zeitwerk:check passes, and production boots with USD; COLLECTOR_CURRENCY=XYZ fails boot as intended.
  • Manual walkthrough at 1280px and 390px, light and dark, against the design-system previews. It found and fixed four issues (commits 073bb82, 04e6682, 09b5e9c, 395ed95):
    • a shared face image shown twice
    • stylized English printed_name on tiles
    • set · number wrapping on phones
    • no spacing above forms
  • Performance (the NFR): with 5,000 lots and the full catalog, in development mode, /collection took 54–106 ms, and search took 177–255 ms warm (425 ms cold).
  • collector:user was checked against a real database: after it created the first admin, the first-run redirect stopped.

Notes for review

  • Upgrading existing instances: an upgraded instance has no users, and the first visitor becomes admin. The README's "Before you expose Collector" and upgrade notes cover this.
  • Spec changes during execution (v2.1.0 → v2.1.3) are recorded in the spec changelog, and controller rulings are in the commit bodies (Ruling: lines).
  • Known follow-ups (not in scope):
    • Bulk and table views, and export/import.
    • Focus rings and touch targets were checked only by eye.
  • Added after review:
    • 19ea114: a shared card image, or the placeholder when there's no image, names every face it covers.
    • 1869546: the Claude pre-commit RuboCop hook now also lints files staged by git add in the same command, and tracked changes for commit -a.

🤖 Generated with Claude Code

plainprogrammer and others added 30 commits September 29, 2026 21:58
…collection

Phase 0: Doc-first commit and routing spec

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 0: Doc-first commit and routing spec

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 1: Install the design system

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

Phase 1: Install the design system

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2: Users, sessions and sign-in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2: Users, sessions and sign-in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2: Users, sessions and sign-in

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

Phase 2: Users, sessions and sign-in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling: renumber unreleased 004 migrations after 002 — keeps migration history ordered — cost if wrong: one renamed file

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

Phase 3: First run, sign-up and the sign-up setting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 3: First run, sign-up and the sign-up setting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 3: First run, sign-up and the sign-up setting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 4: App shell and navigation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 4: App shell and navigation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling: follow Rack 3.2's status name — removes deprecation noise — cost if wrong: none (same 422)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 5: Lots, collecting vocabulary and currency

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 5: Lots, collecting vocabulary and currency

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

Phase 5: Lots, collecting vocabulary and currency

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 6: User administration and the command-line user command

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 6: User administration and the command-line user command

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 6: User administration and the command-line user command

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

Phase 6: User administration and the command-line user command

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling: AC-3.6 closes sign-up only on first run — creating a later admin must not undo an admin's setting — cost if wrong: one line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 7: Search in the design system, with owned counts and quick add

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 7: Search in the design system, with owned counts and quick add

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling: headless Firefox's 500px minimum window width means phone widths are tested in a fixed-width same-origin iframe — cost if wrong: tests slightly further from a real phone browser

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 8: The card page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 8: The card page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 8: The card page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 8: The card page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
plainprogrammer and others added 27 commits September 29, 2026 23:00
Phase 11: Design-system docs, 360px sweep and README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruling: FR-12 / Form pattern — invalid fields reference their messages — cost if wrong: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…4 v2.1.2)

Review round 1: spec patch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Turbo submits the sign-in form with fetch, so a visit straight after the
click could abort the POST before its session cookie landed. The helper
now waits for the avatar that every signed-in page renders.

Review round 1: WU-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: WU-8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Split the 390px card page example into header/actions and AC-8.10
(centred image, folded copy rows) examples sharing a setup helper, and
move the collection filter example's records into a helper, so both
fit RSpec/ExampleLength without weakening any assertion.

Review round 2: WU-10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assert that a printing with no usable image renders the c-tile__missing
fallback (name on the sunken surface, no <img>) on search results, the
collection grid and the card page.

Review round 2: WU-11

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Walkthrough: B

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Multi-face layouts whose faces share one image (adventure, split,
prepare) showed the same image twice; show each distinct image once.

Walkthrough: A

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
English printings can carry a stylized printed name ("SERRA ANGEL");
tiles now use Catalog::Entry#display_name, which shows the printed name
only when the printing isn't English (AC-6.2, AC-11.2).

Walkthrough: B

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At 390px the SET · number link in the Printings list wrapped mid-code
beside a long set name; the code no longer wraps, the set name still can.

Walkthrough: C

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lot and admin user forms sat flush under .c-pagehead, so the first label
touched the stats line; a form directly after a page head now gets the
section spacing (space-6). Other page-head pages are unchanged.

Walkthrough: E

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Captured from the dev server against the full English catalog, at 1280px in
light and dark, and at 390px in a fixed-width frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pre-commit PreToolUse hook ran before the Bash command, so it saw an empty
index for 'git add X && git commit'. It now also lints changed and untracked
Ruby files when the command runs git add, and changed tracked files when the
commit uses -a/--all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up: shared face image alt text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Turbo restores the previous page on Back from a snapshot that keeps the
typed field values, so the results reverted but the search box still
showed the old query. A url-sync Stimulus controller on the search and
collection filter forms resets each field from the URL after every
Turbo load (spec 004 AC-6.1, AC-11.4). Without JS the server already
renders the right values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hardening for an intermittent CI failure (run 36748308952) that never reproduced locally; hypothesis: go_back landed mid-navigation.
Ruling: wait for Turbo idle — cost if wrong: none; the flake may persist and needs further diagnosis.

Before go_forward only <html> is checked: Turbo 8.0.23 snapshots the page
while the frame-targeting form submission still marks the results frame
busy, so after Back the restored frame keeps a stale busy attribute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@plainprogrammer
plainprogrammer merged commit 5bc4779 into main Sep 30, 2026
1 check passed
@plainprogrammer
plainprogrammer deleted the feat/004-design-system-collection branch September 30, 2026 18:03
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.

1 participant