feat(web): gate UI affordances on the permission matrix #70

Merged
nalum merged 1 commit from feat/web-affordances-from-matrix into main 2026-08-14 16:56:04 +00:00
Owner

First part of #61: the web now derives every create/manage affordance from the permission matrix instead of a coarse canEdit flag.

Removes canEdit = ADMIN || MEMBER from the auth context. Adds a permission store (web/src/auth/permissions.ts) that reads SystemService.GetPermissionMatrix (from #69), caches it in localStorage and hydrates synchronously so affordances never flicker, and exposes can(service, method) on the auth context. Every page now gates on the specific verb its control performs.

What this fixes and changes:

  • A child now sees the controls the matrix permits: add-job, add-event, and — after #67 — add/manage lists and meals.
  • The Meals page splits its old single flag into the library (MealService, child allowed) and the rota board (MealRotaService, member-only), so a child manages dishes but not the cook rota.
  • Jobs splits create (ItemService.Create, child allowed) from the manager tier (ItemService.Delete, member-only) — a child is a taker, per ADR-0014.
  • Rewards management stays member-and-up; a child never sees grant controls.
  • Per-item ownership checks (canEditJob = isManager || owner === me) are untouched.

Canon updated: AGENTS.md and docs/frontend-plan.html now say affordances mirror the matrix (absent only on a real denial), replacing the old "child sees creation affordances absent entirely" wording.

Still to come in #61: the event-edit flow (web) and the Android consumer + event edit.

Verified: make check passes (tsc, web build, Go tests, lingui — no new strings).

Part of #61.

🤖 Generated with Claude Code

First part of #61: the web now derives every create/manage affordance from the permission matrix instead of a coarse `canEdit` flag. Removes `canEdit = ADMIN || MEMBER` from the auth context. Adds a permission store (`web/src/auth/permissions.ts`) that reads `SystemService.GetPermissionMatrix` (from #69), caches it in localStorage and hydrates synchronously so affordances never flicker, and exposes `can(service, method)` on the auth context. Every page now gates on the specific verb its control performs. What this fixes and changes: - A child now sees the controls the matrix permits: add-job, add-event, and — after #67 — add/manage lists and meals. - The Meals page splits its old single flag into the **library** (`MealService`, child allowed) and the **rota board** (`MealRotaService`, member-only), so a child manages dishes but not the cook rota. - Jobs splits create (`ItemService.Create`, child allowed) from the manager tier (`ItemService.Delete`, member-only) — a child is a taker, per ADR-0014. - Rewards management stays member-and-up; a child never sees grant controls. - Per-item ownership checks (`canEditJob = isManager || owner === me`) are untouched. Canon updated: `AGENTS.md` and `docs/frontend-plan.html` now say affordances mirror the matrix (absent only on a real denial), replacing the old "child sees creation affordances absent entirely" wording. Still to come in #61: the event-edit flow (web) and the Android consumer + event edit. Verified: `make check` passes (tsc, web build, Go tests, lingui — no new strings). Part of #61. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(web): gate UI affordances on the permission matrix
All checks were successful
check / commits (pull_request) Successful in 5s
check / web (pull_request) Successful in 1m29s
check / go (pull_request) Successful in 2m31s
check / report (pull_request) Successful in 3s
8d9bfc4385
The web decided every create and manage control from one coarse flag,
canEdit = ADMIN || MEMBER, a client-side guess that hid controls a role
was actually permitted — a child saw no create buttons even for events,
jobs, lists and meals the server allows. Replace canEdit with a
permission store that reads the server's real matrix (GetPermissionMatrix,
cached and hydrated synchronously) and a can(service, method) check, so
every affordance mirrors what the interceptor enforces. The Meals page
splits its old single flag into the meal library (MealService) and the
rota board (MealRotaService), which have different rules. Part of #61.

Test report

Suite Tests Result Skipped
Unit 1330 ✅ pass 1
Integration 83 ✅ pass —

Coverage: 27.7%

Updated by the check workflow · commit 6432948912

<!-- ci-test-report --> ## Test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit | 1330 | ✅ pass | 1 | | Integration | 83 | ✅ pass | — | **Coverage:** 27.7% <sub>Updated by the check workflow · commit 6432948912aeef400a92e92d793aa8eb3810af0a</sub>
nalum force-pushed feat/web-affordances-from-matrix from 8d9bfc4385
All checks were successful
check / commits (pull_request) Successful in 5s
check / web (pull_request) Successful in 1m29s
check / go (pull_request) Successful in 2m31s
check / report (pull_request) Successful in 3s
to 6432948912
Some checks failed
check / commits (pull_request) Successful in 6s
android / build (pull_request) Successful in 6m16s
check / web (pull_request) Successful in 2m0s
check / go (pull_request) Successful in 2m54s
android / report (pull_request) Successful in 5s
check / report (pull_request) Successful in 4s
check / go (push) Has been cancelled
check / report (push) Has been cancelled
check / web (push) Has been cancelled
tag / tag (push) Has been cancelled
check / commits (push) Has been cancelled
2026-08-14 16:03:16 +00:00
Compare

Android test report

Suite Tests Result Skipped
Unit (debug) 31 ✅ pass 0

Updated by the android workflow · commit 6432948912

<!-- android-test-report --> ## Android test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit (debug) | 31 | ✅ pass | 0 | <sub>Updated by the android workflow · commit 6432948912aeef400a92e92d793aa8eb3810af0a</sub>
nalum changed target branch from feat/permission-matrix-rpc to main 2026-08-14 16:55:56 +00:00
nalum merged commit 6432948912 into main 2026-08-14 16:56:04 +00:00
nalum deleted branch feat/web-affordances-from-matrix 2026-08-14 16:56:04 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!70
No description provided.