Initial Commit
This commit is contained in:
102
openspec/archive/2026-05-25-enterprise-clipboard/proposal.md
Normal file
102
openspec/archive/2026-05-25-enterprise-clipboard/proposal.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Change: enterprise-clipboard
|
||||
|
||||
- **Package**: `@suaveplan/clipboard`
|
||||
- **Status**: PROPOSED
|
||||
- **Design**: ./design.md
|
||||
- **Spec delta**: ./specs/clipboard/spec.md
|
||||
- **Upstream**: `../suaveplan/packages/browser/clipboard` (1.0.3)
|
||||
- **Enterprise audit**: `../suaveplan/enterprise/clipboard.md`
|
||||
|
||||
## Why
|
||||
|
||||
Genesis is migrating `@suaveplan/clipboard` from the legacy `packages/browser/clipboard/`
|
||||
checkout into the canonical `packages/foundation/data/clipboard/` location at enterprise
|
||||
quality. The upstream package shipped at 1.0.3 with a thin `ClipboardManager` facade over
|
||||
the browser Clipboard API plus a textarea+`execCommand` fallback, but the enterprise audit
|
||||
flagged seven gaps that block production use:
|
||||
|
||||
1. No image / arbitrary-blob clipboard I/O — `ClipboardItem` is only used for
|
||||
`text/plain` and `text/html`.
|
||||
2. Errors are hand-crafted strings (`"Clipboard API failed"`) with no `@suaveplan/error`
|
||||
integration, no error code registry, no programmatic discrimination.
|
||||
3. No secure-context guard — calling code outside HTTPS / `localhost` falls through to a
|
||||
generic failure with no diagnostic signal.
|
||||
4. No paste-event fallback for `readText` in non-API contexts; the only outcome is
|
||||
`"Reading clipboard requires Clipboard API"`.
|
||||
5. No permission-state subscription — UIs cannot react to `granted`/`denied` transitions.
|
||||
6. No OTel instrumentation — production cannot measure copy/read volume, fallback usage,
|
||||
error rate, or operation latency.
|
||||
7. No async serialization — concurrent `copy` / `read` calls race and the trailing call
|
||||
wins non-deterministically.
|
||||
|
||||
The greenfield rewrite addresses every gap while preserving the upstream public surface
|
||||
(`ClipboardManager`, `ClipboardNavigator`, `ClipboardData`, `ClipboardOptions`,
|
||||
`ClipboardResult`, `PermissionResult`, `DEFAULT_CLIPBOARD_OPTIONS`) so downstream
|
||||
consumers continue to compile against the same import paths.
|
||||
|
||||
## What changes
|
||||
|
||||
- **Add**: `@suaveplan/clipboard` at version `0.1.0` under
|
||||
`packages/foundation/data/clipboard/` with full enterprise surface — typed errors via
|
||||
`@suaveplan/error`, OTel counters + histogram, async queue serializer, secure-context
|
||||
guard, image / blob copy + read via `ClipboardItem`, paste-event fallback, permission
|
||||
change subscription, focus-aware retry, MIME-keyed format negotiation, paste sanitizer
|
||||
hook, and a runtime-injectable `ClipboardAdapter` / `ClipboardNavigator` seam.
|
||||
- **Add**: error-code namespace `CLIPBOARD.*` with eight stable codes
|
||||
(`API_UNAVAILABLE`, `PERMISSION_DENIED`, `FALLBACK_FAILED`, `INSECURE_CONTEXT`,
|
||||
`NO_CONTENT`, `READ_FAILED`, `DOCUMENT_UNAVAILABLE`, `QUEUE_DRAINED`).
|
||||
- **Add**: OTel instruments — counters `clipboard_copy_total`, `clipboard_read_total`,
|
||||
`clipboard_fallback_used_total`, `clipboard_error_total` and histogram
|
||||
`clipboard_op_latency_ms`.
|
||||
- **Add**: subpath exports per feature module (`./manager/manager`,
|
||||
`./fallback/fallback`, `./permissions/permissions`, `./queue/queue`,
|
||||
`./observability/observability`, `./errors/errors`, `./security/security`,
|
||||
`./sanitizer/sanitizer`, `./types`).
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
`bunx turbo ls --filter=...@suaveplan/clipboard` — empty (clipboard is a leaf migration;
|
||||
no current genesis package depends on it).
|
||||
|
||||
- No dependents (verified by `bunx turbo ls --filter=...@suaveplan/clipboard`).
|
||||
|
||||
Future React consumers (`react-ui`, `react-command-palette`) will adopt this package via
|
||||
a follow-up change once they migrate; no work is required in this proposal.
|
||||
|
||||
## Parity Checklist
|
||||
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
|---|---|---|---|
|
||||
| `ClipboardManager` facade | ✅ | ✅ | Same constructor signature; options bag + injectable `ClipboardNavigator`. |
|
||||
| `copyText(text)` | ✅ | ✅ | Now wraps every call in `withQueue` and emits OTel metrics. |
|
||||
| `readText()` | ✅ | ⬆️ | Paste-event fallback added when no Clipboard API; `requireSecureContext` gate added. |
|
||||
| `copyRichContent({text,html})` | ✅ | ⬆️ | Extended with `image: Blob`, `blobs: Record<string, Blob>`, TrustedHTML accepted, MIME map. |
|
||||
| `readRichContent()` | ✅ | ⬆️ | Now returns `image` and `blobs` fields, not just text/html. |
|
||||
| `checkPermissions()` | ✅ | ✅ | Same shape; resilient to permission-query failures. |
|
||||
| `isSupported()` | ✅ | ✅ | Same semantics. |
|
||||
| `DEFAULT_CLIPBOARD_OPTIONS` | ✅ | ⬆️ | Extended with `requireSecureContext`, `serialize`, `sanitizer`, `focusRetry`. |
|
||||
| `execCommand` fallback | ✅ | ✅ | Retained; emits `clipboard_fallback_used_total{reason}` counter. |
|
||||
| Permission change subscription | 🚫 | ⬆️ | NEW — `onPermissionChange(cb)` returning unsubscribe. |
|
||||
| Image / blob copy + read | 🚫 | ⬆️ | NEW — `ClipboardItem` with PNG / arbitrary MIME blobs. |
|
||||
| Async queue serializer | 🚫 | ⬆️ | NEW — `serialize: true` default; concurrent calls execute in arrival order. |
|
||||
| Secure-context guard | 🚫 | ⬆️ | NEW — `requireSecureContext()` throws `CLIPBOARD.INSECURE_CONTEXT`. |
|
||||
| Focus-aware retry | 🚫 | ⬆️ | NEW — on `NotAllowedError` "Document not focused", awaits next focus once. |
|
||||
| Paste-event fallback | 🚫 | ⬆️ | NEW — `readTextFallback` orchestrates focus + paste capture. |
|
||||
| Paste sanitizer hook | 🚫 | ⬆️ | NEW — `options.sanitizer(html) => string` runs before `readRichContent` returns. |
|
||||
| Typed errors with codes | 🚫 | ⬆️ | NEW — `ClipboardError` extends `SuaveplanError`; all paths use codes. |
|
||||
| OTel counters + histogram | 🚫 | ⬆️ | NEW — five instruments wired into every public method. |
|
||||
| Playwright proof | 🚫 | 🚫 | Scoped out: greenfield package starts with happy-dom unit tests at 100% coverage; Playwright spec lands with `react-ui` adoption follow-up so the host harness exists. |
|
||||
|
||||
## Migration
|
||||
|
||||
This is a 0.1.0 first landing — no prior genesis consumers. Downstream waves that adopted
|
||||
the upstream surface from `../suaveplan/packages/browser/clipboard` should:
|
||||
|
||||
1. Update imports from `@suaveplan/clipboard` (path stays the same).
|
||||
2. Treat `ClipboardResult.error` as `string | undefined` per the upstream contract, but
|
||||
prefer matching on `ClipboardResult.code` (new field) against `CLIPBOARD_CODES.*`.
|
||||
3. Adopt `withQueue`-based serialization automatically — no opt-in required (default
|
||||
`serialize: true`); set `serialize: false` to restore upstream's race-allowed
|
||||
behavior.
|
||||
Reference in New Issue
Block a user