Files
suaveplan/openspec/specs/drive-react-sharing/spec.md
2026-05-27 23:29:53 +01:00

92 lines
3.6 KiB
Markdown

# @suaveplan/drive-react-sharing — React Bindings for Drive Sharing
React-tier UI for managing partitioned drive resources. Wraps the real
`@suaveplan/drive-sharing` engine (PartitionManager / GrantManager /
TokenManager) behind a `useSharing` hook + three accessible components.
## Requirements
### Requirement: useSharing hook
The package SHALL expose `useSharing(options): UseSharingResult` that:
- Holds a live `recipients` list synchronised with `GrantManager.list()`.
- Exposes async actions `addRecipient`, `removeRecipient`,
`updatePermission`, `refresh`. Each action MUST surface failures as
`DriveReactSharingError` (typed `cause` preserving the underlying
`DriveShareError`).
- Increments OTel counters
`drive.react.sharing.recipients_added_total`,
`drive.react.sharing.recipients_removed_total`,
`drive.react.sharing.permissions_updated_total{permission}` on each
successful action.
- Threads AbortSignal through every async path.
- Does NOT mock the engine — consumers pass real `PartitionManager` /
`GrantManager` / `TokenManager` instances.
#### Scenario: updatePermission preserves expiresAt
- **WHEN** `updatePermission(grantId, newPermission)` is called on a grant
with `expiresAt` set
- **THEN** the resulting grant MUST have the same `expiresAt` value as before
(the hook revokes-then-creates internally since `GrantManager` has no
atomic permission mutation).
### Requirement: ShareDialog component
The package SHALL expose `<ShareDialog>` modal with:
- `role="dialog"` + `aria-modal="true"` + `aria-labelledby` referencing the
heading id.
- ESC key dismisses (handler bound to dialog `onKeyDown`).
- Backdrop is a real `<button>` with distinct `aria-label`.
- Recipient input auto-focuses on open.
- Permission select (`read | write | admin`) with explicit `aria-label`.
- Revoke button per row with `aria-label` including recipient id.
- Error banner rendered with `role="alert"`.
### Requirement: PartitionBadge component
The package SHALL expose `<PartitionBadge>` compact indicator with:
- `role="status"` + composed `aria-label` (e.g. "Partition type shared,
encrypted").
- Decorative SVG marked `aria-hidden="true"`.
- Renders partition `type` (`public | shared | private | encrypted`) + an
encryption icon when applicable.
### Requirement: FileAccessList component
The package SHALL expose `<FileAccessList>` read-only list:
- Semantic `<ul>` / `<li>` markup (no redundant explicit `role`).
- Revoke button per row with `aria-label` including recipient id.
- Delete / Backspace keyboard shortcuts on focused row trigger revoke.
### Requirement: Canonical Permission union
The package SHALL re-export `Permission` from `@suaveplan/drive-sharing` as
its canonical type and MUST NOT define a parallel union. Components accept
`"read" | "write" | "admin"` strings; the hook validates against the engine's
known permission set.
### Requirement: Storybook coverage
Each component SHALL ship a `.stories.tsx` with `Default` + `Playground` +
state variants (loading / error / populated / empty as applicable). The
`Playground` story for `<ShareDialog>` MUST wire state through `useSharing`
to exercise the hook end-to-end without an addon story type.
### Requirement: Real-engine tests
Tests MUST construct real `MemoryPartitionManager` / `MemoryGrantManager` /
`MemoryTokenManager` instances. NO `vi.mock`, NO `vi.stubGlobal`. Rendering
uses `@suaveplan/testing-dom`. All test imports come from
`@suaveplan/testing/runner`.
### Requirement: Coverage + gate
`bunx turbo lint typecheck test:coverage --filter=@suaveplan/drive-react-sharing`
MUST be green and istanbul coverage MUST be 100% on every touched module
before any release.