The preview JPEGs (~2.4 MiB, already compressed so gzip passed them
straight through) leave the wasm data segment: a platform-free asset
registry in op-editor-core tracks per-route Absent/Pending/Ready/Failed
with single-flight and install-once semantics, the browser half fetches
over ArrayBuffer XHR with managed-mode headers and a slot-wrapped
callback so no synchronous failure can strand a route in Pending, and
paint sites fall back to the existing placeholder when bytes are not
(yet) there. Native keeps include_bytes verbatim. The staging script
copies the asset dirs into pkg/assets/ — under /pkg/ because the hub
frontend owns /assets/ — and the gate, CI workflow, and web image all
run it and assert the layout. Bundle: 7.13 → 5.10 MiB gzip, so the
tripwire returns to 6 MiB (85% occupancy); the sdk bundle keeps its own
8 MiB pending a real measurement.
Also closes the final review test gaps: the owner-session fixture now
returns a must-use lane guard (a dropped receiver made the saturated
lane read as Disconnected, not Full) and seeds the daemon's baseline
document so the hash check exercises the real path, and the closed
write barrier has a direct multi-page active-page regression test.
The re-review found several remedies that existed but were not
reachable; this closes them:
- online answers /api/auth/status with the verified identity
projection (login/logout stay 404), so the account-switch epoch
actually fires; first identification reloads the account's settings
partition, identity reset also tears down the id allocator, and an
Active projection whose namespace disagrees rebuilds it
- finish_local_edit reports Committed/NoChange/Rejected/Failed and a
real runtime rejection answers 409 version-conflict with the
authoritative version — the one shape the browser's recovery parses
- the REST scope gate runs ahead of every dispatch path including
share, AI, figma, and SSE (/mcp keeps its stronger per-tool check)
- shutdown drains connections (bounded) before the flush, the tenant
store probes create/write/rename/delete at startup and fails closed
naming the container uid, and worker-thread spawn failures abort
startup instead of silently never persisting
- ACL flushes hold the same guard as updates, and the 257th grant is
refused rather than silently truncated on the next write
- the conflict stash lost its call site in an earlier split and is
invoked again before accept; SSE subscribe prunes dead slots; an
uninstalled document push drops its pending thumbnail seed
Tier-1 online collaboration: an owner grants accounts into their
tenant's ACL (grant/revoke/list under /api/share/), and a visitor
addresses it per request with ?tenant=<ownerId> — query rather than
header because EventSource cannot set headers, and share routes always
administer the caller's own tenant so a grant is not re-shareable.
The wasm shell threads the parameter through its four XHR helpers and
the event stream in one place. A shared tenant has no collaboration
session, so the 409 auto-resolve now also accepts when the daemon
advertises serveMode online (the daemon's counter is the total order
and SSE is already delivering the newer document; the old latch would
freeze a visitor permanently). Evicted tenants persist document + ACL
under sha256(user_id) directories via atomic writes — written before
the registry remove so no instant lacks both copies; unwritable
tenants stay resident and unloadable files are set aside as .corrupt
rather than overwritten. Also restores the Dockerfile entrypoint
literal the M2 CMD restructure dropped (its CI test only ran in the
workspace suite) and splits live_sync_glue under the file cap.
HubVerifier resolves both credential shapes onto the tenant registry —
Authorization: Bearer through the hub's internal token introspection
(shared-secret header, constant-time compared) and the op_hub_session
cookie through /api/v1/session — so a browser and an MCP token for the
same account share one document. Verdicts cache under SHA-256 keys
(session 60s, token min(300s, expiry), definitive negatives 15s);
upstream failures are never cached so a hub blip cannot become a 15s
outage, and a configured-but-unbuildable hub fails 503 rather than
silently downgrading to the dev verifier. Cookie-authenticated writes
now require an allowlisted Origin (bearer exempt), CORS echoes the
allowlist instead of *, and the web image gains online mode, a non-root
user, and a healthcheck.
47 commits from the align branch merged onto the force-updated remote
base (which had replayed an earlier snapshot of the same work plus new
overlay/pointer features and CI fixes). Conflict resolution: kept the
newer align side for the generation pipeline (orchestrator, mcp, skills,
design tools), kept the base side for the chat-panel test semantics and
graceful overlay teardown, fused both in sub_agent_session (design-turn
thinking policy + graceful epoch finish), and dropped the files each
side had deleted (legacy concurrent/dashboard paths, retired TS skills).
Deduped two identical replayed hunks (export.rs, chat_session_tests.rs).
Known issue carried over: provider_probe_host::landed_connected_outcome_
without_models_is_failure fails on a host with a live provider config
(env-sensitive test, both sides byte-identical there; green on CI).
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.
- design-agent system prompt + in-process design toolset (parity-locked with
the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
(per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
bubbles, model-pill bottom toolbar, header, empty-state pills, the
PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
(switch / close / + / Cmd+T) with each run bound to its tab
Large checkpoint commit spanning the working tree (Rust shell crates).
- Remove the Zig `agent-native` git submodule (no Rust crate depends on it;
the built-in agent runtime is the Rust `agent` crate under vendor/agent).
- Make the repo root a pure Cargo workspace: delete root package.json + bun.lock;
move all web-SDK JS/Bun tooling into packages/ (new packages/package.json
workspace root, generate-iconify-catalog.mjs, and the oxlint/oxfmt configs).
- Scrub agent-native from all docs and fix README dev commands + prerequisites
to reflect the Rust product (cargo directly; bun tooling runs from packages/).
- Drop the deleted-root-package.json read from the op-host-web ci_workflow test.
The extracted headless crate is consumed by BOTH op-host-desktop (the GUI binary, for its
embedded --serve-web/MCP/chat/export) AND op-host-web-server — so 'web-daemon' was misleading.
Renamed crate dir + package + lib (op_web_daemon -> op_host_services) across all consumer files
(114 refs in 24 files) + the 4 Cargo.toml deps + Dockerfile/guard comments; identity docs
rewritten (it's the GUI-free host backend — daemon/MCP/AI/export/persistence — not web-specific).
No behavior change. (Lock renamed accordingly; the concurrent actor's op-host-web serde line excluded.)
Dockerfile.web-rust builds -p op-host-web-server (was op-host-desktop) and drops ALL GL/X11
build + runtime apt deps (libegl/libgles/libgbm/libxkbcommon/libwayland/libxcb) — the raster
server links none; only freetype/fontconfig + CJK fonts remain for skia text. COPY + CMD repointed
to /app/op-host-web-server. New tools/check-web-server-headless.sh fails CI if op-host-web-server's
isolated dep graph pulls winit/glutin/casement/muda/accesskit-adapters or skia-safe with gl (bare
accesskit core allowed per Codex Issue 1); wired into rust-check.yml + its paths filter. Stale
web_static.rs path comment fixed (op-host-desktop -> op-web-daemon). Desktop-app build job untouched.