Addresses the remaining round-4 codex findings (the `short_src`
byte-slice panic + the arc-handle reverse-iteration fixes already
landed via an earlier sweep).
- `cmd_set_ellipse_arc` clamps `sweep_angle` to ±360° — an API /
MCP sweep beyond a full turn just over-draws; it now persists a
sane single-revolution value.
- Path hit-test (`point_in_node`) follows the flattened, bezier-
aware outline instead of the bounding box: a curved or thin path
no longer selects empty bbox space, a zero-height stroked path
stays clickable, and a filled closed path is hittable across its
interior (new `point_in_polygon` even-odd test).
- The viewport-less `apply_release` now commits / clears
`path_anchor_drag` + `arc_handle_drag` (parity with
`apply_release_with_viewport`) so a drag can't leak across that
release path.
op-editor-core 242 / op-editor-ui 157 / op-host-native 21 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
CI's Rust Check runs a workspace-wide `cargo fmt --check`. Reformat the
new op-acp / op-ai-skills crates and the Part A chat changes to rustfmt
canon. Also sweeps two files an earlier commit left non-compliant
(canvas_viewport_paint.rs, op-pen-loader/adapter.rs) so the workspace
check is clean. Formatting only — no behaviour change.
ChatRequest gains an attachments field; ChatState carries a per-turn
thinking mode, effort level and staged attachments (capped at 4 files /
5 MiB, attachment-only sends allowed). The chat panel grows a controls
row (thinking / effort / attach) and a dedicated attachment-chip row,
with hit-test and paint sharing one input-block origin.
All five providers consume the knobs — Claude maps thinking onto the
SDK token budget, Copilot onto reasoning_effort, the CLI / built-in
transports prepend an in-band directive, the HTTP transport adds body
fields; attachments spill to a private per-turn temp dir (cleaned on
drop) or inline base64. Also wires op-ai-skills into the built-in
provider and op-acp in as the AcpProvider chat backend.
Port of the TS packages/pen-ai-skills engine: a hand-rolled frontmatter
parser (inline / multi-line / next-line / block-list YAML forms), phase
filter + keyword/flag intent matching, token budgeting, the resolve
pipeline, design-context + generation-history memory, and style-guide
selection/parsing. The 95-file skill markdown corpus is embedded
verbatim via include_dir; the crate stays wasm-clean.
Port of the TS packages/pen-acp: ndJSON transport, a hand-rolled
JSON-RPC engine (request-id correlation, session/update notification
routing, session/request_permission auto-approval, and a pending-request
drain on EOF so a dead agent fails fast instead of timing out), and an
AcpConnection driving initialize / session/new / session/prompt over
local stdio or a remote WebSocket. The event adapter maps ACP session
updates onto the chat panel's ChatDelta vocabulary.
The round-3 closed-path fill left the stroke loop unconditional, so
a closed path with a fill but no explicit stroke was filled AND
outlined in the fill colour. Now the stroke paints only for an
explicit `node.stroke`, or — with no stroke — only when the path is
unfilled (so a bare path stays visible). op-editor-ui 176 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Addresses the 3 CONCERNs from the third codex review.
- `signed_sweep`: a negative-sweep drag that collapses onto the fixed
endpoint now snaps to a full -360° circle instead of 0° (mirrors
`norm_sweep`'s positive 0 → 360 rule) — a negative arc no longer
silently loses its sign in that degenerate case.
- Path fallback sizing (`path_to_payload`): an unsized path now
derives its width/height from the handle-aware
`path_bounds_from_anchors` (cubic extrema included) instead of the
endpoint-only point bbox, so handles bowing past the anchors no
longer under-size the scene node.
- Closed-path fill: a closed `Path` with a fill now paints its
enclosed area via `fill_polygon` over the flattened outline — was
stroked-only, so authored fills rendered as bare outlines.
Verified against op-editor-core 239 / op-pen-loader 21 / op-editor-ui
155 (op-host-native not test-built — an unrelated in-progress
AIChat thinking/effort change in the working tree leaves its
`AIChatHit` match non-exhaustive; `signed_sweep` is a pure-fn change).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Addresses the 4 CONCERNs from the second codex review.
- No-op undo: arc + path/handle drags now mutate nothing until the
cursor first travels — a press-release leaves the document and
undo stack untouched. Once the drag has moved, every event keeps
writing (so a drag back to the start point still lands), gated on
`is_move || already_moved`. This closes the hole where a ghost-
handle press-release created a handle with no undo entry.
- Negative arc sweep: `signed_sweep` keeps the sign of the arc being
dragged, so an MCP-authored counter-clockwise (negative) sweep no
longer flips to the major arc under a canvas drag.
- Closed paths: `path_closed` threaded through NodePayload +
SceneNode; `flatten_path` appends the last-anchor → first-anchor
closing segment (cubic or straight) so a closed canonical path
draws its closing edge.
op-editor-core 235 / op-pen-loader 21 / op-editor-ui 148 /
op-host-native 64 / op-host-desktop 21 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A negative `sweep_angle` covers the angular range `[start + sweep,
start]`; the hit-test compared against `sweep.abs()` from `start`,
rejecting points that are actually inside such an arc. Normalise to
a forward sweep before the angle-window test.
Self-review follow-up to the arc/pen-handle work (the codex second
review stalled mid-run). op-editor-ui 148 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Addresses the first codex review of the arc + pen-handle features.
BLOCKs:
- Rotation: the arc + pen-handle overlays and the host hit-tests now
account for node rotation. The overlay paint wraps in save/rotate
like the selection overlay; the hit-tests + drag math un-rotate the
cursor into the node's local frame via `rotate_point`.
- Arc ellipse hit-test: a pie / arc / donut now hit-tests against the
actual sector — the missing wedge + the donut hole are no longer
selectable (was always the full oval).
- Arc no-op undo: `ArcHandleDragState` gains `start_doc`; the move
handler gates `moved` on real cursor travel so a press-release
pushes no undo entry.
CONCERNs:
- Path bounds: the bezier handle-aware bounds algorithm moved into a
shared `op_editor_core::path_bounds`; `refit_path_bounds` uses it
(and is now called after handle / point-type edits) so the loader's
absolutize scale stays 1.0 — a handle no longer rescales the path.
- `cmd_set_ellipse_arc` now honours the locked/hidden editability
guard like the other geometry mutators.
- A full-ring donut strokes its two concentric ovals instead of the
polygon, so the radial seam is not drawn.
op-editor-core 235 / op-editor-ui 148 / op-pen-loader 21 /
op-host-native 64 / op-host-desktop 21 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Completes pen bezier-handle editing on top of the Stage-1 data model.
- canvas_viewport_paint: `flatten_path` — Path nodes with anchor
control handles render as tessellated cubic Beziers (16 steps /
segment); handle-free paths keep the straight `points` polyline.
- canvas_viewport: the Pen-tool anchor overlay now draws each
anchor's two control handles (line + dot), with a faint "ghost"
dot offset from the anchor when a handle is unset — grab it to
create the handle. `path_handle_positions` resolves real / ghost
handle positions, shared with the host hit-test.
- op-host-native: `path_anchor_hit` now distinguishes anchor body
vs handle_in / handle_out (`AnchorDragTarget`); `PathAnchorDragState`
carries the target, the anchor's fixed position, and the Shift
state. The move handler drags the anchor or a handle — a handle
drag sets the anchor's point type on first motion (Shift =
independent/broken, else mirrored/smooth) so `set_path_anchor_handle`
mirrors the opposite handle. Release commits history only on
actual motion.
op-editor-ui 148 / op-host-native 64 / op-host-desktop 21 /
op-pen-loader 21 tests green (+3 flatten-path units).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Stage 1 of pen bezier-handle editing — the data + command layer.
- op-editor-core: `EditorState::set_path_anchor_handle` (set / clear
an anchor's in/out handle; a `Mirrored` anchor keeps both handles
collinear) + `set_path_anchor_point_type` (switching to `Mirrored`
snaps the handles collinear). New `PathHandleSide` enum.
- op-pen-loader: `AnchorPayload` (absolute-coord anchor + resolved
handles + point-type code) on `NodePayload.path_anchors`;
`absolutize_path_anchors` now resolves the schema's anchor-relative
handle deltas into the same absolute frame as `points`.
- op-editor-ui: `SceneAnchor` + `ScenePointType` on
`SceneNode.path_anchors` so the painter + host can read the
editable handles.
op-editor-core 234 / op-pen-loader 21 / op-editor-ui 145 tests green
(+3 handle-setter units).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds the canvas drag handles that author an ellipse arc — start
angle, sweep (end) angle, and the donut inner-radius.
- op-editor-ui: `ArcHandle` enum + `arc_handle_positions` (doc-space
positions of the 3 handles for an Ellipse scene node), re-exported
from `widgets`. The canvas overlay paints them as filled
primary-tinted dots for a single-selected Ellipse with the Select
tool.
- op-host-native: `ArcHandleDragState` + `arc_handle_hit` (checked
before the resize handles since the sweep grip can overlap the
right-mid resize handle). Press starts the drag with a history
snapshot; each move recomputes start/sweep/inner from the cursor
via `arc_drag_command` and re-applies `SetEllipseArc` (no-history
apply); release commits the snapshot only when the arc changed.
Dragging the start handle keeps the end fixed; a sweep collapsing
to 0 snaps to a full 360° circle.
op-editor-ui 145 / op-host-native 64 / op-host-desktop 21 tests
green (+2 arc-handle-position units).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ellipse nodes with authored arc geometry (`start_angle` /
`sweep_angle` / `inner_radius`) were always painted as a full oval —
the arc fields never reached the painter.
- NodePayload + SceneNode gain `arc_start_angle` / `arc_sweep_angle`
/ `arc_inner_radius`; threaded through `ellipse_to_payload` and
`node_payload_to_scene`.
- canvas_viewport_paint: new `arc_polygon` tessellator (pie wedge =
centre + outer arc; donut sector = outer arc + reversed inner arc)
+ `paint_ellipse` — full oval when no arc is authored, otherwise a
filled/stroked polygon. A 360° sweep with no donut hole still
short-circuits to the plain oval path.
Prep for the arc canvas drag-handle UI. op-editor-ui 143 /
op-pen-loader 21 / op-host-desktop 64 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Addresses the 4 BLOCKs from the second codex review.
- kiwi: schema field-count pre-allocation is capped at the remaining
buffer size (a hostile count no longer forces a huge Vec alloc).
- kiwi: array-length validation + pre-alloc now use the *remaining*
byte count rather than the total buffer length (tighter bound).
- zip_reader: the aggregate-size budget is checked against the
central directory's declared uncompressed size *before* the entry
is decompressed, not after.
- zip_reader: stored (uncompressed) entries are now subject to the
per-entry MAX_ENTRY_SIZE cap, matching the deflate path.
op-figma 84 tests green; clean build.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Addresses the first codex review of the binary `.fig` parser.
BLOCKs:
- kiwi: 64-bit varint now uses Kiwi's terminal-byte rule (eight
7-bit groups then a final full-8-bit byte) — the old `& 127` mask
on every byte corrupted u64 values above 2^56.
- kiwi: an invalid schema definition kind (> 2) is now rejected
instead of silently treated as a message.
- kiwi: array decode rejects a length exceeding the buffer size —
guards against a hostile zero-byte-element array spinning the
decode loop billions of times.
- zip_reader: aggregate 2 GiB decompression budget + 10k entry cap
on top of the existing per-entry limit (zip-bomb defence).
CONCERNs:
- detect_kind now recognises the `PK\x03\x04` ZIP magic as Binary —
the common Figma export form (`canvas.fig` + `images/` in a ZIP)
was being rejected before container.rs could unwrap it.
- resolve_style_references now also resolves style refs inside
instance `symbolData.symbolOverrides` entries.
- kiwi: enum field type codes are no longer resolved (unused; kiwi
writes 0) so a stray code can't reject a valid schema.
Plus a zip-wrapped end-to-end test + the misleading zstd test rename.
op-figma 84 tests green (+1); clean build.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Stage G — the binary `.fig` parser is now end-to-end functional;
`parse_fig` no longer returns NotYetImplemented for binary input.
- image_resolver.rs: resolve_image_blobs walks the converted document
and replaces `__blob:N` / `__hash:HEX` image-fill placeholders with
base64 `data:` URLs (MIME sniffed from magic bytes).
- lib.rs: new `parse_fig_binary(bytes, name, layout_mode) -> FigImport`
runs the whole pipeline — container split → Kiwi decode → tree
build → node conversion → image resolution. `parse_fig`'s Binary
arm delegates to it; `FigParseError::NotYetImplemented` retired in
favour of `Binary(String)`. Entry points re-exported.
- binary_e2e_tests.rs: assembles a real fig-kiwi container from
scratch (hand-built Kiwi schema + data chunks, deflate-compressed)
and asserts the full pipeline yields a PenDocument with the
rectangle at the right position/size + canonical-JSON round-trip.
op-figma 83 tests green (+6); clean build, no warnings.
Closes the §2.1 P0 gap — Figma binary `.fig` parsing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Stage 3 — ports figma-types.ts + fig-parser.ts::parseFigFile.
- FigGuid / FigMatrix / FigColor / FigVec2: the geometric primitives
worth typing, each extracted from a decoded FigValue object;
FigGuid::to_key is the canonical `sessionID:localID` map key.
- BlobOrString: the `blobs` pool element (raw geometry bytes | string).
- FigmaDecodedFile { node_changes, blobs, image_files }.
- parse_fig_file: the full pipeline — fig_to_binary_parts → decode the
Kiwi schema chunk → decode the data chunk against it → extract
nodeChanges (with the fallback scan for a guid-bearing array) +
blobs. Mirrors parseFigFile / extractBlobs.
- FigValue gains generic get_f64/get_str/get_bool/get_array accessors.
op-figma 35 tests green (+5).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Stage 2 of the binary `.fig` parser — ports the `kiwi-schema` subset
the format needs.
- ByteBuffer: LEB128 var-uint/int (32 + 64-bit, zigzag), Kiwi's
exponent-rotated var-float (single-0-byte zero optimisation),
NUL-terminated UTF-8 strings, length-prefixed byte blocks.
- decode_binary_schema: the self-describing schema chunk — definitions
(enum/struct/message) + fields; native type codes resolve via
`!code` into NATIVE_TYPES, definition codes by index (forward refs
handled with a two-pass bind).
- FigValue: dynamically-typed decoded tree (the Rust stand-in for the
TS untyped `any`) with get/as_f64/as_str/as_bytes/as_array accessors.
- decode_message: dynamic decoder (the `compileSchema` + `decode*`
equivalent) — message id-prefixed fields, ordered structs, enum
ordinal→name, `byte[]` as a raw block, MAX_DEPTH recursion guard.
Roots at the `Message` definition like Figma's findDecoder.
Tested via an in-test Kiwi writer that round-trips schema + data
fixtures (enum/struct/message/arrays/byte-arrays/floats). op-figma
30 tests green (+7).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
First stage of the Figma binary `.fig` parser port (op-figma was
clipboard-JSON only; binary returned NotYetImplemented).
- zip_reader.rs: hand-rolled minimal ZIP reader — EOCD scan, central
directory walk, store (method 0) + raw-deflate (method 8) entries.
Avoids the full `zip` crate dependency tree. 512 MiB per-entry cap.
- container.rs: ports `fig-parser.ts::figToBinaryParts` — unwraps the
ZIP archive form (`canvas.fig` + `images/*`), verifies the
`fig-kiwi` magic, splits length-prefixed chunks, decompresses each
via the deflate→zstd→raw fallback chain (PNG payloads passed
through). Output: decompressed parts + embedded image map.
- deps: flate2 (miniz_oxide pure-Rust backend), ruzstd, base64 — all
license-clean + wasm32-buildable.
Modules are `allow(dead_code)` until `parse_fig` is wired in the
final stage. op-figma 23 tests green (+6).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
`property_panel_sections.rs` reached 862 lines after the effect
parameter-stepper work — over the 800-line repo file gate. Move
`paint_effects_section` + `paint_effect_row` + `paint_effect_param_row`
into a new `property_panel_effects.rs` (128 lines) and re-export
`paint_effects_section` from `property_panel_sections` so callers
keep using `sections::*`.
`property_panel_sections.rs` is now 755 lines. No behaviour change;
op-editor-ui 140 / op-host-desktop 64 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Completes the Effects-controls gap — each effect row now exposes its
editable scalar parameters:
- op-editor-core: `EffectField` enum + `EditorCommand::SetEffectParam`
+ `cmd_set_effect_param` — writes one shadow param (offset X/Y,
blur, spread) or a blur/background-blur radius; blur values clamp
to >= 0, field/effect mismatches reject.
- panels: each effect block paints a parameter row per field —
`<label> <value> [-] [+]`; the "-"/"+" steppers emit
`AdjustEffectParam` (the walker computes the post-step value from
the current one). `effects_section_height` / the action-rect
walker now take the effects slice so the variable per-kind block
height stays aligned with paint (`VisibleSections.effect_count`
retired in favour of the slice).
- both hosts dispatch `AdjustEffectParam` via `SetEffectParam`,
history-committed.
op-editor-core 233 / op-editor-ui 140 / op-host-desktop 64 tests
green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The property panel's Effects section painted only a header + the
"+" add affordance. It now lists the selected node's effects:
- op-editor-core: `node_effects` read accessor exposes a node's
`PenEffect` slice (container kinds via `container`, leaf shapes
directly).
- panels: `NodeSnapshot.effects` carries an `EffectSummary` per
effect; `paint_effects_section` paints one row per effect (type
label + a "✕"); `VisibleSections.effect_count` + the shared
`effects_section_height` keep paint and the action-rect walker's
y-math aligned through the now-variable-height section.
- `PropertyPanelAction::RemoveEffect(index)` — both hosts dispatch
it through `EditorCommand::RemoveNodeEffect` (history-committed).
Stage 1 of the Effects-controls gap (rows + add + remove); the
per-effect shadow/blur parameter inputs follow. op-editor-core 227 /
op-editor-ui 161 / op-host-desktop 64 tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
`command_tests.rs` had grown to 906 lines past the repo's 800-line
cap as the flip / ellipse-arc / node-effect command tests landed.
Move those per-node-attribute apply tests into a sibling
`command_attr_tests.rs` (both files now well under the cap); the
core CRUD / selection / variable apply tests stay in place.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Effects were inert — the schema carries `PenEffect` (Shadow / Blur /
BackgroundBlur) but nothing could add or drop one.
- editor: `EditorCommand::AddNodeEffect` / `RemoveNodeEffect` +
appliers. `node_effects_slot` reaches the `effects` field on every
variant that has one (Frame/Group/Rectangle via `container`, the
leaf shapes directly); `add` appends a default-parameter effect,
`remove` drops by index and clears the list to `None` when empty.
- mcp: `add_node_effect` / `remove_node_effect` tools, registered on
the host server (catalog 80 -> 82).
This is the command + MCP layer of the Effects gap; the property-
panel editing UI is the remaining piece. op-editor-core 227 /
op-mcp 138 / op-host-desktop mcp tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Completes the smart-guides gap on top of the align_guides geometry:
- editor: EditorUiState.active_guides — transient guide lines for the
current drag (view-only, never serialized / undone).
- host: apply_smart_guides() runs after each node-drag translate —
gathers the moving + sibling AABBs off the layout scene, calls
compute_alignment_guides, applies the snap offset, stores the guide
lines; drag release clears them.
- canvas: paints the active guide lines (magenta) over the nodes.
The "grid" half of the matrix row was already implemented
(canvas_viewport::paint_grid). op-editor-ui 140 + op-host-desktop 64
tests green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
`align_guides.rs` — pure alignment-guide computation for node
dragging: given the moving node's AABB + sibling AABBs it returns the
guide lines to paint and the snap offset that locks the drag onto the
nearest edge/centre alignment within a threshold. Two axes resolved
independently; closest candidate per axis wins, edge-to-edge
preferred on a tie.
This is the computation core of the smart-guides gap — fully unit
tested (6 tests). Host drag-handler wiring + canvas paint of the
returned guides follow as separate steps.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes six verified gaps from the 2026-05-17 TS-vs-Rust gap analysis,
each build- and test-green:
- editor: SetNodeFlip + SetEllipseArc commands (+ set_node_flip /
set_ellipse_arc MCP tools) — schema already had the fields, only
the command path was missing.
- export: export_node_raster crops a raster to one node's bbox;
File -> Export is now selection-aware (single selection -> layer).
- editor: SVG import — hand-rolled parser (shapes + path M/L/H/V/
C/S/Q/T/Z) in svg_import.rs; cubic curves flatten to dense straight
anchors at import time so the renderer/pen-tool stay on their 1:1
straight-segment model. + EditorCommand::ImportSvg + import_svg tool.
- mcp: HTTP transport — mcp_serve::run_http serves MCP over a
TcpListener (--mcp-http <port> <path>); process_message is shared
with the stdio path.
- cli: new op-cli crate (binary `op`) — a dependency-free HTTP MCP
client driving every tool via `op <tool> key=value...`.
- ai: ChatRequest gains thinking/effort fields (ThinkingMode /
EffortLevel, defaults Adaptive/Low to match the TS runtime config).
MCP tool catalog 77 -> 80. Oversized files split to honour the
800-line cap (svg_import, export, mcp_serve, adapter).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- chat_subprocess.rs: `std::os::windows::process::CommandExt` is unused
on Rust 1.94 (`creation_flags` resolves without it, like the unix
`process_group` twin) — rejected by clippy `-D warnings` on Windows.
- deny.toml: remove the tokio process/rt-multi-thread feature ban; the
native agent runtime legitimately needs them and the native cargo-deny
job tripped on it. wasm exclusion stays structural.
The op-* crate reorg never ran `cargo clippy -- -D warnings`, so the CI
lint gate failed. Fix every violation surgically: real fixes for
mechanical lints (needless_range_loop, derivable_impls, ptr_arg,
needless_borrow, doc_lazy_continuation, unused_imports, manual_find,
collapsible_match, never_loop, dead_code, complex types via type
aliases) and scoped `#[allow]` for intrusive ones (too_many_arguments
on paint helpers, result_large_err where ToolOutcome / PenNode payloads
are deliberately the Err type).
The dependency referenced a sibling working copy (../../../agent-rs)
that only exists on a local dev machine, so cargo metadata failed in
CI. vendor/agent is the same agent-rs repo as a git submodule (CI
checks out submodules recursively) and is pinned to the identical
commit, so the build is unchanged.
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.
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