# 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`, 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.