Commit graph

1027 commits

Author SHA1 Message Date
Kayshen-X 08c07604d0 chore(ci): re-point tooling + CI workflows at the op-host-* crates
Phase 7.3 strangler reorg — update every reference to the renamed /
dissolved crates across the boundary scripts and CI workflows.

tools/:
- check-wasm-bundle.sh: openpencil-shell-web -> op-host-web; the
  wasm-bindgen output filenames follow the op_host_web lib name
- check-jian-boundaries.sh: shell-native -> op-host-native,
  shell-web -> op-host-web
- check-widget-boundary.sh: WEB_SRC + path-exclusion regexes ->
  crates/op-host-web; openpencil_shell_core::widgets ->
  op_editor_ui::widgets (the dissolved shim's real source crate)

.github/workflows/:
- rust-multiplatform.yml: wasm + mobile-check jobs -> op-host-web /
  op-host-native; the mobile shell-core clean check -> op-editor-ui
- rust-release.yml: cargo build -p openpencil-desktop ->
  -p op-host-desktop (the shipped executable name is unchanged)
- wasm-bundle-check.yml: -p openpencil-shell-web -> -p op-host-web

Cargo.toml skia-patch comment updated. Boundary checks pass; the
wasm-bundle gate cleanly skips while EMSDK is unset.
2026-05-17 00:01:35 +08:00
Kayshen-X 4b8e0ce956 refactor(rust): add op-app composition-root crate
Phase 7.3 strangler reorg — add op-app, the thin crate that names the
editor application's composition root.

Investigation found no shared host bootstrap left to extract: the
editor-UI composition (widgets, theme, layout scene) already lives in
op-editor-ui, and each host (op-host-native / op-host-web) owns only
platform-specific backend wiring. So per YAGNI op-app stays thin — it
re-exports op-editor-ui plus the per-platform host entry point behind
its target cfg, and documents the composition. If real cross-host
wiring later emerges, it lands here.

Builds green on both native and wasm32-unknown-unknown.
2026-05-16 23:57:03 +08:00
Kayshen-X 8733f45e2d refactor(rust): dissolve openpencil-shell-core re-export shim
Phase 7.3 strangler reorg — the final consumer (op-pen-loader) is
repointed off the openpencil-shell-core shim onto op-editor-ui (the
real source crate for the layout scene / scene-var / render-backend
facade), then the shim crate is deleted.

- op-pen-loader: openpencil-shell-core dep -> op-editor-ui;
  every openpencil_shell_core:: path -> op_editor_ui::
- git rm crates/openpencil-shell-core/ (lib + jian.rs module + the
  two re-export anchor tests, all superseded by op-editor-ui's own
  surface; the jian.rs module had no consumers outside the shim)
- stale shell-core / shell-native comment refs in op-editor-core +
  op-host-desktop manifests updated
2026-05-16 23:54:33 +08:00
Kayshen-X 1e0b3cab7a refactor(rust): rename native + desktop hosts to op-host-* crates
Phase 7.3 strangler reorg — rename the native widget host and the
desktop runner crates to the op- prefix. The desktop+native merge was
declined: keeping the library / binary split preserves the mobile-
checkable op-host-native lib (cargo check -p op-host-native on iOS /
Android, relied on by check-jian-boundaries.sh + the CI mobile job),
which a folded-in winit binary would break. A clean separate rename is
purely mechanical and the brief permits it.

- openpencil-shell-native -> op-host-native (lib op_host_native)
- openpencil-desktop -> op-host-desktop crate; the shipped executable
  keeps the stable openpencil-desktop [[bin]] name so release
  artifacts + external CLI integrations are unaffected
- every openpencil_shell_core:: path -> op_editor_ui::
- every openpencil_shell_native:: path -> op_host_native::
- doc-comment / manual-smoke note refs updated
2026-05-16 23:49:58 +08:00
Kayshen-X 65c2aa055a refactor(rust): rename openpencil-shell-web host to op-host-web
Phase 7.3 strangler reorg — rename the web widget host crate to the
op- prefix. The crate's openpencil-shell-core dependency is repointed
to op-editor-ui (the real source crate for the widget facade / theme
/ layout scene / scene vars / render-backend / gesture types).

- crate name: openpencil-shell-web -> op-host-web
- lib name: openpencil_shell_web -> op_host_web
- every openpencil_shell_core:: path -> op_editor_ui::
- native skia.rs include_bytes! path follows the moved assets dir
- smoke harness + lib doc-comment refs updated
2026-05-16 23:42:49 +08:00
Kayshen-X 43cb0fdf7e chore: sync Cargo.lock for the op-* crate extractions
Record the op-codegen / op-figma / op-mcp / op-ai / op-editor-ui
package entries in the workspace lockfile.
2026-05-16 23:35:10 +08:00
Kayshen-X 5f68ab3c1a refactor: extract op-editor-ui crate
Relocate the widget facade (widgets/, including the Widget trait,
render primitives, the editor-UI compositions, the CanvasViewport
center canvas, the lucide icon drawer, and editor_state_ext), the
theme tokens, the layout-resolved render scene (layout_scene +
layout_scene_hit), and the design-variable aggregation (scene_vars)
out of openpencil-shell-core into a dedicated op-editor-ui crate.

The canvas widgets (canvas_viewport*) stay inside op-editor-ui rather
than splitting into a separate op-canvas crate: they depend on the
widgets/ siblings editor_state_ext + icons and on the Widget trait, so
a clean mechanical split is not possible — per the task's explicit
allowance not to force a fragile split.

op-editor-ui's lib.rs mirrors the old shell-core crate-root re-exports
(render_backend facade types + jian gesture types + the i18n alias) so
every intra-module `crate::Color` / `crate::theme` / `crate::widgets`
path resolves unchanged — a pure relocation with no path rewrites
inside the moved modules. openpencil-shell-core becomes a thin
re-export shim (`pub use op_editor_ui::{widgets, theme, ...}`) so the
hosts keep resolving `openpencil_shell_core::*` until the Task 7.3
host rename dissolves the crate. The widgets_static integration test
moves to op-editor-ui/tests with its imports rewritten. The widget
boundary script's reverse-check path is updated to the new crate.
No behaviour change; all tests move with their code.
2026-05-16 23:33:47 +08:00
Kayshen-X 0dc231a5e9 refactor: extract op-ai crate
Relocate the AI chat layer (chat_provider.rs, chat_models.rs,
agent_settings_state.rs) out of openpencil-shell-core into a dedicated
op-ai crate. These three modules are dependency-free transport-free
data shapes — the ChatProvider trait, the ModelEntry catalog type, and
the Cmd+, settings-modal state — so they form a clean wasm32-clean
leaf crate. A fresh lib.rs declares all three as modules; chat_models'
`crate::agent_settings_state::` path stays valid. openpencil-desktop's
chat_*.rs transports + model_discovery.rs now import `op_ai::*`. Pure
relocation, no behaviour change.
2026-05-16 23:24:29 +08:00
Kayshen-X e2e287c008 refactor: extract op-mcp crate
Relocate mcp.rs + mcp/* + mcp_tests.rs out of openpencil-shell-core
into a dedicated op-mcp crate. The MCP tool registry + JSON-RPC stdio
layer only depends on jian-ops-schema + op-editor-core (its tools
build on EditorState / EditorCommand). mcp.rs becomes the crate's
lib.rs; the mcp/ submodule files flatten to src/ so the `pub mod`
declarations resolve unchanged, and intra-module `super::` paths stay
valid. mcp_tests.rs becomes a #[cfg(test)] mod of lib.rs with its
`super::mcp::` paths rewritten to `crate::`. openpencil-desktop's
mcp_serve.rs now imports `op_mcp::*`. Pure relocation, no behaviour
change; 138 tests move with their code.
2026-05-16 23:21:17 +08:00
Kayshen-X 9a0112b9a9 refactor: extract op-figma crate
Relocate figma.rs out of openpencil-shell-core into a dedicated
op-figma crate. The .fig detection + clipboard-node parser only
depends on jian-ops-schema (op-editor-core's PenNodeExt is test-only,
so it becomes a dev-dependency). figma.rs becomes the crate's lib.rs;
no intra-crate paths to adjust. Pure relocation, no behaviour change.
2026-05-16 23:17:05 +08:00
Kayshen-X 0cb56ecd9e refactor: extract op-codegen crate
Relocate codegen.rs + codegen_targets.rs out of openpencil-shell-core
into a dedicated op-codegen crate. The code generators only depend on
jian-ops-schema + op-editor-core, so they form a clean leaf crate.
codegen.rs becomes the crate's lib.rs; crate::codegen:: paths in
codegen_targets.rs collapse to crate::. Pure relocation, no behaviour
change; tests move with their code.
2026-05-16 23:14:45 +08:00
Kayshen-X a18ca7bbfe refactor: delete shell-core's document module
Phase 7 Task 7.1 final step. The `crate::document` module's shared
enum/value types were the last remnant of shell-core's dead `Document`
model. This repoints every consumer onto canonical types and deletes
the module entirely.

- Enum types (`Tool`, `AlignAction`, `FillType`, `FlexLayout`,
  `PropertyTab`, `PropertyFocus`, `ColorTarget`, `Viewport`, `NodeId`,
  `BooleanOp`, `ChatAnchor`, `ReorderDirection`) repointed onto their
  variant-identical `op_editor_core` equivalents.
- `NodeKind` / `Effect` / `DropShadow` are paint-time scene types with
  no canonical home — moved into `layout_scene.rs` (the surviving
  scene type that is their only consumer). `Stroke` consumers switched
  to the existing `SceneStroke`.
- `VariableTable` + friends (`Variable`, `ThemeAxis`, `ThemedValue`,
  `VariableKind/Scalar/Value`) are a shell-core-local paint-time
  aggregate consumed by the `LayoutScene` builder — moved to a new
  crate-root `scene_vars` module.
- `editor_state_ext.rs` converters that became identity now that
  widgets speak canonical types deleted (`doc_tool`, `doc_align_action`,
  `doc_fill_type`, `doc_flex_layout`, `doc_property_tab`,
  `doc_property_focus`). The widget-local→canonical reverse bridges
  (`file_menu_choice`, `shape_choice`, `export_format`) added there.
- `op-pen-loader/bridge_enums_rev.rs` (the SC document→EC enum bridge)
  deleted; hosts feed canonical widget hit-test results straight in.
- Dead `large_document_perf.rs` integration test (exercised the
  removed `Document` struct directly) deleted.

Pure type-repointing — widgets/canvas/export paint identically.
2026-05-16 23:07:57 +08:00
Kayshen-X 4abe80213f refactor(op-pen-loader): delete dead Document-bridge code
With the LayoutScene builder no longer routing through the shell-core
Document model, the EditorState -> paint-Document bridge is fully
dead. Removes editor_state_bridge.rs (apply_editor_state_ui,
editor_chat_to_chat_state, editor_components_to_component_library),
bridge_ui.rs (editor_ui_to_ui_state), the forward bridge_enums.rs
translators, and payload.rs's pen_document_to_document / to_payload /
apply_payload / DocPayload<->Node converters. None had a production
caller.

payload.rs keeps the DocPayload DTOs + load_canonical (live: adapter
builds DocPayload, the LayoutScene builder reads it, persistence.rs
calls load_canonical). bridge_enums_rev.rs stays — its rev:: enum
translators are still used by the native + web hosts.

The shell-core document.rs / document/ model itself is NOT deleted:
~20 shell-core widget files + layout_scene_hit.rs still consume its
enum types (NodeKind, Tool, FillType, PropertyFocus, AlignAction, ...).
Migrating those onto op-editor-core enums is Phase 7 Task 7.2's scope.
2026-05-16 22:42:44 +08:00
Kayshen-X 16a31251b5 refactor(op-pen-loader): build LayoutScene directly from PenDocument
editor_state_to_layout_scene now constructs SceneNodes straight from
the layout-resolved DocPayload (pen_document_to_payload) instead of
routing through the intermediate shell-core Document model. The
DocPayload already carries the resolved geometry + paint fields;
apply_payload's only transforms on them are lossless format
conversions, so the scene output is byte-identical (verified by the
existing layout_scene tests). Adds effects_from_payload_ref so the
builder can rebuild effects from a borrowed payload slice.
2026-05-16 22:37:46 +08:00
Kayshen-X 43cbf2131d refactor(electron): migrate export renderers off Document onto LayoutScene
Rewrite raster (PNG/JPEG/WEBP), SVG and PDF export to consume the
layout-resolved `LayoutScene` instead of shell-core's `Document`. The
export call site builds the scene from the live `EditorState` via
`op_pen_loader::editor_state_to_layout_scene`, so jian's flex pass and
`$ref` fill resolution run once and the exported pixels match the
on-screen canvas painter. Rotation now pivots around `aggregate_bounds`
to match `canvas_viewport_paint.rs`; drop shadows render behind Frame /
Rect / Ellipse fills.

Drops the native host's dead `document()` / `derive_paint_doc` path now
that nothing derives a paint `Document`. Splits the SVG serializer into
`export/export_svg.rs` to stay under the 800-line cap.
2026-05-16 22:28:27 +08:00
Kayshen-X 5dde9e8741 refactor(host): hit-test input against LayoutScene, drop per-frame paint_doc
Both editor hosts now hit-test the input path against the cached
LayoutScene instead of a per-frame derived Document. Hit-test helpers
(node_at_doc_point, selection_handle_at_point, rotation_corner_at_point,
drop_target_at) moved into layout_scene_hit.rs and rewritten onto
SceneNode. The paint_doc field is removed; Document survives only in the
export renderers and the op-pen-loader payload bridge.
2026-05-16 22:18:31 +08:00
Kayshen-X 0b545e8b20 refactor(canvas): flip CanvasViewport paint onto LayoutScene + EditorState
The PAINT path of CanvasViewport was the last widget still consuming
shell-core's Document. It now reads viewport / selection / tool /
pen-draft from op_editor_core::EditorState and walks the layout-
resolved SceneNode tree from a LayoutScene instead of Document Nodes.

- CanvasViewport::from_document → from_editor(&EditorState, &LayoutScene).
- Per-kind painter ported into a new canvas_viewport_paint.rs module
  (walks SceneNode; fills are pre-$ref-resolved so no var_table lookup)
  to keep canvas_viewport.rs under the 800-line cap.
- Overlay painter migrated: paint_fill_then_stroke takes &SceneNode,
  paint_pen_rubber_band reads pen-draft + scene geometry.
- Both hosts cache a layout_scene field rebuilt by
  editor_state_to_layout_scene alongside paint_doc when
  editor_state_dirty is set.
- SceneNode gains find / aggregate_bounds derivation helpers
  (pure geometry over existing fields) for the selection overlay.

The host-input hit-test helpers rotation_corner_at_point /
selection_handle_at_point keep &Document — they serve the input path,
migrated by a later task.
2026-05-16 21:49:29 +08:00
Kayshen-X 9fb8a42036 feat(layout-scene): add layout-resolved render scene type + builder 2026-05-16 21:35:03 +08:00
Kayshen-X e9861a0e21 refactor(panels): migrate PropertyPanel onto op-editor-core EditorState
PropertyPanel now reads the selected node(s) off EditorState: the
NodeSnapshot is built from the canonical PenNode (geometry via
op-editor-core aggregate_bounds, fill/stroke via the fills helpers),
and the panel state (focus/draft/flex/size/fill-type/tab/export) reads
EditorState.ui + editor_ui. PropertyLabels::for_document becomes
for_editor_ui.

The widget keeps shell-core's document-typed NodeKind / PropertyFocus /
FillType / FlexLayout / PropertyTab in its paint + hit-test surface;
variant-identical converters were added to editor_state_ext so the
hosts' input/hit-test path is untouched. Added op-editor-core
fills::node_stroke_width for the Stroke section's scalar width.
Both hosts' paint passes and the input-path hit-test sites updated.
2026-05-16 21:20:42 +08:00
Kayshen-X 9049946794 refactor(panels): migrate LayerPanel onto op-editor-core EditorState
LayerPanel now walks the canonical PenNode tree on EditorState.doc
instead of shell-core's derived Document. The page rows, the
depth-flattened layer rows, collapse state (editor_ui.collapsed_layers),
selection, hover and inline-rename drafts all read from EditorState.

The widget keeps a shell-core document::NodeId in LayerItem / the
hit-test surface — both id types are string newtypes, so the walk-time
conversion is lossless and the hosts' input/hit-test path stays
untouched. Constructors renamed from_document* -> from_editor*; both
hosts' paint passes and the input-path hit-test sites updated.
2026-05-16 21:12:31 +08:00
Kayshen-X 62c5f02f0a refactor(editor): migrate AIChat panel onto op-editor-core chat state
`AIChatPlaceholder` + the model-picker dropdown now read op-editor-core
types directly:

- `ChatState` / `ChatRole` / `ChatMessage` / `ModelEntry` /
  `AgentProvider` swap from shell-core (`document::*` /
  `chat_models::*` / `agent_settings_state::*`) to
  `op_editor_core::chat::*`.
- `AIChatPlaceholder::from_editor[_at]` takes `&EditorState`, reading
  `state.chat` + resolving chrome labels through `editor_state_ext`'s
  `translate` / `theme_for` instead of `Document::t` / `Document::theme`.

Both hosts' paint + click + press call sites feed `&EditorState`. The
desktop model-discovery worker keeps emitting shell-core `ModelEntry`s,
already translated to op-editor-core's at the `editor_state.chat`
write boundary in `model_discovery.rs` — no change needed there.
2026-05-16 20:59:39 +08:00
Kayshen-X 9fe4ef5192 refactor(editor): migrate AgentSettings modal onto op-editor-core state
The five agent-settings widget files now read op-editor-core types:

- `AgentSettings` / `AgentSettingsTab` / `McpCli` / `McpServer` /
  `AgentProvider` swap from `crate::document::*` to
  `op_editor_core::agent_settings::*` (and `chat::AgentProvider`).
- `AgentSettingsPanel` takes `&EditorState`; the four tab modules +
  `agent_settings_i18n::t` thread `&EditorUiState` instead of
  `&Document`, reading `locale` + `settings_input_draft` directly.

Both hosts' construction + hit-test call sites feed `&EditorState`;
the `SelectTab` / `Connect` / `ToggleMcpCli` / `nav_at` paths drop
their shell-core->op-editor-core enum translation shims since the
widget hit enum now carries op-editor-core types end to end.
2026-05-16 20:55:51 +08:00
Kayshen-X 7a4f958ff5 refactor(editor): migrate four modal widgets onto op-editor-core state
ColorPicker, FigmaImport, LayerContextMenu and VariablesPanel now read
op-editor-core types directly instead of shell-core's `Document`:

- ColorPicker / LayerContextMenu take `&EditorState`, swapping
  `ColorPickerState` / `ColorPickerDrag` / `LayerContextMenuState` /
  `LayerContextTarget` to their op-editor-core equivalents. `drag_for_hit`
  returns op-editor-core's `ColorPickerDrag`, dropping the host-side
  shell-core->op-editor-core translation shim.
- FigmaImport stores the active `Locale` directly off `EditorUiState`.
- VariablesPanel reads `doc.variables` / `doc.themes` +
  `ui.variables.active_theme`, snapshotting owned row / chip / theme
  data at construction.

Construction + hit-test call sites updated in both hosts' widget_host
modules to feed `&EditorState` instead of the derived `paint_doc`.
2026-05-16 20:48:55 +08:00
Kayshen-X 3e9f2bb26e refactor(editor): migrate ExportDialog onto EditorUiState
ExportDialog::paint now reads op_editor_core::EditorUiState
(export_format + export_scale) instead of shell-core's Document.
A doc_export_format converter bridges the state-layer ExportFormat
to the widget-layer enum. Native host paint site updated.
2026-05-16 20:33:20 +08:00
Kayshen-X 0567e4f9f0 refactor(editor): migrate chrome-leaf widgets onto EditorState
TopBar / StatusBar / Toolbar / ShapePicker / LocalePicker / FileMenu /
AlignToolbar now read op_editor_core::EditorState (or the narrowest
sub-struct each needs) instead of shell-core's Document. A new
widgets/editor_state_ext.rs provides the theme()/t() derivations and
the Tool / ShapeChoice / AlignAction / FileMenuChoice converters.
Both hosts' paint + hit-test construction sites updated to pass
&self.editor_state.
2026-05-16 20:27:44 +08:00
Kayshen-X e9960e6944 refactor(editor): make EditorState the web host's single source of truth
Mirror the native host migration for `openpencil-shell-web`. The web
`WidgetHost` now holds an `op_editor_core::EditorState` authoritatively
instead of a shell-core `Document`. The ~30 shared widgets stay
`&Document`-bound and read-only — they are fed a derived `paint_doc`
snapshot rebuilt lazily by `refresh_paint_doc()` whenever
`editor_state_dirty` is set.

- Every mutation routes through an `op-editor-core` mutator + flags the
  dirty bit; the snapshot re-derives once before paint / before any
  hit-testing input event.
- Hit-test-then-mutate handlers refresh, hit-test on `&paint_doc`,
  extract owned results, then mutate `editor_state`; shell-core hit
  enums translate via `op_pen_loader::rev::*`.
- `paint` takes `&mut self` to drain the cache at the top of the pass.
- `op-editor-core` / `op-pen-loader` are optional deps behind the
  `skia` feature (matching `skia-safe`) so the skia-free wasm32 CI stub
  baseline stays clean — `op-pen-loader` pulls `jian-skia` transitively.
- Fix two pre-existing `--features skia` build blockers in `lib.rs`
  (missing `Performance` web-sys feature, `canvas` moved before reuse).

No web-editor behaviour change — feature parity preserved.
2026-05-16 20:13:42 +08:00
Kayshen-X 1b310c714a fix(editor): address host-migration review concerns (stale-doc hit-tests, dirty-mark, legacy load, view-state persist, fill payload)
- CursorMoved refreshes the derived paint Document before cursor_hint
  so a post-mutation / pre-paint cursor move can't read stale geometry.
- apply_cursor_move refreshes the paint doc up front so every hit-test
  inside it (align toolbar, color picker, layer menu, panel resize)
  sees a fresh document.
- commit_variable_row_focus_if_any routes every exit through one
  mark_dirty so an invalid variable edit can't leave the cache stale.
- Legacy private DocPayload files are detected and rejected with an
  explicit "saved by an older version, must be re-saved" message
  instead of a silent / opaque schema parse failure.
- active_page_index is persisted in a minimal .opmeta sidecar so it
  survives a save / load round-trip; the .op file stays canonical.
- set_primary_fill_type carries the existing fill body across a
  fill-type change (gradient stops / opacity / blend mode) instead of
  overwriting it with a default body, matching shell-core behaviour.
- collapsed_layers documented as intentionally view-only at the
  EditorSnapshot and editor_ui_state sites.
2026-05-16 19:53:08 +08:00
Kayshen-X f0bb56b6cd refactor(editor): make EditorState the native host's single source of truth
Flip WidgetHostNative off shell-core's Document onto
op_editor_core::EditorState. The host now owns one authoritative
state; every paint pass + hit-test reads a lazily-derived,
read-only Document snapshot (`paint_doc`), rebuilt via
op-pen-loader's pen_document_to_document + apply_editor_state_ui
whenever an `editor_state_dirty` flag is set.

Why: removes the dual-state strangler scaffold so the canonical
PenDocument model is the only editable state — input handlers
mutate EditorState, widgets stay read-only over the derived
Document. .op save now serializes editor_state.doc; load seeds
EditorState::from_document. shell-core's Document becomes a
paint-only target.

- op-editor-core: add host_support (EditorState::sample,
  create_node_for_tool, replace_paths_with_polyline) + re-export
  VariableScalar/VariableKind.
- shell-native: rewrite all 8 widget_host submodules onto
  editor_state; split press.rs -> press_helpers.rs and keyboard
  click routing -> click.rs to stay under the 800-line cap;
  boolean_ops becomes a pure compute_boolean_op committed back
  through EditorState.
- desktop: persistence / settings_io / chat_session /
  model_discovery / main / frame move onto editor_state accessors.
2026-05-16 19:29:34 +08:00
Kayshen-X 58346c2a9c feat(op-editor-core,op-pen-loader): host-migration prerequisites (fill-type, chat-model, layer-collapse, panel predicates, reverse enum bridge) 2026-05-16 18:54:04 +08:00
Kayshen-X 302c9750d6 feat(op-pen-loader): bridge EditorState UI/chat/components into a paint Document 2026-05-16 18:34:27 +08:00
Kayshen-X a79dbaf0e3 feat(op-editor-core): close variable/theme gaps for the host migration
Three additive variable-handling fixes so the editor host can migrate
onto EditorState as a mechanical port:

- Gap 1: add `variable: Option<String>` to ColorPickerState, port
  `open_color_picker_for_variable`, and route `color_picker_set_hsv` /
  `close_color_picker` through `set_variable_color` when set.
- Gap 2: add `op_pen_loader::editor_state_var_table(&EditorState)`,
  folding persisted variables/themes plus the transient active-theme
  selection + ref caches into a shell-core VariableTable. Lives in
  op-pen-loader (not op-editor-core) to keep op-editor-core wasm-clean
  and free of any shell-core dependency.
- Gap 3: confirmed undo/redo of variable create/delete/rename round-
  trips for free (EditorSnapshot clones the whole PenDocument); added
  tests to lock it in.
2026-05-16 18:19:35 +08:00
Kayshen-X 77f89b6ef8 refactor: extract op-pen-loader (canonical .op → Document) into a shared crate 2026-05-16 18:04:46 +08:00
Kayshen-X 88e155b324 refactor(op-editor-core): rename UiChrome → EditorUiState (drop the 'chrome' term) 2026-05-16 17:51:39 +08:00
Kayshen-X ec5ac70118 feat(op-editor-core): extend EditorState into a full state superset of Document 2026-05-16 17:47:53 +08:00
Kayshen-X b9d7032474 refactor(host): scoped EditorState bridge on WidgetHostNative
Phase 6 strangler scaffolding: WidgetHostNative now holds an
op_editor_core::EditorState alongside the legacy shell-core Document
so the ~30 widgets can migrate onto the canonical model one group at
a time while the workspace stays build-green.

The bridge is intentionally minimal — Document remains the source of
truth (every un-migrated widget paints from it and every apply_*
mutates it), and editor_state is not yet wired into paint or input.
There is no Document -> PenDocument converter, and the desktop
pen_doc_adapter only goes the other way (baking flex layout into AABB
rects, which is irreversible), so a per-frame round-trip would be
unsound. Each later 6.x task adds its own per-group sync point as it
switches a widget group's paint/input onto editor_state.

Field, accessors and dependency are all marked TEMPORARY — deleted in
Phase 7 when Document dies and editor_state becomes the host's only
state.
2026-05-16 17:30:44 +08:00
Kayshen-X 8078a70d8c refactor(figma): port figma import onto PenNode
FigmaClipboardNode::to_node now produces canonical
jian_ops_schema::PenNode instead of shell-core's flat Node. Maps the
8 common Figma kinds onto Frame/Group/Rectangle/Ellipse/Line/Polygon/
Path/Text variants; unmapped kinds fall back to Frame with the
original kind preserved in base.role since PenNode has no Other
escape hatch. Tests ported to assert on PenNode variants + base
accessors. The import stays in shell-core (Option B, in-place port).
2026-05-16 17:21:35 +08:00
Kayshen-X fa6fe21ed5 refactor(codegen): port codegen onto PenDocument 2026-05-16 17:15:44 +08:00
Kayshen-X 39162ad08e refactor(mcp): port MCP server onto op-editor-core EditorCommand
Port the in-process MCP server off shell-core's legacy `Document` /
`McpCommand` onto `op_editor_core::EditorState` / `EditorCommand`. The
module physically stays inside `openpencil-shell-core` (an `op-mcp`
crate extraction is a later Phase-7 task).

- Read tools snapshot `EditorState` (canonical `PenDocument`); write
  tools emit `op_editor_core::EditorCommand` applied via
  `EditorState::apply`. Node ids are now canonical `.op` schema
  strings, not the old `u64`.
- Component commands surface a clean `ToolFailed` "known gap" error —
  `op-editor-core` has no component registry yet. Same for
  `set_node_collapsed` (`NodeFlag::Collapsed` has no schema field).
- `mcp_serve.rs` loads the `.op` file straight into an `EditorState`
  (plain `jian-ops-schema` deserialization) and saves the
  `PenDocument` back on every successful write.
- Delete the orphaned legacy apply path (`document/mcp_apply*.rs` +
  `Document`/`VariableTable::apply_mcp_command`); the widgets that use
  `Document` are untouched.
- Split `tools.rs` → `tools.rs` + `read_tools.rs` and
  `component_tools.rs` → `component_tools.rs` + `page_tools.rs` to
  hold the 800-line cap.
2026-05-16 17:07:42 +08:00
Kayshen-X 6cd2b5cd8c feat(op-editor-core): EditorCommand DTO + EditorState::apply 2026-05-16 16:32:44 +08:00
Kayshen-X 3e04f9a261 feat(op-editor-core): port color/align/variables/rename mutators 2026-05-16 16:21:12 +08:00
Kayshen-X 9d9862a099 feat(op-editor-core): port editing mutators onto EditorState 2026-05-16 16:10:36 +08:00
Kayshen-X 58a4ee58e0 feat(op-editor-core): EditorState on jian PenDocument 2026-05-16 15:54:32 +08:00
Kayshen-X 8f8b36b76f docs: fix stale i18n + renderer paths flagged by Codex review 2026-05-16 15:45:54 +08:00
Kayshen-X 01731d6af8 refactor(document): migrate test code to string NodeId 2026-05-16 15:42:46 +08:00
Kayshen-X 0174aba93a refactor(render): move RenderBackend trait into op-editor-core 2026-05-16 14:36:09 +08:00
Kayshen-X b67fe5fbcc chore: scaffold op-editor-core crate 2026-05-16 14:32:53 +08:00
Kayshen-X 0ab38e1ca0 fix(i18n): point convert-locales.py output at the op-i18n crate 2026-05-16 14:25:58 +08:00
Kayshen-X 65e2dd9ebb chore: bump vendor/jian to the clippy-clean commit 2026-05-16 14:17:31 +08:00
Kayshen-X 08686c3b95 refactor(i18n): extract op-i18n crate (with Locale) from openpencil-shell-core 2026-05-16 14:13:38 +08:00
Kayshen-X 1ef513d5a6 chore: bump vendor/jian to the relocated-renderer commit 2026-05-16 13:57:08 +08:00