# Emaki trial workspace All operations are browser-local. Use `await emakiStudio.execute(command)` or paste the same JSON into `#command-input` and click `#command-run`. Result: `{ok:true,...}` or `{ok:false,error:{message},state}`. Never treat ok:false as success. `emakiStudio.preview`/`accept` archive trials the same way. For a local change, avoid resending the whole scene. These operations start from the current accepted scene, validate a detached copy, render it as an unaccepted trial, and keep the fixed paper header intact: - `{"op":"append_layer","layer":LAYER,"expectedRevision":R}` - `{"op":"replace_layer","layer":COMPLETE_LAYER_WITH_SAME_ID,"expectedRevision":R}` - `{"op":"remove_layer","layerId":"detail","expectedRevision":R}` - `{"op":"preview_patch","patch":{"title":"...","appendLayers":[],"replaceLayers":[],"removeLayerIds":[]},"expectedRevision":R}` Add `trialId` to start from an archived trial. `patch_scene` is a JSON-command alias of `preview_patch`. Patches cannot edit canvas or material settings; use a complete scene preview for those deliberate changes. ## New sheet `{"op":"canvas_options"}` lists the standard profile and aspect ratios. `{"op":"new_document","aspectRatio":"3:4","expectedRevision":R}` creates a new blank; only aspectRatio is configurable. Previous scene is undoable with `{"op":"undo","expectedRevision":NEW_R}`. Save important work first. Selecting #paper-aspect alone only prepares the next sheet; #studio-new creates it. `{"op":"scene"}` reads the current scene; supply trialId for an archived scene. Edit title/layers of that copy, retaining canvas/seed/dimensions/material. Status.canvas describes the committed sheet; preview.canvas describes its candidate. Untagged historical imports keep their settings and report profile:legacy. Never change/delete a new scene's profile to bypass fixed settings; use new_document for a different aspect ratio. ## Loop 1. `{"op":"status"}` -> revision, writable, busy, active job, WebMCP registration. No image payload. `job` is only the ACTIVE job; read a finished one with `{"op":"job","jobId":ID}`. 2. `{"op":"snapshot"}` -> preserve the current accepted paper as baseline; `{"op":"list"}` -> trial IDs, status, short notes. No change to painting. list defaults to 20 results; use offset/limit (max 128). Notes are clipped to 180 chars unless fullNotes:true. 3. `{"op":"preview","scene":SCENE,"expectedRevision":R,"parentId":ID,"notes":{"aim":"...","preserve":"...","hypothesis":"...","change":"..."}}` -> candidateId + jobId + archive storage status; does NOT adopt. Omit parentId to snapshot current as baseline. 4. `{"op":"branch","trialId":ID,"expectedRevision":R,"layers":[LAYER],"notes":{...}}` -> re-preview saved scene with complete layers replaced by matching layer.id, or appended for new IDs. Omit layers to restore exactly. The ID denotes saved scene/PNG, not frozen material state; replay recomputes it. Layer deletion/reordering: read scene, edit detached copy, preview. 5. `{"op":"compare","leftId":A,"rightId":B,"crop":[0,0,1,1],"anonymous":false}` -> display equal-size images side by side. crop=[x,y,width,height] normalized; requires same output dimensions. A common crop is not automatic reference alignment. Use anonymous:true to hide labels, not as proof of unbiased judgment. 6. `{"op":"image","trialId":ID,"crop":[0,0,1,1],"maxWidth":1200}` -> explicit PNG dataURL + identity/dimensions. Actually display the image with allowed tools; do not dump the payload into text for routine status. `{"op":"scene","trialId":ID}` returns its complete scene. 7. `{"op":"record","trialId":ID,"decision":"rejected","notes":{"observation":"...","reason":"..."}}` -> save a concise observed result. Decisions: kept/rejected/deferred. None adopts or deletes painting. Notes may also include feedback; max 1200 chars/field, 100 events/trial. Intentional bands are valid; compare to the aim. 8. To use an old winner, branch it again, inspect, then `{"op":"accept","candidateId":NEW_ID,"expectedRevision":R}`. Stale adoption fails; read status, never blindly retry. Baseline/no-change and defer are valid outcomes. 9. `{"op":"export","trialId":ID,"format":"png"}` downloads PNG; json downloads painting scene; record downloads scene + notes/events (no PNG). Main Save/legacy export still exports accepted work only. 10. `{"op":"artifacts"}` lists files exported during this page session with stable re-download URLs. `{"op":"export_bundle","video":false}` creates and downloads accepted PNG + scene JSON; `video:true` also records the process video. ## Process video `{"op":"video","secondsPerLayer":4,"subtitles":false,"wait":true}` records and downloads the accepted painting's process video (0.5 s finished painting, then the process with layer intents as captions). Required at finish; see video.md. Takes about real time; with `wait:false` it returns at once and `status.video` / `observe().video` reports `recording` → `done` or `failed`. Keep the tab visible. ## Async jobs Use `{"op":"submit","scene":SCENE,"expectedRevision":R,"notes":{...}}` to start without waiting for full rendering. Poll `{"op":"job","jobId":ID}`; states running/completed/failed/cancelled, completed/total commands, trialId on completion. Completed means the candidate is rendered, installed and ready for accept with the original expectedRevision; no extra delay is needed. Check status.archive: memory-only means the finished trial could not be saved to history. Another later operation may still supersede that candidate. `{"op":"cancel","jobId":ID}` requests interruption: v1–v3 yield between commands; v4 also yields within long commands, every .5 material seconds (not .5 wall-clock seconds). Cancellation is not immediate. No background execution after closing the page. Jobs are in-memory (last 64); successfully archived trials persist; memory-only trials do not survive closing/reloading the tab. `preview`/`branch` await completion. After interrupted render, read status and list; accepted painting is unchanged. ## Persistence and editor Accepted artwork is saved synchronously in localStorage **before** adoption. Trial scenes, PNGs and notes use IndexedDB. They are separate stores, not a cloud backup. Closed IndexedDB connections reopen automatically, with at most two retries per operation. Only aborted transactions are replayed; capacity, quota and validation failures are not blindly retried. `observe().archive` and `status.archive` expose `pendingTrialId`, `persistence` (`stored`, `memory-only`, or `unavailable`), `error`, and lifecycle counters (`opens`, `closes`, `retries`, `failures`, `lastError`, `lastClose`). Counters are session-local. `lastClose` records the event type/time, not the browser's underlying cause. `list` also returns archive status; during an outage it may show only the retained trial, not the full saved history. If a completed preview returns `archiveWarning`, inspect the candidate normally; it has **not** been silently adopted. A failed history save retains one candidate's JSON/PNG in the current tab. Keep the tab open, then: - `await S.execute({op:"retry_archive"})` (or click history refresh `#trial-refresh`) retries the retained write without repainting or changing revision. - `await S.execute({op:"export",trialId:ID,format:"json"})` and `format:"png"` export that trial even while IndexedDB is unavailable. Main `exportJSON`/`exportPNG` export the **accepted** painting instead. - `await S.accept(ID,{expectedRevision:R})` still saves the chosen painting in localStorage. Check `archiveWarning` for a failed history update. A localStorage failure leaves revision and the candidate unchanged. A later preview first retries the pending save; if it fails, the previous candidate stays available. There is no timer that adopts artwork automatically. Memory-only rescue does not survive a crash/reload; the unload warning is best effort. Export or adopt before closing the tab. A missing/unavailable archive does not stop an already saved painting from loading or being exported. IndexedDB retains up to 128 records / estimated 256 MiB per origin, including baselines. No silent eviction by the app; capacity or quota failures preserve a completed trial in memory; a baseline failure before rendering prevents the new trial from starting. Browser storage can still be cleared/evicted; export important work. No cloud sync, no delete-all helper. Export to files before storage fills; capacity management is not automated. The scene textarea is a draft, not the displayed scene. API operations preserve user edits; explicit `#editor-current` or `#editor-candidate` loads that source and replaces the draft. Comparison images identify saved trials, not the live replay. Notes are separate from the painting schema. No auto-generated critique or private reasoning log. ## WebMCP On browsers exposing `document.modelContext.registerTool`, top-level tools named emaki_status/canvas_options/new_document/undo/list/scene/snapshot/submit/job/preview/preview_patch/append_layer/replace_layer/remove_layer/branch/accept/record/image/compare/export/artifacts/export_bundle/video/cancel/retry_archive invoke this same dispatcher. Check status.webmcp; registration does not prove an agent can discover/call them. No native WebMCP requirement, server or model is introduced. No polyfill pretends support. JSON form and browser JS remain fallback paths under tool permissions. Compare opens the trial workspace and updates the displayed images, selected IDs, crop and anonymous checkbox together. A superseded concurrent compare returns shown:false. Record updates an untouched note form; unsaved typed notes stay, with an explicit draft notice. Selecting a trial loads its saved notes/decision. ## DOM-only agents If you can click and type but not run page JavaScript: `#paper-aspect` + `#studio-new` (new blank; selecting a ratio alone changes nothing), open `#scene-controls summary`, put scene JSON in `#scene-input`, `#studio-preview`, inspect the sticky `#console-paper` preview, and read colocated `#scene-result` before `#studio-accept`; `#studio-current`/`#studio-candidate` switch the view; `#studio-undo`; save via `#studio-png`/`#studio-json`. The generated files remain as links in `#artifact-list`. Trial and patch commands: open `#trial-workspace`, paste one command into `#command-input`, click `#command-run`, read `#command-result`. Do not bypass tool permissions. ## Legacy imports Untagged JSON (no `canvas`) is compatibility mode: status reports `profile:"legacy"` and the original size/paper are kept. Do not strip the profile from new work. Historical technique examples keep their original paper; move their strokes into a standard `S.scene()` copy and retest instead of copying their header or claiming the old evidence covers the new paper.