Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
854aa99
docs: add domain glossary with account scope decision
claude Sep 28, 2026
2b82eb6
docs: ADR 0001 — Firebase Auth and Firestore, no own server
claude Sep 28, 2026
9fe630e
docs: ADR 0002 — guests use Firebase anonymous sign-in
claude Sep 28, 2026
882c959
Merge remote-tracking branch 'origin/main' into docs/simplify-stack
claude Sep 29, 2026
47e09e4
docs: match glossary and ADRs to the grill-with-docs formats
claude Sep 29, 2026
05dadd6
docs: ADR 0001 — host the static app on Firebase Hosting
claude Sep 29, 2026
58284ee
docs: submitted yields go public only after review
claude Sep 29, 2026
628d952
docs: editing an approved yield resubmits it for review
claude Sep 29, 2026
7fe0fad
docs: ADR 0003 — five pages; inventory is a separate tool
claude Sep 29, 2026
8d25faf
docs: rejected yields return to private with an optional review note
claude Sep 29, 2026
09dcc65
docs: ADR 0004 — community dataset is CC BY 4.0, download now
claude Sep 29, 2026
7e6f8e9
docs: ADR 0004 — legacy shared yields need owner consent to be approved
claude Sep 29, 2026
c5a797d
docs: cover existing-account sign-in, reviewer rights and the address…
claude Sep 29, 2026
0f98d9c
docs: guest-data handoff, display-name-only attribution
claude Sep 29, 2026
27d36a8
docs: freeze writes on the old API before the Neon copy
claude Sep 29, 2026
9e5b663
docs: rewrap ADR 0001 cutover paragraph
claude Sep 29, 2026
c22070a
docs: cover offline guests, public fields and last-write-wins
claude Sep 29, 2026
bf5cd82
docs: cover legacy logins, starting form and public copy removal
claude Sep 29, 2026
a59f12b
docs: map legacy accounts explicitly and keep the offline cache
claude Sep 29, 2026
685f208
docs: read-only old client, anonymous write limits, CSV formulas
claude Sep 29, 2026
051dfc8
docs: offline handoff file, sign-out cache, private source, quotas
claude Sep 29, 2026
111f20f
docs: import columns, display name updates, zero yields, old address
claude Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Local Catch

A yield and cost calculator for fishermen and fishmongers. It turns what a fish
costs whole into what each finished product really costs, and lets people keep
and share the yields they measure themselves.

## Language

### Yields and costs

**Conversion**:
Going from one form of a fish to another, written `From → To` (e.g.
`Round → Skinless Fillet`).
_Avoid_: cut, transformation

**Yield**:
The share of weight kept by a conversion, as a percentage above 0 and up to 100.
Comment thread
paccloud marked this conversation as resolved.
_Avoid_: recovery rate, fraction (0.42)

**Reference yield**:
A published yield from the MAB-37 research that ships with the app.
_Avoid_: default yield, static data

**Custom yield**:
A yield a person measured from their own processing. Private unless approved
into the community dataset.
_Avoid_: user data, my data

**Saved calculation**:
A cost or weight calculation a person chose to keep.
_Avoid_: calc, saved calc

### People and the community dataset

**Account**:
An optional sign-in that exists for two reasons only: keeping a person's custom
yields and saved calculations across devices, and submitting custom yields to
the community dataset. A reviewer's account can also review submitted yields.
_Avoid_: user, profile

**Guest**:
Someone using the app without signing in. A guest can save, but loses what they
saved if they clear their browser data, and cannot submit yields.
_Avoid_: anonymous user

**Submitted yield**:
A custom yield its owner has asked to add to the community dataset. It stays
private until a reviewer approves it. A rejected yield goes back to private,
where its owner can fix it and submit it again. Editing an approved yield takes
it out of the dataset and submits it again.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
paccloud marked this conversation as resolved.
_Avoid_: shared yield, published yield, contribution

**Review note**:
An optional message from the reviewer to a yield's owner saying why it was
rejected. Only the owner sees it.
_Avoid_: rejection reason, feedback

**Reviewer**:
An account that has been granted the right to approve or reject submitted
yields. Being signed in is not enough.
_Avoid_: admin, moderator

**Community dataset**:
All approved yields, readable and downloadable by anyone under CC BY 4.0,
attributed only by display name or as "Anonymous".
_Avoid_: community pool, public data

