Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# `permissions/permissions.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Probe and subscribe to clipboard permission state via the browser's
Permissions API. `checkPermissions` returns a snapshot of the
`clipboard-read` and `clipboard-write` permission states with graceful
degradation to `"prompt"` when the API is missing or a query rejects.
`onPermissionChange` subscribes a callback to `PermissionStatus.change`
events on both descriptors so UI surfaces can reflect real-time grants
and revocations without polling.
## Features
- `checkPermissions(nav)` — `{ read, write }` snapshot; never throws.
- `onPermissionChange(nav, cb)` — subscribe with one callback, unsubscribe
with a single returned function.
- Tolerant of partial API support: falls back to `PermissionStatus.onchange`
when `addEventListener` is unavailable.
- Tolerant of permission-query rejection: skips that slot without
re-throwing.
## Usage: Basic
```ts
import { checkPermissions } from "@suaveplan/clipboard";
const { read, write } = await checkPermissions(navigator);
if (read === "denied" || write === "denied") {
showFallbackUi();
}
```
## Usage: Advanced
```ts
import { onPermissionChange } from "@suaveplan/clipboard";
const unsubscribe = await onPermissionChange(
navigator,
(snapshot, descriptor) => {
console.info(
"[clipboard] permission change",
descriptor,
snapshot,
);
if (snapshot.read === "granted" && snapshot.write === "granted") {
enableCopyButtons();
}
},
);
// later — on unmount / dispose
unsubscribe();
```
## API Reference
### `checkPermissions(nav: ClipboardNavigator): Promise<PermissionResult>`
Resolves with `{ read, write }`. Each slot is one of `"granted"`,
`"denied"`, `"prompt"`. Returns `{ read: "prompt", write: "prompt" }`
when `nav.permissions` is undefined, and `"prompt"` for any individual
descriptor whose `query()` rejects or returns an unknown state.
### `onPermissionChange(nav, cb): Promise<() => void>`
Subscribes `cb` to permission changes on both `clipboard-read` and
`clipboard-write`. Returns an unsubscribe function. The callback receives
the latest `PermissionResult` snapshot plus the descriptor name whose
state changed. The change handler internally re-runs `checkPermissions`
so both `read` and `write` are always up to date on every callback.
## Implementation Notes
The Permissions API exposes two listener mechanisms — `addEventListener`
and `onchange`. Some platforms (older WebKit / mobile) support only one,
so the module attaches via `addEventListener` when available and falls
back to assigning `onchange` otherwise. The unsubscribe function clears
the corresponding mechanism for each subscription. The
`checkPermissions` helper normalises unknown state strings to `"prompt"`
so callers never have to defend against future spec values. Query
rejection is treated as `"prompt"` rather than `"denied"` because a
rejection typically signals "API not supported" rather than "user
explicitly refused" — pessimistic mapping would block correct callers in
older browsers. Tests cover both `addEventListener` and `onchange` paths,
plus the unsubscribe contract.