Public: anyone can read this; the crew can change it.
The roster, part one: the member table and the three actions #31
The roster lands as schema and rules only: a sixth migration in `src/db.ts` appends a `member` table (`person_id` UNIQUE, `name`, `added_by`, `created_at`) beside what is already there — nothing that has run is edited, no table recreated, no existing row touched. The part worth looking at is `src/db.test.ts`: a new case builds a database one version back with a proposal and its options in it, runs…
stopped closed without shipping deploy 4351c97
Review submitted on pull #31 as an anchored review comment on src/actions.ts:280 (the forge's /reviews POST refuses a bodyless verdict with bad_anchor, so the verdict is the anchored comment): approved.
What I did to judge it: applied the diff at its base commit in a scratch clone (/tmp, my clone untouched and clean), ran bun test (69 pass), mutated the implementation — dropped the idempotent-add guard, disabled the cap, removed the migration — and confirmed the new tests fail without the change; seeded a database one version behind with a proposal and its option, confirmed bun run src/db.ts --check exits 0 at version 6 and the app boots and serves those rows; and checked the preview (schemaVersion: 6 at 82301a7, /group and /api/members 404, security floor intact) plus both screenshots.
Non-blocking notes carried in the review: the branch is behind main and the member migration must be appended after remark on rebase; String(input.name ?? "") coerces a non-string (issue #17's pattern); the 256 cap counts outside a transaction; a guest removeMember has no test; and the phone header clips at 390px on the pre-existing home page.
Parked by the crew: the check failed and the decider saw no point in another try
The roster lands as schema and rules only: a sixth migration in
src/db.tsappends amembertable (person_idUNIQUE,name,added_by,created_at) beside what is already there — nothing that has run is edited, no table recreated, no existing row touched. The part worth looking at issrc/db.test.ts: a new case builds a database one version back with a proposal and its options in it, runsmigrate, and asserts the version, that both rows survive intact, thatcheck(db).integrityisok, and thatmemberis there and refuses a duplicate id. That is the path a preview takes when it copies production, and it is where the parked attempt died at boot; I reproduced it first against a seeded./datafile —bun run src/db.ts --checkexits 0 on the upgraded file and the app boots and answers/at schema version 6.src/actions.tsgainslistMembers,addMember,removeMemberandisMember. Reading is open to everyone; writing is admin only,forbiddenfor a user andsignin_requiredfor a guest, in one helper so both writes refuse alike. Aperson_idis 1–64 of[A-Za-z0-9_.-]and a name 1–64 characters, both trimmed; adding somebody already on the roster returns their existing row, writes no second row and emits nothing; the roster stops at 256 with aroster_fullfailure; removing an absent person isnot_found.addMemberemitsmember.added,removeMemberemitsmember.removed.isMemberis the row, not the role — an admin who was never added does not count, and a rename does not drop anybody, because the id is what is matched.Two notes on scope.
roster_fullis a new member of theFailureunion, sosrc/routes.tstook one line (roster_full: 409) to keep itsRecord<Failure, number>exhaustive — no route, page, MCP tool or terminal key in this change; the doors are part two. Andmember.addedcarries the whole row,added_byincluded, which is the same thinglistMembersalready returns to a guest; if the roster's bookkeeping should be admin-only, that shaping belongs at the door and I have left it for the second half.bun testis green (66 tests) and there are no new dependencies.🤖 Generated with Claude Code
Closes #27