Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View 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.

View File

@@ -0,0 +1,110 @@
# Tasks: enterprise-browser-clipboard
**Change ID**: enterprise-browser-clipboard
**Package**: `@suaveplan/clipboard`
**Target path**: `browser/platform/clipboard`
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/logger`
---
## 0. Pre-flight (non-negotiable)
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 1. Scaffold
- [ ] Create directory tree per `proposal.md`.
- [ ] `package.json` v0.0.0, `"sideEffects": false`.
- `dependencies`: `@suaveplan/error workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/schema-registry workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `happy-dom catalog:testing`, `@playwright/test catalog:testing`, `@suaveplan/testing workspace:^`
- Subpath exports: `"."`, `"./types"`
- [ ] `tsconfig.json`, `vitest.config.ts` (pool: forks, happy-dom environment).
---
## 2. Error codes
- [ ] `CLIPBOARD_API_UNAVAILABLE`, `CLIPBOARD_PERMISSION_DENIED`, `CLIPBOARD_FALLBACK_FAILED`, `CLIPBOARD_INSECURE_CONTEXT`.
- [ ] `registerClipboardErrorCodes()` — call eagerly.
---
## 3. Types (`src/types/types/`)
- [ ] `ClipboardData` (text, html, image, blobs), `ClipboardOptions`, `ClipboardResult`, `PermissionResult`, `ClipboardNavigator`.
- [ ] Write `types.md`.
---
## 4. ClipboardOptions schema (`src/schema/clipboard-schema/`)
- [ ] Zod schema for `ClipboardOptions`; `registerClipboardSchema()`.
- [ ] Write `clipboard-schema.md`.
---
## 5. Async queue (`src/clipboard/queue/`)
- [ ] `queue.ts` — `OperationQueue<T>` serializes concurrent async operations; rejects queued calls after configurable timeout.
- [ ] Write `queue.test.ts` (sequential execution, timeout rejection). Write `queue.md`.
---
## 6. Fallback module (`src/clipboard/fallback/`)
- [ ] `fallback.ts` — `execCommandFallback(text)` for copy; `pasteEventFallback()` for read (focus + paste event capture).
- [ ] `requireSecureContext()` — throws `CLIPBOARD_INSECURE_CONTEXT` on HTTP.
- [ ] Focus-aware retry: on `Document is not focused`, wait for focus, retry once.
- [ ] Write `fallback.test.ts` (execCommand success, paste capture, insecure context). Write `fallback.md`.
---
## 7. OTel metrics (`src/telemetry/metrics/`)
- [ ] `metrics.ts` — 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`.
- [ ] Write `metrics.md`.
---
## 8. `ClipboardManager` (`src/clipboard/clipboard/`)
- [ ] `clipboard.ts` — `ClipboardManager` composing queue + fallback + metrics.
- [ ] `copyText` / `readText` with secure-context guard + API → fallback path.
- [ ] `copyRichContent` / `readRichContent` — `text/plain`, `text/html`, `image/png`, arbitrary MIME via `ClipboardItem`.
- [ ] `checkPermission`, `onPermissionChange` via `PermissionStatus.onchange`.
- [ ] Log deprecation metric when `execCommand` path fires.
- [ ] Write `clipboard.test.ts` (all paths, image copy, permission change, queue serialization, insecure context). Write `clipboard.md`.
---
## 9. Integration test (`tests/integration.test.ts`)
- [ ] Plain text copy + read round-trip.
- [ ] Rich content (text + html) round-trip.
- [ ] Queue serializes 3 concurrent copy calls.
- [ ] Insecure-context throws `CLIPBOARD_INSECURE_CONTEXT`.
---
## 10. Playwright E2E (`e2e/clipboard.spec.ts`)
- [ ] Chromium, WebKit, Firefox.
- [ ] Copy and read plain text with real `clipboard-write` / `clipboard-read` permissions.
- [ ] Copy and read PNG image in Chromium.
- [ ] Permission-denied path returns `CLIPBOARD_PERMISSION_DENIED`.
- [ ] `onPermissionChange` subscription fires on deny → grant transition.
---
## 11. Docs + verify
- [ ] Write `.md` per module (≥200 words, API reference, examples).
- [ ] Write `README.md` with browser-matrix table, API guide, permission guide.
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/clipboard` — green.
- [ ] `bunx turbo test:coverage` — 100%.
- [ ] No `@ts-expect-error` in `src/` or tests. No `vi.stubGlobal`.
- [ ] Playwright E2E green on Chromium, WebKit, Firefox.
- [ ] `bun scripts/publish.ts --package @suaveplan/clipboard`.
- [ ] Archive to `openspec/archive/2026-04-24-enterprise-browser-clipboard/`.