Skip to content

Give windows their own addresses, keep preferences and contain crashes (app review batch 5, getting around) - #31

Merged
EmotiveImpact merged 11 commits into
mainfrom
claude/app-review-navigation
Oct 12, 2026
Merged

EmotiveImpact merged 11 commits into
mainfrom
claude/app-review-navigation

Conversation

@EmotiveImpact

Copy link
Copy Markdown
Owner

Before: anything opened on top of a screen (an expense, a note, the breakdown, a fund) had no address of its own, so Back left the screen instead of closing it, a reload closed it and a link to it could not be sent to anyone. Preferences lived in the tab's session storage, so closing the tab lost the commune you were using, dark mode, photos and payment usernames. A drawing error anywhere blanked the whole app, and nobody found out. An invitation opened by an account with a different email address said only "Invitation unavailable", with no way on. Billing and Analytics sat in the navigation without doing anything, the Currency preference changed nothing, and adding the app to a phone's home screen gave a screenshot for an icon and no name.

After: every window has its own address, such as /expenses/<id> or /settle/breakdown, and commune screens name their commune with ?commune=<id>. Back closes a window, Forward and a reload open it again, a link opens it for another member, and a page opened while signed out opens after signing in. Preferences stay in this browser, the theme can follow the device, and a new tab reopens the last commune. A crash stays inside the screen or window it happened in, with Try again and Go to Home, and is reported with personal details removed. The invitation page names the address it was sent to, masked, and offers to switch account. Billing, Analytics and Currency are gone until they do something, and the app has a home-screen name and icons.

This is the second of three pull requests for batch 5 of the app review fixes. It is built on #30 (accessibility), so review that first; the diff here shows only this part.

What changed, by finding

Finding Change
15 Windows and records take their address from the URL (apps/web/src/nav.ts): an expense at /expenses/<id>, a note's editor at /notes/<id>/edit, the breakdown at /settle/breakdown, a fund at /funds/<id> and Profile at /settings/profile, and commune screens carry ?commune=<id>. Closing a window returns to the entry it was opened from; a saved or cancelled form is replaced, so Back never reopens it, and Back over typed text asks first. An address for a commune this account is not in says "This commune isn’t available to you." and never shows another commune's records; an address Commune does not have shows "This page isn’t here." Each page has a short title, such as "Expenses · 14 Park Street · Commune". A page opened while signed out, including through Google sign-in, opens after signing in; only a path on this site is kept, never a full or protocol-relative URL.
16 Preferences live in local storage, through wrappers that never throw when storage is full, blocked or private. Theme, sidebar and notices belong to the browser; the last commune, payment usernames and photos belong to the account and are removed at sign-out. A tab's old session copy moves across once. A new tab reopens the last commune while the account is still a member of it. The Dark mode switch becomes a Theme choice of Match this device (the default), Light or Dark, and a change shows in other open tabs.
21 Error boundaries surround the app, each screen and each window. A screen that fails shows a card in its place with the navigation still working, and a window that fails shows the card inside the window; each offers Try again and Go to Home. Drawing errors and errors in event handlers and promises (not refused requests or dropped connections) are reported once per page load, and about one page load in ten also sends three loading and responsiveness figures from the browser's own PerformanceObserver. Reports go only for a signed-in person, to report_client_event (migration 0038), which keeps them where no client can read them. Record ids, email addresses and queries are removed by the app and again by the database, and each account may send 20 an hour and 100 a day.
31 An invitation opened by an account with a different email address says which address it was sent to, masked ("e•••@g•••.com"), and which account is signed in. It offers "Sign out and use another account", which comes back to the invitation after signing in, and "Continue to Commune". The hint comes from invite_address_hint (0038) only to a signed-in account holding the invitation's token, only while the invitation could still be accepted, and only when the address is not the account's own.
48 Billing and Analytics leave the navigation, and their old addresses go to Home. Analytics was the only screen that showed "Next due"; the figure is kept for when Analytics returns, and now reads "Overdue since 4 Sept" once the date has passed.
51 The Currency preference goes. Each commune keeps its own currency, so the choice changed nothing on any screen.
56 A web app manifest names the app Commune (en-GB, standalone, with the navy theme colour and a cream splash screen), with 192 and 512 pixel icons, a maskable icon and a 180 pixel Apple touch icon, rendered from public/logo-mark.svg by scripts/render-icons.mjs. The page language is en-GB and its description is in the app's own words, without "Hub OS". The calendar stylesheet is bundled with the app's styles instead of loading on its own before the first paint.

