Repository navigation
038: Store Currency per Account - #173
Merged
Merged
Conversation
Spec 038 FR-1, decision 1, AC-4.5: accounts.currency, NOT NULL, default USD, added without a DDL transaction so a rollback copies the table with foreign keys off. The format document's Coverage rows land with the column, since the Coverage guard fails the moment accounts has an unlisted column. Ruling: the Coverage rows move into this commit from the plan's "docs(export-format)" commit, so every commit leaves the suite green; the prose changes stay in their own commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec 038 FR-3, decision 3, AC-4.1–4.5: a data step sets every account to COLLECTOR_CURRENCY (strip, upcase, default USD), checked against an inline copy of the 20 codes, in one bound UPDATE; an unsupported code raises naming the variable and value and changes nothing; down leaves the data. The spec stubs ENV with a Hash (stub_const), which reaches ENV.fetch and ENV[] in the migration as the plan hoped. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec 038 FR-1, decision 2, AC-3.1–3.3, AC-3.5: the initializer keeps the boot
check and stores COLLECTOR_CURRENCY as x.default_currency; Account defaults
its currency to it, validates inclusion in the 20 codes ("Choose one of the
listed currencies.") and answers currency_unit. x.currency stays as a
temporary alias until phase 2 moves the last reader.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec 038 FR-5, AC-5.1–5.3: the export writes accounts.currency, still at format version 3; VERSIONS stays [1, 2, 3]. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec 038 decision 6, AC-5.4: the Currency section says the code is the collector's own, and the Versions section drops #64 from the expected changes, saying it moved without a version. Schemas and examples untouched. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The export now writes accounts.currency, while the import still compares with the instance key; the round trip of each valid example sets both until phase 2b rewrites it (spec 038 AC-6.10). Found by the pre-push run (seed 60803). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec 038 FR-4, decision 8, AC-1.3, AC-1.4: lot_price (card page, collection table, and through lot_line the Move copies and location refusal pages) and the lot form's label and prefix read Current.account.currency_unit; import_lot_line takes a required currency: a held lot in the account's, a file's row in the file's. Ruling: an import_previews_helper spec is added beside the planned lots_helper spec, so the new keyword has a direct test before 2b's request examples (AC-6.8). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 FR-6, decision 7, AC-6.1–6.9: a well-formed code outside the 20 is
refused as unsupported_currency ("Collector can't show prices in XYZ."),
replacing other_currency; the plan gains currency_change (from the account's,
to the file's), counts it against "nothing to change" and appends both codes
to the fingerprint only when set, so a plan without one keeps its old
fingerprint; replan reloads the account inside the apply's transaction; the
apply writes the currency first in its transaction; the notice ends
"; prices now shown in EUR"; the preview's Profile says "Your prices would be
shown in EUR instead of USD. Nothing is converted."
Ruling: the plan's three commits (refuse, plan, apply and notice) land as one,
because removing the other_currency refusal makes a file in another currency
previewable, which needs the plan's change to be right — splitting them would
leave a commit that previews a EUR file as "nothing to change".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 AC-6.10, AC-6.11: the round trip no longer stubs the instance currency or sets the account's; each valid example (versions 1–3) applies to an account at the default and exports the same collection, its currency included, and then has nothing to change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t its return Spec 038 decision 2, FR-4: x.currency is gone; spec/currency_reads_spec.rb fails on any read of x.currency or x.default_currency in app/, lib/ or config/ outside Account and the initializer. The source-check spec joins the RSpec/DescribeClass exclusions, as spec/footer_core_spec.rb does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 FR-2, decisions 4–5, AC-1.2, AC-1.3, AC-1.5–1.8, AC-2.1–2.3:
/currency/edit shows one native select of the 20 codes ("EUR — €", in
Collector::Currency's order, the account's own selected) and Save; PATCH
/currency updates Current.account only (params.expect), redirects to More
(303) with "Prices are now shown in EUR.", or re-renders 422 with "Choose one
of the listed currencies." and the saved code selected. No Stimulus.
Ruling: the two sentences ("Changing the currency doesn't convert…" and the
priced-lot count) are the field's c-field__hint, tied to the select with
aria-describedby, as the import page's hint is, so the page needs no new CSS
or pattern doc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 decision 4, AC-1.1: More lists "Currency" after Locations, and the desktop account menu after Import collection; the navigation examples that pin positions move by one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 AC-4.7: next.rb puts Grace's account in EUR, so the next release's upgrade proof carries a non-default currency across; the seeds spec checks it. No seed_exceptions entry was needed (the coverage check passes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 AC-7.1: a backup of accounts in USD and EUR restores each with its own currency, and the EUR collector's table writes €12.50. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 "Documents that change": collection and lots (price paid in the account's currency, the default, the Currency page, the guard), data portability (the export's source, unsupported_currency, the plan, fingerprint, preview, apply order and notice, More's rows), the north star's breach note closed, the roadmap's #64 line built, README's COLLECTOR_CURRENCY row and backup paragraph, compose.yaml and config/deploy.yml comments. docs/backup-restore.md repeats no currency sentence, so it is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Superseded-by cells of specs 004, 006, 017, 022, 025, 028, 030 and 032 name 038, matching its Supersedes table; the shipped specs are not edited. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found looking at the screenshots: after a refused code the account kept its errors, so Rails wrapped the label and select in .field_with_errors and the select shrank to its content width. The controller copies the messages for the page and clears the record's errors; the request spec asserts no wrapper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 038 Mode B review (verification.md): - m2: Collector::Currency.fetch! takes setting:, and Account#currency_unit names accounts.currency, so a bad stored code no longer blames the environment variable. - m3: a request example renders the stale page after a currency change and finds "Your prices would be shown in EUR instead of GBP." (AC-6.7). - m4: a lower-case `xyz` currency is a schema refusal (AC-6.6). - m1: MorePage.md lists Currency after Locations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
verification.md records each AC's evidence, the rulings, the AC-4.6 vacuity note on spec/backup_upgrade_spec.rb (STEP=1 now rolls back only the empty data step), the gaps, and the Mode B review (SPEC-ALIGNED WITH MINOR GAPS) with how each finding was handled. Screenshots and the walkthrough GIF were captured against the worktree's dev server with a seeded collector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lesson from spec 038: a form that renders its own field errors must clear the record's, or Rails' .field_with_errors wrapper breaks the c-field layout; found only in a screenshot of the refused state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-portability The upgrade that carried COLLECTOR_CURRENCY to every account (Story 4) and the backup's restoring each account's currency (Story 7) join the statements phase 4 wrote; both docs gain a Distillation log row and their Sources name the stories. The index marks spec 038 Frozen, by both docs, with its plan's retirement owed. CLAUDE.md states no currency rule, so it is unchanged. 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>
This was referenced Nov 6, 2026
Owner
Author
plainprogrammer
marked this pull request as ready for review
November 6, 2026 05:46
Brings in specs 037 (location filter) and 039 (art index missing file). Conflicts resolved by keeping both sides: db/schema.rb regenerated by migrating (accounts.currency and bulk_selections' location filter, version 2026_11_06_100001); collection-and-lots Sources and Distillation log hold both 037 and 038 (#47 and #64 both closed); the specs index keeps 037 and 039 frozen from main and 038 frozen from this branch, and spec 030's Superseded-by cell names both 039 and 038. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in spec 040 (scan a sitting into a location). Conflicts resolved by keeping both sides: collection-and-lots drops both resolved open items (#56 and #64) and lists both specs in Sources and the Distillation log; data-portability lists both specs; spec 032's Superseded-by cell names 040 and 038. db/schema.rb regenerated by migrating (accounts.currency and scanner_sittings' location). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
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 038 (v0.2.0) per its plan: each account stores the currency its prices paid are shown in.
COLLECTOR_CURRENCYbecomes only the currency a new account starts with. A collector changes theirs on a Currency page linked from More and the account menu. The export writes the account's currency (format version 3 unchanged), and applying an import brings the file's currency in.What each phase did
Phase 1, the data layer.
accounts.currencyisNOT NULL, defaultUSD, added without a DDL transaction so its rollback copies the table with foreign keys off.COLLECTOR_CURRENCYto every account. It checks the value against an inline list of the 20 codes, writes it with one boundUPDATE, and raises on an unsupported code.Accountdefaults the column tox.default_currency, validates it ("Choose one of the listed currencies.") and adds#currency_unit.accounts.currency.accounts"Exported" in its Coverage table and updates its prose. No schema, example or version changed.Phase 2, every reader moves.
lot_price(card page, collection table, and throughlot_linethe Move copies and location-refusal pages) and the lot form's label and prefix readCurrent.account.currency_unit.import_lot_linetakescurrency:: the account's for a removed lot, the file's for a problem row.unsupported_currency, replacingother_currency.currency_change, which counts against "nothing to change". Its two codes join the fingerprint only when there is a change, so other plans keep their old fingerprint.replanreloads the account inside the apply's transaction, and the apply writes the currency first in that transaction.x.currencyis removed.spec/currency_reads_spec.rbfails on any read ofx.currencyorx.default_currencyoutsideAccountand the initializer.Phase 3, the Currency page.
/currency/edithas one native select of the 20 codes (EUR — €), with a hint saying that changing converts nothing and how many lots have a price.PATCH /currencyaccepts onlyaccount[currency]viaparams.expect. It redirects to More (303) with "Prices are now shown in EUR.", or re-renders 422 with a field error.Phase 4, upgrade, backup and documents.
script/upgrade/seeds/next.rbputs Grace in EUR (AC-4.7).compose.yamlandconfig/deploy.ymlcomments.Rulings and deviations (each also in its commit body)
other_currencyalone would preview a EUR file as "nothing to change".c-field__hint, tied byaria-describedby, as the import page's is. There is no new CSS or pattern doc;MorePage.mdgains the Currency row.Collector::Currency.fetch!takessetting:, so a bad stored code blamesaccounts.currency, not the environment variable (review m2)..field_with_errorswrapper narrowed the select. This was found in the screenshots and fixed tests first (lesson indocs/lessons/).68f9bee) set the round trip's account currency between phases 1 and 2b; 2b replaced it.a684f56–68f9bee) carryCo-Authored-By: Claude Sonnet 5.5by mistake. The session runs on Opus 5.5, and every later commit carries the Opus trailer. They are not rewritten (no force-push).Tests
other_currency).bin/cipassed on5847f25: 2471 examples, 0 failures, 1 pending.bin/cionf6bd0f7passed on the second attempt. The first ran 2474 examples, and its one failure was the known cross-worktree harness tmpdir flake (Flaky: Upgrade::Harness "removes its temporary directory" races other suites through the shared tmpdir #178).Implementation review
SPEC-ALIGNED WITH MINOR GAPS (
sdd-reviewMode B, read-only, Opus). Nothing was blocking. Every AC was met; AC-4.6 is met by CI's upgrade proof only, and AC-3.4 by the unit test plus reading.verification.mdwas missing. It is written.fetch!'s error naming the environment variable: fixed.xyz: now tested.Gemfile.lockarm64 noise: left uncommitted.Details per AC are in
verification.md.Screenshots
Captured in headless Firefox against this worktree's dev server, seeded by a scratch script with one collector, four printings and three priced lots. The tiles have no card art.
Walkthrough: the table in
$, More → Currency → EUR → Save, the notice, the table in€, the lot form(EUR)/€, and an import preview of a USD file ("Your prices would be shown in USD instead of EUR", removed lots in€).A refused code (422), and the import preview in full:
For the maintainer to check by hand
bin/upgrade-smoke, on both architectures, must stay green.spec/backup_upgrade_spec.rbrolls backSTEP=1, which is now the empty data step, so it no longer exercises a schema rollback. The migration spec covers the column's rollback. ConsiderSTEP=2there.CHF — CHF,kr). Those repeats are accepted by decision 8.Closes #64
🤖 Generated with Claude Code