Sharing UI: invite by email, roles, read-only viewer mode (#17) #36

Merged
steve merged 2 commits from phase-6-sharing-ui into main 2026-07-19 04:14:30 +00:00
2 Commits
Author SHA1 Message Date
steveandClaude Opus 4.8 85c355ce4d Address Gadfly review on #17: share-response bug + error surfacing
Build image / build-and-push (push) Successful in 17s
Critical:
- useAddShare/useUpdateShareRole no longer parse the response with the list's
  Share schema — the backend returns a bare GardenShare (no email/displayName),
  so every successful invite/role-change was throwing and showing a false error.
  onSuccess refetches the list, which has the full data.
- ShareGardenModal surfaces role-change / remove failures (mutate onError → the
  dialog's Alert) instead of swallowing them; Modal busy now covers all three
  pending states.

Robustness / defense in depth:
- Inspector/PlopInspector patch() early-returns when readOnly, so a blur can't
  fire a mutation if the role was downgraded mid-edit.
- onPickPlant guards canEdit.
- Ownership is now the authoritative ownerId==me check (GardenCard via a
  currentUserId prop, GardenEditorPage via useMe), so a missing my_role can never
  lock an owner out or show them a Leave button.

Cleanup:
- Shared cardActionClass/cardDangerClass (GardenCard + PlantCard).
- Normalized the read-only <fieldset> indentation; dropped the dead useShares
  `enabled` param.

tsc --noEmit clean; 24/24 vitest; production build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
2026-07-19 00:12:54 -04:00
steveandClaude Opus 4.8 be5ed18db0 Add sharing UI: invite by email, roles, read-only viewer mode (#17)
Build image / build-and-push (push) Successful in 14s
Gadfly review (reusable) / review (pull_request) Successful in 7m21s
Adversarial Review (Gadfly) / review (pull_request) Successful in 7m21s
- lib/shares.ts: zod Share shape + hooks (useShares/useAddShare/
  useUpdateShareRole/useRemoveShare over /gardens/:id/shares).
- lib/gardens.ts: myRole on the garden schema + canEditRole/isOwnerRole helpers
  (role always comes from the server's my_role, never guessed).
- ShareGardenModal (owner only): invite an existing user by email as viewer/
  editor, change a share's role inline, remove; surfaces the backend's friendly
  "no account with that email". Reachable from the garden card and the editor.
- GardenCard: "shared · viewer/editor" badge on non-owned gardens; owner gets
  Share/Edit/Delete, a recipient gets Leave (LeaveGardenModal removes their own
  share).
- Read-only viewer mode in the editor, gated on a single canEdit flag:
  viewers get no palette, no select/move/resize/rotate handles, no plop overlay,
  no placement, and read-only inspectors (a disabled <fieldset> makes every field
  read-only in one shot; Change/Delete/Remove/Plant-here hidden), plus a "View
  only" indicator. Defense in depth: the canvas create/place handlers also check
  canEdit, so a missed gate can't fire a mutation that would 403.

tsc --noEmit clean; 24/24 vitest; production build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
2026-07-18 23:57:59 -04:00