openpencil/crates
Kayshen-X a6d208d313 feat(mcp): insert_node tool — third write (the big one)
Third MCP write tool, completing the "create + style + theme" write
surface for LLM clients. `insert_node` is the TS pen-mcp
equivalent's flagship capability — without it, LLMs can only
modify existing nodes; with it, they can build entire designs.

`crates/openpencil-shell-core/src/mcp.rs`:
  - `McpCommand::InsertNode { kind, name, x, y, width, height,
    fill_hex }`. fill_hex is `Option<String>` — color-bearing
    shapes can carry a fill; structural nodes (frame/group)
    pass None.

`crates/openpencil-shell-core/src/document/mcp_apply.rs` (NEW):
  - `Document::apply_mcp_command(cmd)` — lifted to Document level
    so InsertNode can reach Pages + the id allocator (variable +
    theme commands still route to `var_table.apply_mcp_command`).
  - `Document::next_node_id_seed()` — allocates a fresh id past
    `max_node_id() + 1`, saturating_add-guarded so u64::MAX
    returns 1 instead of colliding with NodeId::NONE.
  - `parse_node_kind(s)` — accepts the same lowercase strings
    the read-side tools (get_node, get_selection) emit, so an
    LLM can round-trip a node's kind through read → modify →
    re-insert without re-encoding.
  - Pulled out of mutators.rs to keep that file under 800 lines.

`crates/openpencil-shell-core/src/mcp/tools.rs`:
  - `InsertNode` (stateless tool struct) + `insert_node_snapshot()`
    factory. No document snapshot needed — the tool doesn't need
    to know the current state; the host's applier handles id
    allocation + bounds installation.
  - `McpTool::call` validates:
      - All required args present (kind / name / x / y / width /
        height); each `MissingArgument` carries the missing
        name.
      - kind in ALLOWED_KINDS (frame / group / rect / ellipse /
        polygon / line / text / path).
      - x / y / width / height parse as decimal i32 (negative x/y
        allowed for nodes placed off the page-origin; width /
        height must be non-negative).
      - Optional fill_hex parses as #rgb / #rrggbb / #rrggbbaa.
  - Returns `OkWithCommand(InsertNode)` on success.

Tests (6 added, 325 shell-core total):
  - insert_node_validates_required_args — missing kind →
    MissingArgument; invalid kind → InvalidArgument.
  - insert_node_validates_numeric_args — non-numeric x →
    InvalidArgument; negative width → InvalidArgument.
  - insert_node_validates_optional_fill_hex — bad hex →
    InvalidArgument.
  - insert_node_returns_command_with_parsed_args — happy path;
    every field round-trips into the McpCommand payload.
  - apply_mcp_command_routes_insert_node — end-to-end: doc.empty()
    → apply InsertNode → new node lives on active page, bounds +
    fill flow through, name matches.
  - apply_mcp_command_rejects_invalid_node_kind — bad kind at
    apply time → false (defensive re-check beyond the tool's
    validation, so host-only call sites are safe too).

mutators.rs trimmed from 814 → 798 (apply_mcp_command extracted +
some redundant doc comments compacted). All OP-owned files now
under the 800 cap except 3 pre-existing tech-debt violators
(codegen.rs 867, widget_host/press.rs 840,
widgets/canvas_viewport.rs 839).

MCP write surface now: 3 first-party writes (set_variable_color,
set_active_axis_value, insert_node). The TS pen-mcp catalog still
needs: update_node, delete_node, move_node, copy_node, replace,
batch_design, design_skeleton — each extends McpCommand + the
same applier pattern.
2026-05-14 20:37:27 +08:00
..
anthropic-agent-sdk chore(crates): move vendored SDKs into workspace as forkable crates 2026-05-14 16:49:17 +08:00
copilot-sdk chore(crates): move vendored SDKs into workspace as forkable crates 2026-05-14 16:49:17 +08:00
openpencil-app feat(openpencil-app): skeleton crate (Stage F entry placeholder) 2026-05-03 22:05:00 +08:00
openpencil-desktop refactor(desktop): extract rotate-cursor builder into cursor_icon.rs 2026-05-14 17:38:16 +08:00
openpencil-shell-core feat(mcp): insert_node tool — third write (the big one) 2026-05-14 20:37:27 +08:00
openpencil-shell-native feat(themes): axis chip click cycles to the next theme value 2026-05-14 19:39:37 +08:00
openpencil-shell-web feat(shell): export dialog + multi-format raster (PNG/JPEG/WEBP) + Property-panel preview 2026-05-14 14:02:52 +08:00
pen-codegen chore(workspace): cargo-deny 0.18 activation (Phase 1 Task 1.8 Step 6) 2026-05-03 23:05:00 +08:00
pen-core chore(workspace): cargo-deny 0.18 activation (Phase 1 Task 1.8 Step 6) 2026-05-03 23:05:00 +08:00
pen-engine chore(workspace): cargo-deny 0.18 activation (Phase 1 Task 1.8 Step 6) 2026-05-03 23:05:00 +08:00
pen-figma chore(workspace): cargo-deny 0.18 activation (Phase 1 Task 1.8 Step 6) 2026-05-03 23:05:00 +08:00
pen-types feat(pen-types): skeleton crate (bucket A) 2026-05-03 22:35:00 +08:00
wasm-libc-shim style(shell): cargo fmt --all (rustfmt-clean) 2026-05-10 18:47:33 +08:00
CLAUDE.md feat(shell): canonical .op loader + jian-core layout + visual fidelity pass 2026-05-14 09:26:08 +08:00