The privacy page now says what stays in the browser (the last commune, payment usernames and photos until sign-out, and display settings for whoever uses the browser) and has a new "Error reports" section: what a report holds, why it is linked to the account, where it is kept and that it is deleted after 30 days.

Releasing

Nothing hosted was touched. Migration 0038 is local only and follows 0037. Apply it and refresh the Data API schema cache before deploying this client; against an older database the client still works but shows no address hint, and its reports are dropped. The worker's scheduled run deletes reports older than 30 days through prune_delivery_records(), which 0038 replaces in place, so the worker needs no redeploy. A report names its release from Vercel's commit SHA at build time, so Vercel's "Automatically expose System Environment Variables" setting needs to be on; otherwise reports say "local". To read reports, use an owner connection: SELECT kind, release, route, browser, message, created_at FROM commune_private.client_reports ORDER BY created_at DESC.

Needs the owner

  • The privacy page changes join the legal wording you are already reviewing for finding 13.
  • Error reports: 20 an hour and 100 a day per account, kept for 30 days and linked to the account only to enforce those limits. Say if you want other numbers.
  • Three choices you may want to revisit. The page asked for is not kept after a voluntary sign-out or an ended session, so the next person on a shared device lands on Home. Back closes a fund, but a pledge, contribution or edit form opened from it stays open until finished. Recurring bills, members and saved splits are edited in place on their screens, so they have no address of their own, and a search result highlights them there.

How it was checked

On the final branch:

  • 559 web tests in 50 files, including the new addresses (30), error-recovery (12), preferences (14), invitation-hint (6), home-screen (4) and placeholder-screens (4). Every new test failed before its fix.
  • 26 database tests through run-local.sh, including the new client_reports.sql and invite_address_hint.sql; 2 more need a Neon branch.
  • 20 local API suites on a disposable database and port, including the new client-reports.mjs and invitation-hint.mjs, and the reminder worker's unit tests and 4 database suites.
  • Typechecks for the web app, local API and worker, a production build, and the secret scan.

In Chromium, on a disposable database with three communes, on top of the Home and notices branch: a signed-out visit to Settings returns there after sign-in, and Back then leaves the app instead of showing the sign-in form; an expense opens at its own address, Back closes it, Forward and a reload open it again; Back over typed text asks before discarding and Keep editing keeps the text; Theme stays Dark after a reload; an unknown address and a commune the account is not in each say so; /billing goes to Home; and the manifest names Commune with 3 icons. No page errors.

How

The open window, the record it shows and the active commune all come from the address, so nothing else records what is open. Opening a window adds a history entry that remembers where it opened from, and leaving it goes back to that entry when it is the one before, or replaces it otherwise. Preferences are written only while the account that owns them is still the one signed in, so a sign-out cannot write them back and one account's preferences cannot land on another's. docs/HANDOFF.md, infra/neon/README.md and infra/local/README.md record the addresses, preferences, reports and migration 0038.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS


Generated by Claude Code

claude added 11 commits October 9, 2026 18:08
Each commune keeps its own currency, so the Settings choice changed
nothing on any screen. The preference, its stored value and its state
go; the Profile window no longer lists it among the settings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
"Next due" took the earliest due date of any open bill, so a rent
payment a month late still read as "4 Sept". It now reads "Overdue
since 4 Sept" once the date has passed.

Billing only said it was not connected, and Analytics was the activity
list with four figures on top. Both leave the navigation, and their old
addresses go to Home, until they do something.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
Preferences lived in the tab's session storage, so closing the tab lost
the commune you were using, dark mode, photos and payment hints, and a
new tab opened the newest commune instead.

Device preferences (theme, sidebar, notices) and each account's own
(last commune, photos, payment hints) now live in local storage, read
and written through wrappers that never throw when the store is full,
blocked or private. A tab that still holds the old session copy moves
it across once. A new tab reopens the last commune while the account is
still a member of it, and falls back to the newest otherwise.

The dark mode switch becomes a Theme choice: Match this device (the
default, following prefers-color-scheme), Light or Dark. A change in
one tab shows in the others. Signing out still removes everything kept
for the account and leaves only the device preferences.

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

Every window now has an address that continues its screen's (an expense is
/expenses/<id>, a note's editor /notes/<id>/edit, the breakdown
/settle/breakdown, a fund /funds/<id>), and a screen that shows one commune
names it with ?commune=<id>. The address is the only record of what is open,
so Back closes a window, Forward and a reload open it again and a link opens
it for another member. Closing a window returns to the entry it was opened
from when there is one; leaving a form replaces it, so Back never reopens a
saved or cancelled form. Back over typed text asks first, as Close does.

