Commit graph

1773 commits

Author SHA1 Message Date
Kayshen-X bfef05ca13 refactor(mcp): use jian-scene, drop op-editor-ui dep
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.
2026-06-19 23:46:03 +08:00
Kayshen-X 9add35583c refactor(editor): source CursorHint from jian-core; host owns handle+Pointer map
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.
2026-06-19 23:42:30 +08:00
Kayshen-X 71fa9baa5b refactor(editor-ui): re-export scene from jian-scene/op-editor-core
Delete the moved modules (layout_scene, layout_scene_hit, scene_vars + their
tests); shim re-export jian_scene::{layout_scene,layout_scene_hit} +
op_editor_core::scene_vars so every consumer path (incl. dirty hosts) resolves
unchanged. Paint imports path geometry from jian-scene. 647 tests green.
2026-06-19 23:39:02 +08:00
Kayshen-X d76ba4528d refactor(editor-core): move scene_vars in from op-editor-ui
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.
2026-06-19 23:18:43 +08:00
Kayshen-X 210fc21bf6 chore(vendor): bump jian submodule — jian-scene crate + scene skeleton removal 2026-06-19 23:12:30 +08:00
Kayshen-X 7678352071 ci: fix stale op-web-daemon path after crate rename to op-host-services
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.
2026-06-19 22:41:43 +08:00
Kayshen-X 88a571c028 refactor(host): rename op-web-daemon crate to 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.)
2026-06-19 22:35:22 +08:00
Kayshen-X b7951fc598 ci(host): build web image from op-host-web-server + headless-boundary guard (Phase 6, Tasks 6.2-6.4)
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.
2026-06-19 22:14:56 +08:00
Kayshen-X f198cdf76a feat(host): add headless op-host-web-server binary (Phase 6, Task 6.1)
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.)
2026-06-19 22:09:58 +08:00
Kayshen-X c6743453b6 refactor(host): move web_canvas_server + web_chat_standard, daemon fully in op-web-daemon (Phase 5, Task 5.3)
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.
2026-06-19 22:01:31 +08:00
Kayshen-X c24c4bb73f refactor: remove orphan op-app crate files
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.
2026-06-19 21:58:22 +08:00
Kayshen-X a172b74023 refactor(host): move chat_intent to op-web-daemon; extract host-coupled test (Phase 5, Task 5.3a)
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.
2026-06-19 21:55:33 +08:00
Kayshen-X d5727d322d refactor(host): move mcp_live to op-web-daemon (Phase 5, Task 5.2)
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.
2026-06-19 21:27:35 +08:00
Kayshen-X 7acd125ccd refactor(host): move mcp_serve runners to op-web-daemon, keep argv dispatch (Phase 5, Task 5.1)
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.
2026-06-19 21:16:10 +08:00
Kayshen-X 18a162c732 refactor(host): split design_session; keep GUI pumps in op-host-desktop (Phase 4, Task 4.4)
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.
2026-06-19 21:01:47 +08:00
Kayshen-X 199d9bbec9 feat(sdk): add React + Vue adapters for op-web-sdk
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.
2026-06-19 21:01:27 +08:00
Kayshen-X baae07addd refactor(host): split settings_io; keep touch_recent in op-host-desktop (Phase 4, Task 4.3)
The headless settings persistence (Fingerprint/fingerprint/save_if_changed/load/save
+ the on-disk *Payload DTOs, id allocators, detect_system_locale, enum<->str converters)
+ settings_io_tests move to op_web_daemon::settings_io. The host-coupled touch_recent
(takes &mut WidgetHostNative — orphan rule) stays as the op-host-desktop residual.
main.rs / app_handler / web_canvas_server load/save/fingerprint/save_if_changed refs
repointed to op_web_daemon::; persistence keeps crate::settings_io::touch_recent. 11
settings_io tests green; no dep/lock change.
2026-06-19 20:45:30 +08:00
Kayshen-X 24472bdbe7 fix(host): scope test-only AcpAgentProbeOutcome import to the test module
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.
2026-06-19 20:44:54 +08:00
Kayshen-X da195b995b refactor(host): split provider/acp probe hosts; move job structs + probe fns (Phase 4, Task 4.2)
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.
2026-06-19 20:37:00 +08:00
Kayshen-X 97a4854ddf refactor(host): move discovery cluster + refactor poll_into to take EditorState (Phase 4, Task 4.1)
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.
2026-06-19 20:26:48 +08:00
Kayshen-X 00b773e0ad refactor(host): move ai_proxy to op-web-daemon (Phase 3, Task 3.3)
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.
2026-06-19 20:19:05 +08:00
Kayshen-X fe40be77dd refactor(host): move the AI-chat provider cluster to op-web-daemon (Phase 3, Task 3.2)
chat_runtime / chat_claude / chat_copilot / chat_http_server / chat_subprocess /
chat_builtin_http / chat_agent_loop (the mutually-recursive provider + agent-loop
cluster) + their 3 #[path] sibling test files move together. Their
op_web_daemon::chat_attachment/spawn/canvas_tools/subprocess_quirks refs flip to
crate:: (now intra-crate); BlockingRecvIter::new + top-level pub(crate) items
promoted to pub. 17 external refs repointed across web_chat_standard /
chat_session_launch / ai_proxy / design_session / chat_acp / acp_agent_probe_host /
chat_session_tests. op-web-daemon 158 tests green; no dep/lock change.
2026-06-19 20:16:02 +08:00
Kayshen-X 3a11cedbd8 refactor(host): split chat_attachment headless helpers into op-web-daemon (Phase 3, Task 3.1)
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}.
2026-06-19 20:05:48 +08:00
Kayshen-X 2ae343785b refactor(host): carve headless doc-io out of persistence into op-web-daemon (Phase 2, Task 2.3)
The host-free load/save core moves to op_web_daemon::doc_io: save_to_path,
load_editor_state, document_fingerprint, is_supported_document/_figma_import,
ActionOutcome, ErrorKind + the shared EditorState helpers (set_file_name_display,
preserve_app_preferences, active_page_bbox, sidecar_path). The rfd/winit dialog
flow (handle_open/save, run_action, show_error_dialog) stays in op-host-desktop
and imports them back. 23 external refs repointed; codex Issue-4 shared helpers
promoted to pub. Adds op-i18n for the dialog strings. op-web-daemon stays GL-free.
2026-06-19 19:57:28 +08:00
Kayshen-X 2281643d62 refactor(host): move export cluster to op-web-daemon (Phase 2, Task 2.2)
export + export/{scene_painter,screenshot,export_svg} + export_pdf — server-side
PNG/SVG/PDF rasterization via op-host-native's raster NativeFrameBackend (no GL).
Self-contained cluster; promoted to pub; 5 consumers repointed. op-web-daemon
still links zero winit/glutin + skia without gl (verified).
2026-06-19 19:38:51 +08:00
Kayshen-X e9378673ed refactor(host): move pre_validator to op-web-daemon (Phase 2 leaf)
Pre-validation adapter (PlannedFix -> EditorCommand via op-design-lint) for the
design pipeline. Promoted to pub; 2 consumers repointed. Completes Task 2.1
(all 7 host-free leaves moved). No dep/lock change.
2026-06-19 19:28:55 +08:00
Kayshen-X 586a37b73c refactor(host): move chat_system_prompt to op-web-daemon (Phase 2 leaf)
Builds the agent/chat system prompts (skills + design memory) for the chat
bridges. Promoted to pub; 3 consumers repointed. No dep/lock change.
2026-06-19 19:27:26 +08:00
Kayshen-X fa3427631f refactor(host): move chat_provider_llm to op-web-daemon (Phase 2 leaf)
The LlmClient adapter bridging op-orchestrator's LLM trait to op-ai chat
providers. Promoted to pub; 3 consumers (web_chat_standard, chat_intent,
chat_session_launch) repointed. No dep/lock change.
2026-06-19 19:25:52 +08:00
Kayshen-X 429246718a refactor(host): move chat_canvas_tools to op-web-daemon (Phase 2 leaf)
MCP chat-tool execution (tool defs, channel, execute, apply_design_modification,
UiChatToolExecutor) shared by the chat bridges. Items promoted to pub; 7
consumers repointed to op_web_daemon::chat_canvas_tools. No dep/lock change.
2026-06-19 19:23:35 +08:00
Kayshen-X 4bc88e430b refactor(host): move chat_subprocess_quirks to op-web-daemon (Phase 2 leaf)
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.
2026-06-19 19:20:32 +08:00
Kayshen-X 722d584cd1 refactor(host): move chat_spawn to op-web-daemon (Phase 2 leaf)
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).
2026-06-19 19:17:46 +08:00
Kayshen-X 20971e04b4 feat(sdk): add @zseven-w/op-web-sdk TS core (read-only web viewer)
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.
2026-06-19 19:01:14 +08:00
Kayshen-X 00526dec31 chore(host): lock op-host-desktop's op-web-daemon dependency
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.
2026-06-19 18:46:01 +08:00
Kayshen-X 249614aa87 refactor(host): move web_static to op-web-daemon (Phase 2, first leaf)
Establishes the extraction move pattern: git mv module (+ its asset subdir) ->
op-web-daemon, add the op-host-desktop -> op-web-daemon dep edge, register
pub mod, repoint consumers (crate::web_static -> op_web_daemon::web_static in
main.rs + web_canvas_server.rs), and promote the cross-crate items pub(crate)
-> pub (resolve_bundle_dir / handle_static_request / write_static_response /
ICONIFY_BRANDS_JSON / StaticReply). web_static (static file serving for the
--serve-web daemon) now lives in op-web-daemon. Verified: both crates build;
13 web_static tests pass. 6 more leaves + export + doc_io remain in Phase 2.
2026-06-19 18:39:07 +08:00
Kayshen-X 3872c90289 chore: refresh README crate table + lock op-web-daemon entry
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).
2026-06-19 18:29:41 +08:00
Kayshen-X 55d6972f50 fix(host): op-app opts into gl-host on desktop to keep native host API
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.
2026-06-19 18:06:48 +08:00
Kayshen-X d100b07c16 docs: add "Why Rust" section comparing the Rust rewrite to TS/Electron
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.
2026-06-19 18:03:50 +08:00
Kayshen-X a13d2adc53 fix(sdk): export set_viewport/zoom_to_fit/forward_wheel to wasm
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.
2026-06-19 17:58:33 +08:00
Kayshen-X fd1516656a feat(host): scaffold op-web-daemon crate (Phase 1)
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.)
2026-06-19 17:56:01 +08:00
Kayshen-X 75ec668463 feat(sdk): add op-web-sdk read-only web viewer crate
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.
2026-06-19 17:52:12 +08:00
Kayshen-X 70e6e76e29 chore: add default-members + fix stale openpencil-shell-* refs in cargo scripts and crates/CLAUDE.md 2026-06-19 17:49:39 +08:00
Kayshen-X 47e9e3cac6 refactor(host): gate op-host-native GL stack behind opt-in gl-host feature
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.
2026-06-19 17:47:34 +08:00
Kayshen-X 7b20644a23 fix(editor): invalidate the scene cache on incremental node drag
The J4 incremental drag patches layout_scene via translate_nodes without updating
scene_cache.last. If the doc later returns to the cached build's value (undo, or
editor_state_dirty flipping mid-drag) while the scene is patched, maybe_rebuild
would skip and leave the stale patch on screen. Invalidate the cache after the
patch in both hosts so the next refresh always rebuilds (no perf cost — the drag
itself never refreshes). Found by codex review. +native regression test.
2026-06-19 17:37:54 +08:00
Kayshen-X 5cba1700b1 fix(editor): rebuild the scene on document import (invalidate the cache)
The scene-build cache (d3773625) could skip the deferred post-import rebuild
when the imported document's inputs matched the last build. The native
install_imported_state empties layout_scene (it hands the old scene to a
background drop thread), so a skipped rebuild left the canvas blank. Invalidate
the cache on import in both hosts so the next refresh always rebuilds.
Found by the codex stop-time review. +native regression test.
2026-06-19 16:55:51 +08:00
Kayshen-X 194c7d0157 feat(editor): gate preview mode and widget config behind experimental toggle
The canvas Preview (Play) button and the property-panel Widget section are
unfinished surfaces. Add an opt-in "Experimental features" switch in
Settings → System (default off) that gates both:

