98 lines
3.5 KiB
Markdown
98 lines
3.5 KiB
Markdown
# 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.
|