An address for a commune this account is not in says so instead of showing
another commune, and an address Commune does not have shows a not-found page
in the sign-in layout. Each page sets a short title, such as
"Expenses · Flat 4 · Commune".

A page opened while signed out (a reminder email's link to Settings, a shared
expense, an invitation) opens after signing in or creating an account,
including through Google. Only a path on this site is kept: never an
absolute or protocol-relative URL, a backslash or a control character.

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

An invitation opened by an account with a different email address said only
"Invitation unavailable", with no way on. The page now says which address it
was sent to, masked ("e•••@g•••.com"), and which account is signed in, and
offers "Sign out and use another account", which comes back to the
invitation once signed in again, and "Continue to Commune". A signed-in
account whose invitation is unavailable for any other reason can also
continue to Commune.

The hint comes from the database (public.invite_address_hint, migration
0038) only to a signed-in account holding the invitation's token, only
while the invitation could still be accepted, and only when the address is
not the account's own. The page now reads its token through the router,
like every other page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
React removes everything below a drawing error that nothing catches, so one
error anywhere left a blank page. Error boundaries now surround the app,
each screen and each window. A screen that fails shows a card in its place
with the navigation still working; a window that fails shows the card
inside the window, over the untouched screen; and if the app itself fails, a
whole-page card in the connection card's style. Each says what happened in
plain words and offers Try again and Go to Home.

Nobody found out about failures either. The boundaries, and errors in event
handlers and promises (not refused requests or dropped connections), are
reported once per page load, and about one page load in ten reports its
largest contentful paint, interaction to next paint and layout shift from
the browser's own PerformanceObserver when it is first hidden. Reports go
only for a signed-in person, through the local API (POST
/api/client-reports) or the Data API, to public.report_client_event
(migration 0038), which keeps them in commune_private.client_reports where
no client can read them: kind, release (the commit Vercel built), the page
as a pattern such as /expenses/:id, the browser family, and an error's
message and first ten stack frames, with record ids, email addresses and
queries taken out by the app and again by the database. An account may send
20 reports an hour and 100 a day; more are dropped rather than refused. The
worker's scheduled run deletes reports after 30 days.

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

Adding the app to a phone's home screen gave a screenshot for an icon and
no name. A web app manifest now names it Commune (en-GB, standalone, on the
navy theme colour, with the cream canvas behind its splash screen), with
icons at 192 and 512 pixels, a maskable one that keeps the mark inside every
launcher's mask, and a 180-pixel Apple touch icon. The icons were rendered
from public/logo-mark.svg by scripts/render-icons.mjs with headless
Chromium, which is kept for when the mark changes. The hosted policy
already allows the manifest and icons (default-src and img-src 'self').
apple-mobile-web-app-title keeps the home-screen name Commune now that each
page has its own title.

The page language is en-GB and its description is in the app's own words,
without "Hub OS". The calendar stylesheet is bundled with the app's styles,
ahead of them as its link was, rather than loaded on its own before the
first paint; the design reference links the same file from src.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
Remembering the commune a tab shows moved into an effect, which can run
just after sign-out has removed the account's preferences, and so wrote
them back. Saving now checks that the account is still the one signed in
here, which also stops one account's preferences landing on another's
after a switch in the same tab.

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

What stays in the browser now matches where preferences live: the last
commune, payment usernames and photos for the account until it signs out
there, and display settings for whoever uses the browser. A new Error reports
section says what a report holds, how often speed figures go with it, why it
is linked to the account, where it is kept and that it is deleted after 30
days. Both are part of the legal wording waiting for the owner's approval.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
- infra/neon/README.md: 0038 joins the local-only range and the release
  steps, with what it adds, the order to release it in, and the Vercel
  setting a report's release needs.
- infra/local/README.md: 0038 in the local setup order.
- DESIGN-LANGUAGE.md: the calendar stylesheet now lives in src/styles and is
  bundled.
- HANDOFF.md: a Start here entry for addresses, preferences, error recovery
  and reports, the invitation hint, the hidden placeholders and the icons.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6P7aTcMBccooYYr4xYnVS
The page sits in the sign-in layout, where the heading and text are
centred, but its button used the app's row of buttons and sat to the
left with underlined text. It now uses the same full-width button as
"Back to sign in" on the password reset screens.

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 6:33pm 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 Web app check, which builds the app, passes on this head (3481442). 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-accessibility to main October 12, 2026 02:01
@EmotiveImpact
EmotiveImpact marked this pull request as ready for review October 12, 2026 02:01
@EmotiveImpact
EmotiveImpact merged commit 40266f0 into main Oct 12, 2026
6 of 7 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:02:11.705318Z 3481442 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 — 34814425 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