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
- 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