docs: plan binary browser attachments
This commit is contained in:
parent
5be14d5ec0
commit
95e83b4f6b
|
|
@ -257,9 +257,10 @@ exact domain match into the bound workspace queue. Its first conversion workflow
|
||||||
creates ordinary Markdown notes through the public Files API and publishes a
|
creates ordinary Markdown notes through the public Files API and publishes a
|
||||||
`browser.capture.converted` event, which Activity records through its public
|
`browser.capture.converted` event, which Activity records through its public
|
||||||
provider subscription. Browser Inbox also creates human-readable `.url` link
|
provider subscription. Browser Inbox also creates human-readable `.url` link
|
||||||
files through the public Files API. It now accepts selected text files from the
|
files through the public Files API. It now accepts selected files from the
|
||||||
browser extension and creates ordinary workspace files through the public Files
|
browser extension and creates ordinary workspace files through `api.files.writeText`
|
||||||
API. Binary attachment capture/conversion remains future work.
|
or bounded `api.files.writeBytes`. Chunked large-file attachment capture remains
|
||||||
|
future work.
|
||||||
|
|
||||||
## 9. `official.search`
|
## 9. `official.search`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ Known remaining gaps:
|
||||||
- `fileActions`, `noteActions`, and `contextMenuEntries` are exposed through the
|
- `fileActions`, `noteActions`, and `contextMenuEntries` are exposed through the
|
||||||
desktop contribution summary and hosted by the official Files/Notes surfaces.
|
desktop contribution summary and hosted by the official Files/Notes surfaces.
|
||||||
- Sidecar host is not implemented.
|
- Sidecar host is not implemented.
|
||||||
- Files/Notes are usable but not complete: binary write/streaming, richer
|
- Files/Notes are usable but not complete: chunked streaming/large-file import,
|
||||||
conflict UX, and remaining Notes polish are still incomplete.
|
richer conflict UX, and remaining Notes polish are still incomplete.
|
||||||
- Activity, journal, browser inbox conversion workflow, indexed search,
|
- Activity, journal, browser inbox conversion workflow, indexed search,
|
||||||
secrets, and templates plugins are not complete product features.
|
secrets, and templates plugins are not complete product features.
|
||||||
- File/image preview exists as a basic provider with bounded inline image
|
- File/image preview exists as a basic provider with bounded inline image
|
||||||
|
|
@ -58,8 +58,9 @@ Known remaining gaps:
|
||||||
scaffold; desktop has a local receiver and mounted-view inbox plugin; receiver
|
scaffold; desktop has a local receiver and mounted-view inbox plugin; receiver
|
||||||
pairing, basic Browser Inbox domain binding, create-note conversion, and
|
pairing, basic Browser Inbox domain binding, create-note conversion, and
|
||||||
create-link conversion are implemented, text file attachment conversion is
|
create-link conversion are implemented, text file attachment conversion is
|
||||||
implemented, and Activity records conversions. Binary attachment
|
implemented, bounded binary attachment conversion is implemented, and Activity
|
||||||
capture/conversion remains future work.
|
records conversions. Chunked large-file attachment capture remains future
|
||||||
|
work.
|
||||||
- Packaging/update/release workflow is not product-grade yet.
|
- Packaging/update/release workflow is not product-grade yet.
|
||||||
|
|
||||||
## 4. Implementation Phases
|
## 4. Implementation Phases
|
||||||
|
|
@ -171,8 +172,7 @@ Tasks:
|
||||||
- [x] record converted inbox entries in Activity through public plugin events;
|
- [x] record converted inbox entries in Activity through public plugin events;
|
||||||
- [x] convert inbox entries into link files through public plugin APIs;
|
- [x] convert inbox entries into link files through public plugin APIs;
|
||||||
- [x] convert captured text file attachments through public plugin APIs;
|
- [x] convert captured text file attachments through public plugin APIs;
|
||||||
- convert captured binary attachments through public
|
- [x] convert captured bounded binary attachments through public plugin APIs.
|
||||||
plugin APIs.
|
|
||||||
|
|
||||||
Verification:
|
Verification:
|
||||||
|
|
||||||
|
|
@ -241,8 +241,9 @@ Verification:
|
||||||
3. [x] External open public v2 API to replace Files fallback.
|
3. [x] External open public v2 API to replace Files fallback.
|
||||||
4. [x] Notes trash/delete UX in `verstak-official-plugins`.
|
4. [x] Notes trash/delete UX in `verstak-official-plugins`.
|
||||||
5. [x] Sync hardening pass with expanded real two-vault smoke.
|
5. [x] Sync hardening pass with expanded real two-vault smoke.
|
||||||
6. [~] Browser inbox protocol design, extension scaffold, local receiver, and
|
6. [x] Browser inbox protocol design, extension scaffold, local receiver,
|
||||||
minimal inbox plugin are implemented; binary attachment conversion remains.
|
minimal inbox plugin, and note/link/text-file/binary-file conversions are
|
||||||
|
implemented.
|
||||||
|
|
||||||
This order finishes generic platform surfaces before building product features
|
This order finishes generic platform surfaces before building product features
|
||||||
that depend on them.
|
that depend on them.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
# Binary File Write And Browser Attachments Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Add bounded binary file writes to the public Files API and use them for Browser Inbox binary attachment conversion.
|
||||||
|
|
||||||
|
**Architecture:** Desktop owns the safe byte-write primitive and sync payload compatibility. SDK and frontend bridge expose the method as public plugin API. Browser extension sends base64 for selected files, receiver republishes it, and Browser Inbox converts through `api.files.writeBytes`.
|
||||||
|
|
||||||
|
**Tech Stack:** Go Files service and Wails bridge, plain JavaScript plugin host and WebExtension code, TypeScript SDK types/tests, official plugin smoke harness, Markdown docs.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Do not add chunked streaming in this slice.
|
||||||
|
- Do not allow plugins to write outside vault-relative path policy.
|
||||||
|
- Keep the write limit at 8 MB to match existing `readBytes`.
|
||||||
|
- Keep sync backward compatible with existing text `content` payloads.
|
||||||
|
- Use TDD for each behavior change.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Desktop Files API `writeBytes`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/files/service.go`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/files/service_test.go`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/api/app.go`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/api/app_test.go`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/src/lib/plugin-host/VerstakPluginAPI.js`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/tests/plugin-api-files-test.mjs`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/wailsjs/go/api/App.js`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/frontend/wailsjs/go/api/App.d.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces `api.files.writeBytes(relativePath, dataBase64, options)`.
|
||||||
|
- Produces backend `WriteVaultFileBytes(pluginID, relativePath, dataBase64, options)`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write RED service/API/bridge tests**
|
||||||
|
|
||||||
|
Add tests for successful byte write, invalid base64 rejection, oversized payload
|
||||||
|
rejection, permission enforcement, sync `dataBase64` payload, remote binary
|
||||||
|
apply, and frontend bridge exposure.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-desktop-current
|
||||||
|
go test ./internal/core/files -run TestWriteVaultFileBytesAtomicAndConflictBehavior -count=1
|
||||||
|
go test ./internal/api -run 'TestFilesBridgeReadWriteListMoveTrash|TestFilesBridgePermissions|TestApplyRemoteFileOps|TestFileBridgeRecordsSyncOps' -count=1
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node frontend/tests/plugin-api-files-test.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: failures because `writeBytes` does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement byte write**
|
||||||
|
|
||||||
|
Add atomic bounded base64 decode/write in the Files service, Wails bridge method,
|
||||||
|
plugin host method, generated Wails stubs, sync payload `DataBase64`, and remote
|
||||||
|
apply support.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run GREEN**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./internal/core/files
|
||||||
|
go test ./internal/api
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node frontend/tests/plugin-api-files-test.mjs
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all commands exit 0.
|
||||||
|
|
||||||
|
### Task 2: SDK `writeBytes` Contract
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-sdk/src/plugin-api.ts`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-sdk/src/test-utils.ts`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-sdk/src/plugin-api.test.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces SDK `files.writeBytes(relativePath, dataBase64, options?)`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write RED SDK test**
|
||||||
|
|
||||||
|
Assert `createMockPluginAPI().files.writeBytes` writes base64 content that can
|
||||||
|
be read back through `readBytes`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-sdk
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: failure because `writeBytes` is missing.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement SDK types and mock**
|
||||||
|
|
||||||
|
Add the method to `VerstakPluginAPI` and mock implementation.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run GREEN**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: SDK tests pass.
|
||||||
|
|
||||||
|
### Task 3: Browser Extension Binary Capture
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/protocol.js`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/background.js`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.js`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/scripts/test-protocol.js`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-browser-extension/README.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces file captures with `file.dataBase64` for selected files up to 8 MB.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write RED protocol test**
|
||||||
|
|
||||||
|
Add a binary file capture assertion and validate that missing both `file.text`
|
||||||
|
and `file.dataBase64` is rejected.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-browser-extension
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: failure because `dataBase64` is not preserved.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement base64 file capture**
|
||||||
|
|
||||||
|
Read selected files as `ArrayBuffer`, base64 encode them, keep text only when
|
||||||
|
`file.text()` succeeds for text-like files, and enforce the 8 MB limit.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run GREEN**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: extension tests and build pass.
|
||||||
|
|
||||||
|
### Task 4: Receiver And Browser Inbox Binary Conversion
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/browserreceiver/receiver.go`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/internal/core/browserreceiver/receiver_test.go`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/frontend/src/index.js`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes `file.dataBase64`.
|
||||||
|
- Produces Browser Inbox conversion through `api.files.writeBytes`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write RED tests**
|
||||||
|
|
||||||
|
Receiver test asserts `fileDataBase64`; Browser Inbox smoke asserts `Create File`
|
||||||
|
uses `writeBytes` for binary captures.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-desktop-current
|
||||||
|
go test ./internal/core/browserreceiver -run TestReceiverAcceptsFileCaptureAndPublishesEvent -count=1
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: failures for missing `fileDataBase64` and missing `writeBytes` path.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement receiver and plugin conversion**
|
||||||
|
|
||||||
|
Add `DataBase64` to receiver file payload, preserve it in Browser Inbox storage,
|
||||||
|
and call `api.files.writeBytes` when present.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run GREEN**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-desktop-current
|
||||||
|
go test ./internal/core/browserreceiver
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-official-plugins
|
||||||
|
PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js
|
||||||
|
PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: receiver and official plugin checks pass.
|
||||||
|
|
||||||
|
### Task 5: Documentation And Final Verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md`
|
||||||
|
- Modify: `/home/mirivlad/git/verstak2/verstak-desktop-current/docs/PLUGIN_RUNTIME.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Documents public `files.writeBytes` and binary Browser Inbox completion.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update docs**
|
||||||
|
|
||||||
|
Document `writeBytes`, base64/8 MB limits, sync compatibility, and mark binary
|
||||||
|
attachment conversion complete.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run full verification**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-desktop-current && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/test.sh
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-sdk && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-browser-extension && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm test && PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH npm run build
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-official-plugins && PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh
|
||||||
|
cd /home/mirivlad/git/verstak2/verstak-docs && git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all commands exit 0.
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Binary File Write And Browser Attachments Design
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Browser Inbox still cannot complete binary file attachment conversion because
|
||||||
|
the public Files API can read bounded bytes but cannot write them. This slice
|
||||||
|
adds a bounded public `files.writeBytes` contract and uses it to convert browser
|
||||||
|
file captures that contain base64 data.
|
||||||
|
|
||||||
|
The goal is not streaming or large-file import. It is a safe, testable binary
|
||||||
|
write path for small attachments that keeps plugins on public APIs.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This slice adds:
|
||||||
|
|
||||||
|
- `api.files.writeBytes(relativePath, dataBase64, options)` for enabled plugins
|
||||||
|
with `files.write`;
|
||||||
|
- desktop service support for atomic bounded byte writes up to 8 MB;
|
||||||
|
- sync op payload support for `dataBase64`, while preserving older text
|
||||||
|
`content` payloads;
|
||||||
|
- SDK type/mock/test coverage for `files.writeBytes`;
|
||||||
|
- browser extension file capture that sends `file.dataBase64` for all selected
|
||||||
|
files up to 8 MB and keeps `file.text` only for text-compatible files;
|
||||||
|
- local receiver flattening of `fileDataBase64`;
|
||||||
|
- Browser Inbox conversion that prefers `api.files.writeBytes` when binary data
|
||||||
|
is present and falls back to `api.files.writeText` for text-only captures.
|
||||||
|
|
||||||
|
It does not add chunked streaming, folders, drag-and-drop from pages, download
|
||||||
|
interception, or binary preview changes.
|
||||||
|
|
||||||
|
## Files API Contract
|
||||||
|
|
||||||
|
`files.writeBytes(relativePath, dataBase64, options)`:
|
||||||
|
|
||||||
|
- accepts canonical vault-relative slash paths;
|
||||||
|
- rejects traversal, absolute paths, `.verstak`, symlinks, folders, missing
|
||||||
|
parents, and conflicts using the same path policy as `writeText`;
|
||||||
|
- rejects invalid base64;
|
||||||
|
- rejects decoded payloads over `MaxBinaryReadBytes` (8 MB);
|
||||||
|
- writes atomically through a temp file in the target directory;
|
||||||
|
- requires `files.write`;
|
||||||
|
- records the same `file.changed` activity shape as text writes;
|
||||||
|
- records sync create/update payloads as `{ "path": "...", "dataBase64": "..." }`.
|
||||||
|
|
||||||
|
Remote sync apply supports both:
|
||||||
|
|
||||||
|
- old text payloads: `{ "path": "...", "content": "..." }`;
|
||||||
|
- new byte payloads: `{ "path": "...", "dataBase64": "..." }`.
|
||||||
|
|
||||||
|
## Browser Capture Contract
|
||||||
|
|
||||||
|
For `kind: "file"`, the extension sends:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file": {
|
||||||
|
"name": "photo.png",
|
||||||
|
"mime": "image/png",
|
||||||
|
"size": 1234,
|
||||||
|
"dataBase64": "iVBORw0KGgo...",
|
||||||
|
"text": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`file.name` is required. A file capture is valid when either `file.dataBase64`
|
||||||
|
or `file.text` is present. The extension rejects selected files over 8 MB before
|
||||||
|
sending.
|
||||||
|
|
||||||
|
## Browser Inbox Conversion
|
||||||
|
|
||||||
|
Browser Inbox stores both `fileText` and `fileDataBase64`. `Create File` writes:
|
||||||
|
|
||||||
|
- `api.files.writeBytes(path, fileDataBase64, { createIfMissing: true,
|
||||||
|
overwrite: false })` when binary data is present;
|
||||||
|
- otherwise `api.files.writeText(path, fileText, { createIfMissing: true,
|
||||||
|
overwrite: false })`.
|
||||||
|
|
||||||
|
On success it publishes `browser.capture.converted` with
|
||||||
|
`conversionType: "file"` and removes the capture. On write failure it leaves the
|
||||||
|
capture in the queue and renders an error.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Required checks:
|
||||||
|
|
||||||
|
- core Files service writes bytes, rejects invalid/oversized payloads, and
|
||||||
|
preserves conflict/path behavior;
|
||||||
|
- desktop API bridge enforces `files.write`, records sync `dataBase64`, and
|
||||||
|
applies remote binary payloads;
|
||||||
|
- frontend plugin API exposes `files.writeBytes`;
|
||||||
|
- SDK types and mock API include `writeBytes`;
|
||||||
|
- browser extension protocol builds, validates, and queues binary captures;
|
||||||
|
- receiver accepts file captures with `dataBase64`;
|
||||||
|
- Browser Inbox smoke proves binary conversion uses `writeBytes`;
|
||||||
|
- full verification scripts pass for touched repos.
|
||||||
Loading…
Reference in New Issue