Files
OneCAD-Draft/docs/automation/web.md
T
Afonsop db8db14006 feat: web automation wrapper (#1349), DATALINK table on web, System theme; honest decode accounting
Ported from 2c38de53 (#1717). Main already carries the hatch PDF draw
order fix (#1345), the numeric set_properties compare (#1348) and the
web open from bytes, so those parts stay out; what remains:

#1349 — window.ocsControl.ready/request promise wrapper with an
ocs-ready event (web/ocs-control.js, loaded by the web page), and the
startup donation prompt is suppressed once a control request is seen
(opt-in pre-boot via localStorage["opencadstudio.automation"] = "1").
docs/automation/web.md documents both. The control protocol stays at 1.

DATALINK CSV-to-table works on web (picker + bytes through the session
registry — was std::fs, silently dead); COLORSCHEME accepts System
(prefers-color-scheme on web, AppsUseLightTheme on Windows).

Decode accounting, made honest per the fix-of-fix review: into_rgba8
allocates the RGBA destination while the native buffer is alive for
every non-Rgba8 layout (L8 5, La8 6, Rgb8 7, L16 6, La16 8 B/px
peaks — the formula now says so), GIF bills the screen+frame
coexistence at 8 B/px, and TIFF drops the 3x multipliers both ways —
tiff 0.11 splits the given budget between frame and scratch (a total
ceiling), so the old double-billing refused honest 16-bit GeoTIFFs
~2.25x early while admitting hostile chunk transients at 3x.

(cherry picked from commit 2c38de53)
2026-10-12 12:02:05 +03:00

78 lines
3.6 KiB
Markdown

# Driving the web build
The web build exposes the same control core as `--serve` and `--mcp`, through two
functions on the WebAssembly module. This page covers only what is specific to the
browser; the operations, request envelope and replies are the ones in the
[README](README.md).
## Reaching the control channel
The Trunk loader publishes the wasm-bindgen module as `window.wasmBindings`. The
control channel is ready once `window.wasmBindings?.ocs_control_submit` is defined
(the [save smoke test](web_save_smoke.cjs) waits for exactly that).
| Function | Behaviour |
|---|---|
| `ocs_control_submit(request: string) → string` | Queues one request (a JSON string) and returns a ticket immediately. |
| `ocs_control_take(ticket: string) → string \| undefined` | Returns the reply (a JSON string) once it is ready, `undefined` before that. A reply is removed when taken. |
The page also loads `ocs-control.js`, which wraps the two functions in promises:
| API | Behaviour |
|---|---|
| `await window.ocsControl.ready` | Resolves once the control channel exists; the `ocs-ready` window event fires at the same moment. |
| `await window.ocsControl.request(request, {timeoutMs, pollMs})` | Submits one request and resolves with the final reply, following `accepted`/`running` replies through `op: "operation"`. Rejects only on timeout or a missing channel. |
Without the wrapper, poll `ocs_control_take` until it returns a reply, as the smoke test does:
```js
async function control(request) {
const api = window.wasmBindings;
const ticket = api.ocs_control_submit(JSON.stringify(request));
for (let i = 0; i < 600; i++) {
const reply = api.ocs_control_take(ticket);
if (reply) return JSON.parse(reply);
await new Promise((resolve) => setTimeout(resolve, 50));
}
throw new Error('control timed out');
}
```
Limits: at most 64 requests wait in the queue (a further `submit` is answered with the
`busy` error), and only the newest 64 replies are kept, so take a reply soon after it is
ready. A request that is not valid JSON is answered with `op: "invalid"` and a
`parse_error`.
## Opening a drawing from bytes
The web build has no file system, so `open` takes the file's bytes instead of a path:
```json
{"op":"open","name":"plan.dwg","data_base64":"…"}
```
`name` is reduced to its file name and labels the tab and the recent-files entry;
`data_base64` must be valid base64 and non-empty (`invalid_request` otherwise). While an
earlier drawing is still opening the request is answered with `busy`; retry it once
`state` shows the tab. A `path` is resolved against the browser's "recent files" store
rather than a disk, so use `data_base64` for a file the page has not seen.
`data_base64` is rejected on native builds; use `path` there.
## Startup dialogs
A dialog can be open when the page is ready. The donation prompt appears once per
application version in each browser profile, so a fresh or disposable profile always
sees it — unless the session is automated: the first control request drops a prompt
that is still queued, and a host that sets `localStorage["opencadstudio.automation"]`
to `"1"` before the page loads never gets one. A prompt already on screen stays. While a modal is open `state.modal` is set, and `{"op":"new"}` returns `ok`
without creating a tab until the modal is closed. Close it first:
```js
const state = await control({ op: 'state' });
if (state.modal) await control({ op: 'action', name: 'close_modal' });
await control({ op: 'new' });
```
Native builds can show a second dialog at launch (the file-association prompt), so check
`state.modal` again after closing one.