Initial Commit
This commit is contained in:
166
openspec/changes/enterprise-browser-clipboard/proposal.md
Normal file
166
openspec/changes/enterprise-browser-clipboard/proposal.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# OpenSpec Change: enterprise-browser-clipboard
|
||||
|
||||
- **Change ID**: enterprise-browser-clipboard
|
||||
- **Package**: `@suaveplan/clipboard`
|
||||
- **Category**: browser
|
||||
- **Target path**: `browser/platform/clipboard`
|
||||
- **Status**: IN PROGRESS
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/clipboard.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/clipboard` on the genesis branch at enterprise quality. The package provides `ClipboardManager` with injectable `ClipboardNavigator` seam, plain-text and rich copy/read, permission probing, and `execCommand` fallback. Genesis adds image and arbitrary-blob clipboard I/O, typed errors via `@suaveplan/error`, secure-context guard, paste-event fallback, permission-change subscription, async queue for concurrent operations, OTel counters/histograms, and Playwright E2E across Chromium/WebKit/Firefox.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
Fresh build — does not exist on genesis yet.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | `CLIPBOARD_API_UNAVAILABLE`, `CLIPBOARD_PERMISSION_DENIED`, etc. |
|
||||
| `@suaveplan/logger` (`workspace:^`) | Structured logging |
|
||||
| `@suaveplan/schema-registry` (`workspace:^`) | `ClipboardOptions` schema |
|
||||
| `zod` (`catalog:forms`) | `ClipboardOptions` Zod schema |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Copy/read/fallback metrics |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
Dev: `vitest`, `@vitest/coverage-istanbul`, `happy-dom`, `@playwright/test`, `@suaveplan/testing`.
|
||||
|
||||
---
|
||||
|
||||
## Preserved public API surface
|
||||
|
||||
```ts
|
||||
class ClipboardManager {
|
||||
constructor(navigator?: ClipboardNavigator)
|
||||
copyText(text: string, options?: ClipboardOptions): Promise<ClipboardResult>
|
||||
readText(options?: ClipboardOptions): Promise<ClipboardResult>
|
||||
copyRichContent(data: ClipboardData, options?: ClipboardOptions): Promise<ClipboardResult>
|
||||
readRichContent(options?: ClipboardOptions): Promise<ClipboardResult>
|
||||
checkPermission(name: 'clipboard-read' | 'clipboard-write'): Promise<PermissionResult>
|
||||
onPermissionChange(name: 'clipboard-read' | 'clipboard-write', cb: (s: PermissionState) => void): () => void
|
||||
}
|
||||
|
||||
// Types
|
||||
interface ClipboardData {
|
||||
text?: string
|
||||
html?: string
|
||||
image?: Blob // new
|
||||
blobs?: Record<string, Blob> // new — arbitrary MIME
|
||||
}
|
||||
interface ClipboardResult { success: boolean; method: 'api' | 'fallback'; payload?: ClipboardData; error?: SuaveplanError }
|
||||
interface PermissionResult { state: PermissionState; supported: boolean }
|
||||
```
|
||||
|
||||
Subpath exports: `"."`, `"./types"`.
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
### 1. Image and arbitrary-blob clipboard I/O
|
||||
|
||||
`copyRichContent` and `readRichContent` accept/return `image?: Blob` and `blobs?: Record<string, Blob>` via `ClipboardItem` for modern Clipboard API.
|
||||
|
||||
### 2. Typed errors
|
||||
|
||||
`CLIPBOARD_API_UNAVAILABLE`, `CLIPBOARD_PERMISSION_DENIED`, `CLIPBOARD_FALLBACK_FAILED`, `CLIPBOARD_INSECURE_CONTEXT` — all extend `@suaveplan/error`.
|
||||
|
||||
### 3. Secure-context guard
|
||||
|
||||
`requireSecureContext()` probes `window.isSecureContext`; throws `CLIPBOARD_INSECURE_CONTEXT` outside HTTPS/localhost before attempting clipboard access.
|
||||
|
||||
### 4. Paste-event fallback
|
||||
|
||||
`readText` in non-API contexts orchestrates focus + `paste` event capture as best-effort fallback.
|
||||
|
||||
### 5. Permission-change subscription
|
||||
|
||||
`onPermissionChange(name, cb)` using `PermissionStatus.onchange`; returns unsubscribe function.
|
||||
|
||||
### 6. Async operation queue
|
||||
|
||||
Concurrent `copy`/`read` calls are serialized via an internal queue to prevent "Document is not focused" races.
|
||||
|
||||
### 7. Focus-aware retry
|
||||
|
||||
When `Document is not focused` error fires, waits for the next focus event and retries once.
|
||||
|
||||
### 8. Error codes
|
||||
|
||||
`CLIPBOARD_API_UNAVAILABLE`, `CLIPBOARD_PERMISSION_DENIED`, `CLIPBOARD_FALLBACK_FAILED`, `CLIPBOARD_INSECURE_CONTEXT`.
|
||||
|
||||
### 9. OTel metrics
|
||||
|
||||
Counter `clipboard.copy_total{method}`, counter `clipboard.read_total{method}`, counter `clipboard.fallback_used_total`, counter `clipboard.error_total{code}`, histogram `clipboard.op_latency_ms`.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
browser/platform/clipboard/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
clipboard/
|
||||
clipboard/
|
||||
clipboard.ts (ClipboardManager, ≤250 lines)
|
||||
clipboard.test.ts
|
||||
clipboard.md
|
||||
clipboard.types.ts
|
||||
index.ts
|
||||
queue/
|
||||
queue.ts (async operation serializer)
|
||||
queue.test.ts
|
||||
queue.md
|
||||
index.ts
|
||||
fallback/
|
||||
fallback.ts (execCommand + paste-event fallback)
|
||||
fallback.test.ts
|
||||
fallback.md
|
||||
index.ts
|
||||
telemetry/
|
||||
metrics/
|
||||
metrics.ts
|
||||
metrics.md
|
||||
index.ts
|
||||
types/
|
||||
types/
|
||||
types.ts
|
||||
types.md
|
||||
index.ts
|
||||
schema/
|
||||
clipboard-schema/
|
||||
clipboard-schema.ts
|
||||
clipboard-schema.md
|
||||
index.ts
|
||||
constants/
|
||||
index.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
e2e/
|
||||
clipboard.spec.ts (Chromium/WebKit/Firefox: text, image, permissions)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream**: any Suaveplan browser app using clipboard operations.
|
||||
|
||||
**Breaking changes**: None — all additions are additive. Error types changed from bare strings to `SuaveplanError` (structured), but result shape is unchanged.
|
||||
|
||||
**Prerequisites**: `@suaveplan/error`, `@suaveplan/logger` on Verdaccio.
|
||||
Reference in New Issue
Block a user