Skip to content

Date, read and split money the same way everywhere (app review batch 3) - #26

Merged
EmotiveImpact merged 9 commits into
mainfrom
claude/app-review-batch-3
Oct 12, 2026
Merged

EmotiveImpact merged 9 commits into
mainfrom
claude/app-review-batch-3

Conversation

@EmotiveImpact

Copy link
Copy Markdown
Owner

Before: activity, "This month", statements and exports dated entries by their UTC day, so an expense entered at 9pm in New York showed the next day, and one entered just after midnight in London's summer time showed the day before. Amount fields refused "1,200", and a euro commune could not type "12,50". Add expense could show the spare penny on one person and save it on another. A commune whose only records were recurring bills or exact split templates could switch from pounds to dollars, turning a £90 bill into $90, and the settings screen and the database disagreed about when the currency was fixed. Statements could not be opened for someone who had left. After an attempt whose reply was lost, a later refusal in the money dialogs said nothing had been saved, which might not be true.

After: entries are dated by the member's own day, every money field reads amounts one way, the preview and the save agree on every penny, one rule fixes the currency and the screen says why, statements with former members open from the breakdown, and those dialogs say an earlier attempt may already have been saved and where to check. Each fix has a test that fails on the old code.

This is batch 3 of the app review fixes and is built on #25 (batch 2), so review that first; the diff here shows only this batch.

What changed, by finding

Finding Change
33 One helper dates a stored time by the member's own calendar day, used by activity, "This month", statements and the CSV export. In a statement a repayment keeps its own date but is never listed before the share it pays, nor a reversal before its repayment.
41 One reader for typed amounts, in packages/core/src/money.ts, used by every money field and exact split value. "1,200" and "1,200.50" work everywhere; "12,50" and "1.200,50" work in euro communes; elsewhere a decimal comma gets "Use a full stop for pence, like 12.50." A group of thousands never starts with 0, so "0,500" is refused rather than read as 500. The limit is the database's, 10,000,000,000.00, everywhere.
52 The spare penny goes to the largest remainder, and ties go by user id, so the same people get the same amounts whatever order they were ticked in. Add expense also previews the split between the same people it saves when someone leaves while the form is open.
40 Migration 0035: the currency is fixed once a commune has an expense, fund, recurring bill or exact split template, voided or archived ones included. update_commune_details enforces it and the snapshot reports what fixes it, which the settings screen explains.
54 The breakdown's statement picker lists people who have left, under "Former members", when there is anything between you.
Follow-ups The repayment, payment report, correction and void dialogs no longer say nothing was saved after an attempt that may have been.

Releasing

Nothing hosted was touched. Migration 0035 is local only and follows 0034; it does not change the release order. A client from before 0035 keeps working: its settings screen may still offer a currency change that the database now refuses with a clear message.

How it was checked

On the final branch: 21 SQL test files on fresh databases plus the lock order test, 15 local API suites on a disposable database and port, 414 web tests in 39 files (date tests run in New York and London time zones), the reminder worker's tests, typechecks for the web app, local API and worker, and a production build.

In a browser: "1,200" read as £1,200.00 and saved with an even split, "12,50" refused in a pound commune with the full stop message, "12,50" and "1.200,50" read in a euro commune, a commune whose only record is a recurring bill showing the currency locked and why, and a statement with someone who left after repaying their share.

How

The database stays the authority: commune_private.currency_locked_by decides the currency rule, the snapshot reports it, and the screen only displays it (with the same rule worked out from loaded records against a database without 0035). The commune row is locked while the details change, and adding any of those records waits for that lock, so the database rule cannot be raced. One gap remains: a form left open while an admin changes the currency of a commune with no money records yet saves its amount in the new currency, where it then shows with the new symbol. The patch to update_commune_details and the snapshot uses pg_get_functiondef and replace() and stops if the definition no longer matches.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS


Generated by Claude Code

claude added 9 commits October 9, 2026 15:16
… shares

Finding 33 from the app review.