**Display name**:
The only public identity a person has. Optional, chosen by them, never their
email.
_Avoid_: username, contributor name

_Dropped concepts_: contributor profile (bio/organization pages) and publishing
saved calculations publicly. Neither is one of an account's two jobs.
79 changes: 79 additions & 0 deletions docs/adr/0001-firebase-auth-and-firestore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
status: accepted
---

# Firebase Auth, Firestore and Firebase Hosting, with no server of our own

The app ran two backends (Express + SQLite locally, Vercel functions + Neon in
production), a shared handler layer, a custom offline sync engine and two
sign-in systems, for about two accounts whose only jobs are keeping custom
yields and saved calculations across devices and submitting custom yields. We
chose Firebase Auth plus Cloud Firestore, reached directly from the browser:
security rules decide who reads and writes what, and Firestore's persistent
offline cache (so offline edits survive a reload) replaces our sync engine. The
Comment thread
paccloud marked this conversation as resolved.
static app is served from Firebase Hosting, so the whole app lives with one
provider. Firebase Auth was already in use, so no one signs up again.

## Considered Options

- **Supabase** (Postgres, auth, row-level security): about as simple, but free
projects pause after a week idle, and it would change the sign-in system a
third time.
- **Keep Neon and delete the Express/SQLite backend** (the direction in
`AUDIT_REPORT.md` item 2.1): smallest change, but we'd still own API code,
token verification and a sync engine.
- **Firebase SQL Connect** (proved in `dataconnect/`): needs a paid Cloud SQL
instance; more than this app needs.

## Consequences

- `api/`, `server/`, `shared/`, SQLite, Neon, the legacy password login and the
custom sync layer go away, and so does the rule to change both backends.
Comment thread
paccloud marked this conversation as resolved.
- Excel/CSV import parses in the browser. The import and export format gains
separate starting-form and finished-product columns; a row without a starting
form imports with it blank, like the Neon copy.
- Security rules cannot hide single fields of a readable document, so approved
yields are copied into a separate public collection holding only the public
fields (species, conversion, yield, display name). The owner's user id, the
free-text source note, submission details and review note stay in the owner's
private document. The reviewer's approval writes both in one batch. Changing
the display name rewrites the name on all of that owner's public copies in one
batch. Editing or deleting an approved yield deletes its public copy in the
same batch, and the rules let an owner delete the public copy of their own
yield.
- Firestore's persistent cache keeps private data in the browser after sign-out,
so signing out first sends pending writes (or, if offline, asks whether to
wait or discard them) and then clears the local cache.
- Our sync engine's revision check goes away: when the same record is edited on
two devices while one is offline, the later write wins. We accept this because
the accounts belong to one person; the conflict screen is dropped.
- The community dataset can be downloaded as CSV/JSON from the Community page. A
stable file address for other tools comes with the versioned releases in
ADR 0004.
- The Firebase SDK makes the app download larger than today's REST calls.
- Local development and CI use the Firebase emulators.
- The address moves from `*.vercel.app` to `*.web.app`. Browser storage belongs
to an address, so a redirect cannot carry a guest's saved data or unsynced
edits across. Before the redirect is switched on, the last version on the old
address asks guests to sign in so their data reaches their account, and
finishes syncing signed-in users. A later release makes the old app read-only
too: it stops accepting new edits, still pushes what is pending, and shows a
banner about the move. An old copy that was offline through all this can't be
seen from the server, so the read-only app also lets anyone save their unsent
changes as a file, which the new app's import accepts. After a waiting period
Comment thread
paccloud marked this conversation as resolved.
for devices to come online, the old API stops accepting writes (it answers
them with an error), so nothing changes in Neon while it is copied. The copy
uses a written list that maps each Neon account holding data to its Firebase
user id, checked by hand (there are about two accounts); it stops if an
account with data is missing from the list, appears twice, or two accounts map
to one Firebase user. Neon custom yields record only the finished product, not
the starting form, so the copy leaves the starting form blank for the owner to
fill in. The dry run lists any yield that is not above 0 and up to 100; the
owner fixes those before the freeze, and the copy refuses to run while any
remain. After the copy has been checked, the old address keeps serving the
read-only app for six months, so an installed copy that comes back online
still gets the save-to-file option and a link to the new address; only then is
Vercel reduced to a redirect. Bookmarks follow the redirect; installed copies
of the app need to be installed again from the new address. PR previews come
from Firebase preview channels.
- Supersedes `docs/AUTH_MIGRATION_ROADMAP.md` (Better Auth + Cloudflare).
43 changes: 43 additions & 0 deletions docs/adr/0002-guests-use-anonymous-sign-in.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
status: accepted
---

