Frontend for garden sharing: owners invite existing users by email as viewer or editor, and the whole app respects the actor's role — most visibly a read-only editor mode for viewers, where the garden renders beautifully but nothing can be changed.
Scope
Share dialog (owner only), reachable from the garden card and the editor: list current shares (name/email + role), invite by email with a viewer/editor select, change a share's role, remove a share. Friendly "no account with that email" error.
/gardens list: role badge on shared gardens ("shared · viewer" / "shared · editor"); owned gardens unbadged. Data comes from my_role in the list payload (#16).
Role-aware UI gating driven by my_role from /full/list responses (never guessed client-side): viewers get no palette, no handles, no inspector edit fields (read-only inspector is fine), no plant-placement action, plus a subtle "view only" indicator in the editor; editors get everything except the share dialog and garden meta edit/delete; owners get it all.
Defense in depth: mutations are also disabled at the mutation-hook level for viewers, so a missed UI gate can't fire a request that would 403.
A recipient can remove themselves from a shared garden ("leave garden" on the card).
Out of scope
Backend (#16). Real-time presence/updates (out of scope for v1 by design — a viewer sees changes on reload/refetch).
Key files & patterns
API from #16; editor gating touches #11/#15 components — keep the gate a single canEdit derived flag, not scattered conditionals.
Dependencies
Blocked by #8 and #16 (full read-only coverage also assumes #11/#15 are in place; if picked up before #15, gate what exists).
Acceptance criteria
Two-browser flow: A shares with B as viewer → B sees the badge, opens the garden, canvas renders, pan/zoom works, nothing is editable, no mutation requests fire; A upgrades B to editor → after refetch B can move objects and place plops; A removes the share → the garden leaves B's list.
Owner sees the share dialog; editor doesn't; viewer doesn't.
npx tsc --noEmit clean.
Part of epic #20 · Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md)
## Context
Frontend for garden sharing: owners invite existing users by email as viewer or editor, and the whole app respects the actor's role — most visibly a **read-only editor mode** for viewers, where the garden renders beautifully but nothing can be changed.
## Scope
- [ ] Share dialog (owner only), reachable from the garden card and the editor: list current shares (name/email + role), invite by email with a viewer/editor select, change a share's role, remove a share. Friendly "no account with that email" error.
- [ ] `/gardens` list: role badge on shared gardens ("shared · viewer" / "shared · editor"); owned gardens unbadged. Data comes from `my_role` in the list payload (#16).
- [ ] Role-aware UI gating driven by `my_role` from `/full`/list responses (never guessed client-side): viewers get no palette, no handles, no inspector edit fields (read-only inspector is fine), no plant-placement action, plus a subtle "view only" indicator in the editor; editors get everything except the share dialog and garden meta edit/delete; owners get it all.
- [ ] Defense in depth: mutations are also disabled at the mutation-hook level for viewers, so a missed UI gate can't fire a request that would 403.
- [ ] A recipient can remove themselves from a shared garden ("leave garden" on the card).
## Out of scope
- Backend (#16). Real-time presence/updates (out of scope for v1 by design — a viewer sees changes on reload/refetch).
## Key files & patterns
- API from #16; editor gating touches #11/#15 components — keep the gate a single `canEdit` derived flag, not scattered conditionals.
## Dependencies
Blocked by #8 and #16 (full read-only coverage also assumes #11/#15 are in place; if picked up before #15, gate what exists).
## Acceptance criteria
- Two-browser flow: A shares with B as viewer → B sees the badge, opens the garden, canvas renders, pan/zoom works, nothing is editable, no mutation requests fire; A upgrades B to editor → after refetch B can move objects and place plops; A removes the share → the garden leaves B's list.
- Owner sees the share dialog; editor doesn't; viewer doesn't.
- `npx tsc --noEmit` clean.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of epic #20 · Design: DESIGN.md
Context
Frontend for garden sharing: owners invite existing users by email as viewer or editor, and the whole app respects the actor's role — most visibly a read-only editor mode for viewers, where the garden renders beautifully but nothing can be changed.
Scope
/gardenslist: role badge on shared gardens ("shared · viewer" / "shared · editor"); owned gardens unbadged. Data comes frommy_rolein the list payload (#16).my_rolefrom/full/list responses (never guessed client-side): viewers get no palette, no handles, no inspector edit fields (read-only inspector is fine), no plant-placement action, plus a subtle "view only" indicator in the editor; editors get everything except the share dialog and garden meta edit/delete; owners get it all.Out of scope
Key files & patterns
canEditderived flag, not scattered conditionals.Dependencies
Blocked by #8 and #16 (full read-only coverage also assumes #11/#15 are in place; if picked up before #15, gate what exists).
Acceptance criteria
npx tsc --noEmitclean.