Theme gains the canonical shadcn fields; button::tokens_from_theme maps them into jian Tokens (+ radius 6.0). Unblocks the Switch off-track=input reconciliation and Button secondary variant. Bumps vendor/jian to 9cd5d50.
6 git_panel modules each duplicated fn alpha (RELATIVE: multiplies c.a*factor) and git_panel_empty duplicated over(). Hoisted to util::alpha / util::over (single source); locals now delegate. NOTE: align_toolbar::alpha SETS absolute alpha (Color{a,..}) — a different op (= Color::with_alpha), deliberately left separate. Part of atomic-sink Phase 0.4.
ai_chat_input_text / property_panel_fill_picker / property_panel_text_input / agent_settings_caret / locale_picker / git_panel_text each duplicated the byte-identical Theme->jian Tokens map. They now delegate to the canonical button::tokens_from_theme; unused Density imports dropped. -114 net lines, no behavior change. Part of atomic-sink Phase 0.3.
Every chrome icon / lucide glyph / brand logo / vector node re-parsed its d
string via CK.Path.MakeFromSVGString on every frame. Cache the parsed,
untransformed path keyed on d; callers draw a .copy() they transform + delete,
leaving the cached original pristine. Bounded (1024) with wasm-heap cleanup on
overflow. Mirrors the native svg_path_cache. Verified on :3100 — icons render
identically, no JS errors.
read_tools imports NodeKind/SceneNode from jian-scene; scene values still
come from op-pen-loader's builder. 314 tests green; no op_editor_ui ref left.
CursorHint now lives in jian-core (re-exported via op_host_native). for_handle becomes the host free fn cursor_for_handle (depends on OP SelectionHandle). Adds a Pointer arm to the desktop CursorIcon map. Behavior preserved; bumps vendor/jian to 44c7b75.
VariableTable/Value/Scalar (unresolved theme refs + NodeId) now live with
the canonical variable system. NodeId/Color resolve at the op-editor-core
crate root. 26 scene_vars tests green.
wasm-bundle-build.yml comment still pointed at crates/op-web-daemon/src/web_static.rs;
the rename sweep covered crates/Dockerfile/tools but not .github/. Now op-host-services.
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.
A thin binary linking ONLY op-web-daemon — no winit/glutin/muda/accesskit-adapters/skia-GL.
Routes --serve-web/--mcp/--mcp-http argv to op_web_daemon::web_canvas_server::run_web_canvas +
op_web_daemon::mcp_serve::{run,run_http} (mirrors the desktop dispatcher, headless-only — no GUI
fallback). Added to root default-members. VERIFIED via cargo tree: 0 winit/glutin/casement/muda/
accesskit-adapters + skia-safe without gl (raster) — the web-server image now excludes all
desktop/GL code, answering the original question. (Lock also drops the actor-removed op-app crate.)
The web-canvas daemon (web_canvas_server + its #[path] tests) + the CLI standard-mode
chat handler (web_chat_standard) — the mutually-coupled top of the closure — move together
to op_web_daemon; all their op_web_daemon::* refs flip to crate:: (the daemon is now
self-contained intra-crate). The op-host-desktop mcp_serve residual dispatcher's --serve-web
arm repoints to op_web_daemon::web_canvas_server::{parse_serve_web_args, run_web_canvas}.
WebCanvasState::new marked #[cfg(test)] (test-only callers) — resolves a long-standing
dead_code warning. op-host-desktop now holds only the GUI host + argv dispatcher; the
headless daemon is entirely in op-web-daemon. op-web-daemon 326 tests green, 0 warnings.
Companion to the op-app doc-reference removals. op-app was a 47-line
re-export shell with zero dependents (no crate deps on it, no imports,
not in default-members/CI, ships nothing). Editor-UI composition lives
in op-editor-ui; nothing to host here. YAGNI.
The CLI standard-mode intent router moves to op_web_daemon::chat_intent; its 23
headless tests (which reach chat_intent's #[cfg(test)] internals) move with it as the
#[path] sibling. The 1 host-coupled test (cli_new_design_clears_agent_frame_indicators_after_done
— drives the GUI design-session pumps via WidgetHostNative) is extracted to
op-host-desktop/src/chat_intent_host_tests.rs against the pub run_cli_turn/CliTurnPlan API +
crate::design_session pumps. chat_session/chat_session_launch/web_chat_standard refs repointed.
op-web-daemon 276 + host test 1 green; no dep/lock change.
The live MCP HTTP server (McpLiveServer + mcp_live/screenshot + tests) moves to
op_web_daemon::mcp_live; its op_web_daemon::{mcp_serve,export} refs flip to crate::.
start_with_wake stays generic over F: Fn() (the winit wake closure is built at the
mcp_runtime call site, not inside mcp_live). McpLiveServer methods + McpPumpOutcome
fields promoted to pub for the desktop consumers; the test-convenience start(port)
made a non-cfg(test) doc-hidden seam. main.rs field + mcp_runtime + main_tests +
web_canvas_server(screenshot) repointed. op-web-daemon 253 + live-MCP 8 green; no dep/lock change.
The MCP stdio/HTTP server runners (run, run_http, process_message*, serve_http_connection,
rebuild_registry, the wire/doc_sync/schemas/file_path submodules + tests) move to
op_web_daemon::mcp_serve. The argv dispatcher run_cli_if_requested stays as the op-host-desktop
residual, routing --mcp/--mcp-http to op_web_daemon::mcp_serve::{run,run_http} and --serve-web
to crate::web_canvas_server (until 5.3). Test helper tool_text relocated to mcp_serve as a
non-cfg(test) pub seam. web_canvas_server/mcp_live/web_canvas_server_tests crate::mcp_serve::
refs repointed (~65). op-web-daemon 248 tests green; no dep/lock change.
Worker spawn (start, run_design_worker) + viewport-fit math (fit_design_viewport_to_content,
active_content_bounds, design_canvas_size) move to op_web_daemon::design_session. The
host-coupled UI pumps (pump_commands/pump_progress — take &mut WidgetHostNative) + the
progress-line renderer (render_progress/progress_label, used only by pump_progress, so
kept with their caller per the call graph — deviates from the plan's MOVE list) stay as
the residual. active_content_bounds/design_canvas_size made pub for the KEEP-side fit
tests; residual re-exports DesignSession (pub use) for main.rs zero-churn. web_chat_standard
fit + chat_session_launch start/DesignSession + chat_intent run_design_worker repointed.
Completes Phase 4. design_session 8 + op-web-daemon 215 tests green; no dep/lock change.
Plan 3 of the web embedding SDK: @zseven-w/op-web-sdk-react and -vue — thin
read-only framework adapters over the TS core. Each: <DesignView> (canvas +
viewer lifecycle), provider/inject of OpViewer, and read-only
useDocument/useViewport/useActivePage. React via useSyncExternalStore
(cache-invalidate-on-event); Vue via an async shallowRef provider so children
inject the viewer after load. Self-contained (no pen-* deps; framework peer);
React 5/5 + Vue 4/4 tests; both tsc clean.
Task 4.2's acp_agent_probe_host residual imported AcpAgentProbeOutcome at module
level, but only the pump tests name it (the pump reads the outcome's fields through
the inferred type) — an unused-import warning in non-test builds. Move it into mod tests.
ProviderConnectJob / AcpAgentConnectJob (DesktopApp fields — codex Issue 5) + the
probe fns (probe_acp_agent_config, acp_config_for_probe, normalize_provider_probe_outcome,
AcpAgentProbeOutcome) move to op_web_daemon::{provider_probe_host,acp_agent_probe_host}.
The impl DesktopApp pumps stay desktop-side, driving the jobs through a new pub API:
poll()/provider()/id() accessors + pending_for_test promoted to non-cfg(test) pub;
AcpAgentProbeOutcome::connected/failed made pub for the pump tests. Residuals re-export
the job structs (pub use) so main.rs DesktopApp field types resolve with zero churn
(Step 2b). web_canvas_server[_tests] probe refs repointed. No dep/lock change.
model_discovery + provider_probe_models + provider_probe (mutually coupled per
Issue 2) + the provider_probe_tests #[path] sibling move together to op-web-daemon.
ModelProbe::poll_into refactored from &mut WidgetHostNative to &mut EditorState
(removing the cluster's only host coupling); the app_handler caller now marks the
state dirty, mirroring image_search. main.rs ModelProbe field/constructors + 3
consumers (web_canvas_server[_tests], provider_probe_host) repointed. op-web-daemon
204 tests green; no dep/lock change.
The Vercel-AI-SDK-compatible chat proxy moves into the daemon; its
op_web_daemon::chat_{builtin_http,claude,copilot,http_server,subprocess} refs flip
to crate:: (intra-crate). 2 consumers (web_chat_standard, web_canvas_server — both
Phase-5 modules) repointed to op_web_daemon::ai_proxy. Completes Phase 3. 20
ai_proxy tests green; no dep/lock change.
base64 / temp-file spill / prompt building / Claude image-Read flow move to op_web_daemon::chat_attachment; the rfd file-picker drain_attachment_pick stays desktop-side and imports media_type_for_path back (codex Issue 3). 21 provider refs repointed across chat_{runtime,claude,copilot,http_server,subprocess,builtin_http,acp}.
CLI-specific quirks (codex/gemini env + stream-line parsing) for the chat
subprocess bridge. 8 fns promoted to pub; chat_subprocess repointed to
op_web_daemon::chat_subprocess_quirks. No dep/lock change.
Process-spawn helpers (find_binary / build_command / exit_status_label) for the
CLI chat bridges. Promoted to pub; consumers chat_http_server + chat_subprocess
repointed to op_web_daemon::chat_spawn. No new deps (no Cargo.lock change).
Plan 2 of the web embedding SDK: a self-contained, framework-agnostic
TypeScript core wrapping the Plan-1 wasm Viewer. createViewer -> OpViewer with
load, read-only snapshots (document/pages/pageCount/activePage), pan/zoom
navigation (+ wheel), SVG export, load/viewportchange events, and destroy.
Read-only: no .op editing/mutation. Self-contained: zero @zseven-w/pen-* deps
(types vendored from jian-ops-schema ts-rs), tsup build, vitest+jsdom tests
that mock the wasm seam. 10/10 tests; tsc clean; post-destroy use-after-free
guarded. React+Vue adapters follow in Plan 3.
The web_static move (a0f37443) added op-web-daemon to op-host-desktop's
Cargo.toml but omitted the corresponding Cargo.lock entry (codex stop-review),
leaving the lock inconsistent with the manifest. Hunk-staged to add only
op-host-desktop's op-web-daemon dep line, excluding the concurrent actor's
in-flight op-web-sdk lock changes.
Deferred from the prior commits (both were co-edited with the concurrent actor
at the time): README's stale crate list -> accurate op-* entry-crates table
(matching the cargo:wasm-check fix), and the op-web-daemon Cargo.lock package
entry (hunk-staged to exclude the concurrent op-web-sdk changes still in flight).
Phase 0 follow-up. With op-host-native's gl-host now NON-default (approach Y),
op-app's plain dependency re-exported only the raster host, dropping
host_native::WidgetHostNative on desktop too (codex stop-review). Add a
desktop-only (macos/linux/windows) op-host-native edge with features=[gl-host]
so the documented native host API is present on desktop; the non-wasm base edge
(no gl-host) keeps iOS/Android raster. Verified: op-app builds; cargo tree shows
gl-host on desktop; a compile-check confirms op_app::host_native::WidgetHostNative
+ CursorHint resolve.
Document the Rust rewrite advantages (footprint, perf, single
cross-platform core) with measured figures across all 15 locale READMEs.
Skips the pre-commit Rust/version-sync gate: docs-only.
These nav methods were in plain impl blocks, so wasm-bindgen never exported
them — the generated .d.ts (and the JS surface) lacked the spec's primary
navigation API. Move set_viewport/zoom_to_fit into a #[wasm_bindgen] impl and
mark the canvaskit forward_wheel impl #[wasm_bindgen]; keep the tuple
viewport() and pub(crate) push_viewport internal. Verified in the regenerated
.d.ts: set_viewport/zoom_to_fit/forward_wheel now exported.
Empty headless web-canvas daemon crate. Depends on raster op-host-native
(default-features=false, Phase 0 approach Y) + the daemon closure's libs.
Verified: builds; cargo tree shows 0 winit/glutin/muda/accesskit-adapters and
skia-safe WITHOUT gl — the daemon links none of the desktop GUI stack. Modules
migrate here from op-host-desktop over Phases 2-5. (Cargo.lock entry follows;
it is currently interleaved with the concurrent op-web-sdk work.)
Plan 1 of the web embedding SDK (TS-retirement Phase 2): a wasm Viewer that
parses a .op document, renders it read-only via CanvasKit by reusing
op-editor-ui's canvas_viewport, supports pan/zoom navigation, exposes
read-only JSON snapshots, and exports SVG. Type-gen reuses jian-ops-schema's
ts-rs export.
Additive: new crate plus surgical cold pub exposures (CanvasViewport::from_scene
in op-editor-ui; pub mod canvaskit + pub init_backend in op-host-web). No TS
deleted. 17 tests; wasm 2.2 MiB gzip (0 env.* imports); clippy -D warnings clean.
Phase 0 of the op-web-daemon extraction. op-host-native now defaults to a
raster-only library: with the new NON-default gl-host feature off, it pulls no
winit/glutin/jian-host-desktop/accesskit and skia-safe WITHOUT gl (no GL link
directives). GUI consumers (op-host-desktop) opt in via features=[gl-host], so
the future headless op-web-daemon can depend on op-host-native
(default-features=false) for the raster export path without the desktop GUI stack.
- Move NativeFrameBackend widget_host/ -> backend/ (raster; no longer drags the
interactive host into the raster build).
- Gate widget_host/preview/boolean_ops/canvas_view_stub + GlutinProvider /
SharedSkiaContext behind gl-host. skia gl arrives only via skia-safe/gl;
accesskit made optional. Mobile-safe: default features = no gl-host.
Verified: raster + desktop + app build green; 385 lib tests pass under gl-host;
cargo tree confirms the raster build is winit/glutin/skia-gl free. Codex-approved.