- TopBar: a `preview_button_visible()` predicate (host capability AND the
  opt-in) drives paint, hit-test, and the globe-anchored locale picker so
  the right cluster never drifts.
- Property panel: the snapshot's `widget` is cleared when off, hiding the
  section (rows + height) with no layout change.

Disabling the toggle exits any live preview (host-owned session on native,
core flag on web) and clears stale Widget property focus so a hidden field
can't commit. Persisted via AgentSettings on desktop (settings.json) and web
(web_settings). Adds settings.experimental(+Desc) to all 15 locales.
2026-06-19 16:42:07 +08:00
Kayshen-X 6ca1c43bba style(editor): rustfmt the measure-cache imports
cargo fmt collapses the multi-line use + tightens an assert in measure_cache.rs
(missed under --no-verify in 06fca41d). No behavior change.
2026-06-19 16:36:34 +08:00
Kayshen-X 76b408ca98 perf(editor): skip the layout-scene rebuild when its inputs are unchanged
editor_state_to_layout_scene is a pure function of the document, the
authored-geometry latch, the active page index, and the resolved variable table
(active theme + ref caches) — it drops selection / hover / chat / viewport. But
the hosts mark the scene dirty on nearly every interaction, and the worst case —
streamed chat / codegen deltas — re-ran a full taffy solve + whole-tree
SceneNode re-allocation once per animation frame for content that never touches
the canvas. New op-pen-loader SceneBuildCache content-compares those inputs and
skips the rebuild when they match (no hand-maintained revision counter, so no
mutation can leave a stale scene). Wired into both hosts' refresh_layout_scene.
2026-06-19 16:35:16 +08:00
Kayshen-X f4eec05ece fix(editor): don't drift locked/hidden nodes in the scene on native node drag
The incremental scene patch built its id list with only the flex-flow filter,
but translate_selected also skips non-editable (locked / hidden) nodes. A
selection containing a locked node would translate it in the scene mid-drag,
then snap it back on the release-time reconversion. Apply the same is_editable
filter the document mutator uses (parity with the web host fix). +regression
test.
2026-06-19 15:30:47 +08:00
Kayshen-X 10daa64ffa perf(editor): patch the scene incrementally on web node drag
A web node drag called mark_dirty() per move, forcing a full serde doc->scene
reconversion + taffy solve on every pixel. Mirror the native host: translate
just the affected absolute scene nodes in place (editable + non-flex-flow,
matching translate_selected) and defer the full rebuild to release. Adds a
regression test that a locked node in the selection does not drift in the scene.
2026-06-19 15:13:24 +08:00
Kayshen-X 7b80dc51e7 perf(editor): cache skia text measurements across reconversions
SkiaMeasure builds + lays out a fresh Paragraph per text leaf — the dominant
cost of the taffy layout pass that re-runs on every editor_state reconversion
(drag / resize / typing). Text rarely changes between reconversions, so wrap the
backend in a memo keyed on the full shaping input; it lives in the existing
thread_local so the cache spans passes. Native only — the web estimate backend
is already cheap, so it stays unwrapped.
2026-06-19 15:13:23 +08:00