Activity, statements, the expenses download and "This month" cut each
timestamp to its UTC date, so in a British summer anything entered between
midnight and 1am, and in New York anything after 8pm, was dated the wrong
day. They now share one helper, timestampDay, which gives the day where
the member is, and the existing timestamp formatter is built on it.

A statement could also list a repayment before the share it pays: when the
money arrived before the bill was entered, or when the share's UTC date
was the next day. A repayment now always follows its share, and a reversal
its repayment, and each keeps its own date.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
Finding 52 from the app review.

When an amount did not split evenly, allocateExpense gave the spare penny
to whoever came first in the list it was given. Add expense previewed the
split in the order people were ticked but saved it in member order, so
after unticking and ticking someone again the preview showed the penny on
one person and the save charged it to another.

Ties for a spare penny now go by user id inside allocateExpense, so the
same people always get the same amounts whatever order they are listed
in, and every preview matches what is saved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
Every money field, and every exact split amount, now reads what was typed
through one parser in packages/core. "1,200" and "1,200.50" work in every
commune; "12,50" works in a euro commune, and elsewhere the field says to
use a full stop for pence. The Add expense window had its own parser with
no upper limit, so it could send amounts the database refuses; every field
now stops at the database's 10,000,000,000.00 and says so, and every money
field is long enough to type it. Exact split templates are stored as plain
figures however they were typed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The settings screen counted only live expenses and funds, so with every
expense voided it offered a currency change the database then refused.
Neither counted recurring bills or exact split templates, so a commune with
only those could switch from pounds to dollars and relabel their amounts.

Migration 0035 puts the rule in one place: the currency is fixed once the
commune has an expense, fund, recurring bill or exact split template,
voided or archived ones included. update_commune_details enforces it, and
the snapshot reports what fixes it, so the settings screen follows the
same rule and says why the currency is locked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The statement picker listed only current members, so there was no way to
see the record with someone who had left. People who have left and share
anything with you are now listed under their own Former members heading,
with the same statement, balance and CSV download.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
When an attempt to record a repayment, or to tell the payer about one, got
no reply, a later refusal still said "Nothing was recorded" or "Nothing was
reported", although the first attempt may have gone through. Both dialogs
now say an earlier attempt may already have been saved and where to check,
as the other money forms do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
…ure attempt

Once a correction or void may have been saved by an attempt whose reply
was lost, a later refusal now says the earlier attempt may already have
saved it and where to check, as the repayment and payment report dialogs
do, instead of "Nothing was changed."

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The local list and the Neon release order now run to 0035, and the
handoff summarises the money accuracy fixes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
A first group of thousands starting with 0 was read as thousands, so
"0,500", meant as 0.50, became 500.00 without a word, in every
currency. A group of thousands now starts with 1 to 9, so it is
refused with the field's example.

Add expense previewed the split between everyone ticked, including
someone who had left since the form opened, but saved it between the
current members only: three people at 9.00 showed 3.00 each and saved
4.50 each. The preview now uses the same people as the save.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
@vercel

vercel Bot commented Oct 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
commune-web Error Error Oct 9, 2026 4:45pm UTC

Copy link
Copy Markdown
Owner Author

The Vercel preview for commune-web shows Error here, as it has on every branch since 6 October. It is the project or account issue explained on #24 (app review finding 45), not this diff: the production build of this branch passes locally (vite build on 42fe7ea). It needs someone with the Vercel login to read a failed deployment's log.


Generated by Claude Code

@EmotiveImpact
EmotiveImpact changed the base branch from claude/app-review-batch-2 to main October 12, 2026 02:00
@EmotiveImpact
EmotiveImpact marked this pull request as ready for review October 12, 2026 02:00
@EmotiveImpact
EmotiveImpact merged commit 62dcdd7 into main Oct 12, 2026
3 of 4 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T02:01:29.051712Z 42fe7ea Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

This branch had an error being deployed

1 failed deployment
Preview — 42fe7ea7 Deployed Oct 9, 2026 by vercel[bot]
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.

2 participants