# Guests use Firebase anonymous sign-in

People should be able to save before deciding to sign up. Instead of keeping
guest data in the browser and copying it into an account with our own code, a
guest's first save signs them in anonymously; their data lives in Firestore
Comment thread
paccloud marked this conversation as resolved.
under the same rules as any account, and signing in with Google or email later
Comment thread
paccloud marked this conversation as resolved.
Comment thread
paccloud marked this conversation as resolved.
links to that same user so nothing is copied. People who only use the calculator
never get an account. Anonymous sign-in needs the network, so a save made
offline before the guest has a user is kept in the browser and written to
Firestore once sign-in succeeds.

## Consequences

- Guest data saved by the current app (in the browser, not Firestore) is not
read by the new app. It moves over the existing way before the switch: the
current app asks guests to sign in, its adoption code moves their records into
the account, and they reach Firestore with the one-time copy from Neon (see
ADR 0001). The old guest storage and adoption code are removed only after
that.

- If the guest signs in with a Google account or email that already belongs to
an account, Firebase cannot link it to the anonymous user. In that case the
app reads the guest's custom yields and saved calculations while still signed
in as the guest, signs in to the existing account, and writes them there. This
copy is the one piece of guest-transfer code we keep.

- A guest who clears browser data loses what they saved, as today.
- Unused anonymous accounts pile up and may need occasional cleanup. Deleting an
Auth user does not delete its Firestore documents, so cleanup removes the
user's custom yields and saved calculations along with the user.
- Anonymous users can write to Firestore without a verified account, so the app
uses App Check, and the rules cap the size and number of each user's records.
Those caps are per user, so the project-wide limits are App Check, Firebase
Auth's limit on new anonymous sign-ups from one IP address (kept low), and the
free plan's hard quotas, which stop writes instead of billing. A bot could
still exhaust the free quota and block saves until it resets; we accept that
for an app of this size and revisit it if it happens.
- Only a non-anonymous account can submit yields to the community dataset, so
every submitted yield has a real owner.
13 changes: 13 additions & 0 deletions docs/adr/0003-five-pages-inventory-is-separate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
status: accepted
---

# Five pages; inventory is a separate tool

The app is only the calculator and what an account needs around it:
Calculator, My data (custom yields, saved calculations, import, submit),
Community (the community dataset and where reference yields come from), About,
and Review (reviewers only). Sign-in is a panel, not a page. Contributor
profiles, the roadmap page and the "Inventory — coming soon" placeholder are
removed. Inventory belongs in a separate tool in the harvester suite, not in
this app, so it should not be added back here.
26 changes: 26 additions & 0 deletions docs/adr/0004-community-dataset-cc-by.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
status: accepted
---

# Community dataset is CC BY 4.0; download now, versioned releases later

Submitting a yield means its owner agrees that, once approved, it is released
under CC BY 4.0 with attribution by display name or "Anonymous"; the submit
dialog says so. No other identifying field (such as the organization from the
old contributor profiles) is published or exported. The code stays MIT. A
license on contributed data cannot be narrowed after the fact, so this is set
before any submissions under the new flow. For now the Community page offers a
CSV/JSON download built from the approved yields. Every CSV export, including
the later scheduled one, neutralizes cells that start with `=`, `+`, `-` or `@`
so a spreadsheet does not run them as formulas. Scheduled, versioned releases (a
GitHub Action exporting a numbered file at a stable address other tools can
fetch) will be added once there are enough approved yields to be worth citing.
Until then the dataset has no fixed download address.

## Consequences

- Yields shared before this consent existed enter the community dataset only
with their owner's agreement to CC BY 4.0: the one-time copy from Neon marks
them approved only for owners who have agreed, and leaves the rest private for
their owners to submit again. A copied yield whose starting form is still
blank stays private until its owner fills it in and submits it.
Loading