# Cross-Site Clipboard Redesign — Keyboard Intercept

## Summary

Replace the modal-based export/import cross-site clipboard with an invisible keyboard intercept system (Elementor-style). Users copy/paste normally with Cmd+C/Cmd+V. When pasting content from another site, a `confirm()` dialog appears. No modal, no toolbar button.

## Architecture

Single script (`clipboard-bridge.js`) loaded in the VB app iframe via `register_package_build`. No CSS, no modal, no toolbar button.

### Copy Flow (Cmd+C / Ctrl+C)

1. Divi handles copy normally (stores to Redux `divi/clipboard`)
2. Our `keyup` listener fires after Divi's handler completes
3. Read latest item from `select('divi/clipboard').getLastItem('all')`
4. Serialize into `CrossSiteClipboardPackage` (JSON + base64 + prefix)
5. Write to system clipboard via `window.top.navigator.clipboard.writeText()`
6. Silent — no UI feedback

### Paste Flow (Cmd+V / Ctrl+V)

1. Our `keydown` listener fires in **capture phase** (before Divi)
2. Read system clipboard via `window.top.navigator.clipboard.readText()`
3. If data does NOT have `DNXTE_CROSSCLIP_V1:` prefix → do nothing, let Divi handle normally
4. If data HAS the prefix:
   a. `preventDefault()` + `stopPropagation()` to block Divi's paste
   b. Decode and validate the package
   c. Show `confirm("Paste content from [sourceUrl]?")`
   d. On cancel → do nothing
   e. On confirm → import media via REST API → remap URLs in payload → inject into `divi/clipboard` store via `dispatch('divi/clipboard').addItem()` → programmatically trigger Divi's paste via `divi.clipboard.pasteModuleFromClipboard()`

### Data Format (unchanged)

```typescript
interface CrossSiteClipboardPackage {
  version: 1;
  plugin: 'divi-essential';
  timestamp: number;
  sourceUrl: string;
  diviVersion: string;
  items: DiviClipboardItem[];
  mediaUrls: string[];
}
```

Prefix: `DNXTE_CROSSCLIP_V1:` + base64(JSON)

### Media Import

REST endpoint (unchanged): `POST /wp-json/dnxte/v1/cross-clipboard/import-media`
- Input: `{ urls: string[] }`
- Downloads remote images, sideloads to WP media library
- Returns `{ mapping: { oldUrl: newUrl }, errors: {} }`

## Files

### Keep & Modify
- `clipboard-bridge.ts` — add keyboard listeners, keep serialization logic, remove `exportToSystemClipboard`/`importFromSystemClipboard` (replaced by listeners)
- `cross-site-clipboard.php` — remove toolbar/modal/CSS registration, keep REST endpoint, register single script
- `types.ts` — remove modal-related types

### Remove
- `toolbar-button.ts`
- `register-modal.ts`
- `modal/component.tsx`
- `modal/container.tsx`
- `modal/style.scss`
- `icons/index.ts`
- `index.ts` (rewrite — just imports clipboard-bridge)
- Built outputs: `toolbar-button.js`, `bundle.css`

### Webpack Config
- Remove `cross-site-clipboard/toolbar-button` entry point
- Keep `cross-site-clipboard/bundle` entry point (now just the bridge)

## Error Handling

| Scenario | Behavior |
|---|---|
| Clipboard API unavailable (HTTP) | Silent fail on copy, console.warn |
| Clipboard permission denied | Browser shows permission prompt |
| Media import fails | Paste proceeds with original URLs |
| User cancels confirm dialog | Nothing happens, Divi keeps internal state |
| Invalid/corrupt clipboard data | Ignored, Divi handles paste normally |
| `divi/clipboard` store not ready | Console.warn, skip |

## Edge Cases

- **Same-site paste**: If `sourceUrl === window.location.origin`, skip media import and confirm dialog — just inject directly
- **Multiple items**: Package can contain multiple clipboard items; all get injected
- **Large media**: REST endpoint has 30s timeout per download; failures are non-blocking

## PHP Dependencies

Single script registration:
```php
'deps' => ['divi-data', 'divi-vendor-wp-hooks', 'divi-clipboard']
```
