Commit graph

15 commits

Author SHA1 Message Date
Kayshen-X 6e012049e4 feat(editor): add Design-MD panel and complete i18n coverage
Design-MD panel — closes the last TS-vs-Rust parity gap:
- New floating op-editor-ui design_md_panel + design_md_markdown
  renderer: collapsible theme/colour/typography/component/layout/
  notes sections, colour swatches, inline markdown highlighting.
- op-editor-core: parse_design_md parser + panel UI state.
- Host: View-menu toggle, top-most paint + hit-test, drag,
  .md import/export; bumps the vendor/jian DesignMdSpec pointer.

i18n coverage:
- Git panel, merge/conflict/error dialogs and GitError messages
  routed through op-i18n; git locale tables split into
  <base>_git.rs under the 800-line cap.
- Closed 10 app-wide hardcoded-string gaps (AI chat, Figma import,
  file menu, layer context menu, property-panel fill, update /
  file-picker / load-error dialogs, accesskit labels).
2026-05-19 22:29:22 +08:00
Kayshen-X e79ff82833 style: clear clippy lints for the workspace -D warnings gate
CI's Rust Check runs `cargo clippy --workspace --all-targets -- -D
warnings`; the fmt failure had masked it, so accumulated lints surfaced
once formatting was fixed. Resolve them:

- op-acp / op-ai-skills (this change set): while-let loop, redundant
  struct update, and `should_implement_trait` allows on the Option /
  infallible token parsers.
- Pre-existing in op-editor-core / op-figma, swept so the workspace
  gate is clean: redundant `drop`, `too_many_arguments` allow,
  collapsible `if let`, a type alias for a complex tuple, manual
  `Iterator::find`, and an `approx_constant` test value.

Lint fixes only — no behaviour change.
2026-05-17 23:12:16 +08:00
Kayshen-X 418c4acf3b fix(figma): codex review round 2 — pre-validation allocation guards
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)
2026-05-17 16:43:44 +08:00
Kayshen-X ac63c701e6 fix(figma): codex review round 1 — hardening + ZIP entrypoint
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)
2026-05-17 16:37:31 +08:00
Kayshen-X 155f9daee7 feat(figma): wire parse_fig binary pipeline + image resolver
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)
2026-05-17 16:23:19 +08:00
Kayshen-X 7f41eeeeda feat(figma): node converters + figma-to-document mapper
Stages D+E — ports converters/* + figma-node-mapper.ts.

- common.rs: ConversionContext, JS-rounding helpers, transform →
  position/rotation/flip extraction, corner-radius mapping,
  common_props, resolve_width/height, scale_tree_children,
  collect_image_blobs, SKIPPED_TYPES.
- node_build.rs: PenNode constructors (frame/group/rectangle/ellipse/
  line/path/text/ref) — fill the behaviour-trait slots with None.
- converters.rs: convert_node dispatch + per-type converters
  (frame/group/component/instance/rectangle/ellipse/line/text/
  vector); auto-layout child ordering, arc-data flip absorption,
  zero-size vector bounds derivation, stroke-only-outline handling.
- instance.rs: apply_instance_overrides (size-scale fast path +
  direct-GUID override/derived resolution + nested forwarding) +
  merge_symbol_props.
- node_mapper.rs: resolve_style_references (inline style refs) +
  figma_to_pen_document / figma_all_pages_to_pen_document /
  get_figma_pages / figma_node_changes_to_pen_nodes entry points.

Whole pipeline compiles clean; op-figma 77 tests green (+5).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-17 16:17:26 +08:00
Kayshen-X 203e424950 feat(figma): document tree builder
Stage D part 1 — ports figma-tree-builder.ts.

- TreeNode: owned figma node + ordered children tree.
- build_tree: indexes node changes by guid, builds parent→children
  adjacency, materializes from the DOCUMENT root; children sorted
  descending by parentIndex.position (z-order). REMOVED / guid-less
  changes skipped; cyclic chains capped at MAX_TREE_DEPTH.
- build_tree_for_clipboard: orphan-rooted trees for clipboard data
  with no DOCUMENT wrapper.
- is_user_page (CANVAS, non-"Internal Only"), guid_to_string,
  collect_components (SYMBOL guid → fig_N id), collect_symbol_tree
  (SYMBOL guid → subtree).

op-figma 72 tests green (+4).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-17 16:09:23 +08:00
Kayshen-X e18916c68c feat(figma): text mapper + vector geometry decoder
Stage F part 2 — ports figma-text-mapper.ts + figma-vector-decoder.ts.

- text_mapper.rs: map_figma_text_props → TextProps. Builds TextContent
  (plain | per-run StyledTextSegment[] from characterStyleIDs +
  styleOverrideTable, UTF-16-indexed), parses font weight from the
  style name (ordered substring match), line-height → multiplier,
  letter-spacing → px, align / vertical-align / growth enums, and
  applies textCase (UPPER / LOWER / TITLE).
- vector_decoder.rs:
  - decode_figma_path_blob — the opcode command stream (Z/M/L/Q/C,
    f32-LE operands, graceful truncation).
  - compute_svg_path_bounds — coordinate-pair bbox.
  - decode_figma_vector_path — geometry-blob path (stroke centerline
    preferred for stroke-only shapes).
  - decode_vector_network_blob — vertex/segment table fallback, chain-
    walked into M/L/C subpaths, scaled by nodeSize/normalizedSize.

op-figma 68 tests green (+16).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-17 16:07:16 +08:00
Kayshen-X 1e32c6f6cf feat(figma): fill/stroke/effect/layout/sizing style mappers
Stage F part 1 — ports figma-color-utils.ts + figma-fill-mapper.ts +
figma-stroke-mapper.ts + figma-effect-mapper.ts + figma-layout-mapper.ts.

- color.rs: figma_color_to_hex (linear 0-1 → #RRGGBB[AA], JS
  round-half-up) + figma_color_opacity.
- mappers.rs:
  - map_figma_fills → PenFill[] — solid / linear (angle from the
    paint transform's column-0 direction) / radial-angular-diamond
    (cx/cy/r = 0.5) / image (`__hash:`/`__blob:` placeholder URLs);
    invisible + unmappable paints dropped.
  - map_figma_stroke → PenStroke — uniform vs per-side thickness,
    align/join/cap, dash pattern; stroke paints reuse the fill mapper.
  - map_figma_effects → PenEffect[] — drop/inner shadow, fore/
    background blur.
  - map_figma_layout → LayoutProps — stack direction, gap (skipped
    under space-between), per-side padding collapse, justify/align,
    clip-content default.
  - map_width_sizing / map_height_sizing → SizingBehavior — auto-
    layout fit-content / fill-container resolution per axis.

op-figma 52 tests green (+17).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-17 16:02:11 +08:00
Kayshen-X ed8e96a01d feat(figma): decoded-file model + parseFigFile pipeline
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)
2026-05-17 15:56:56 +08:00
Kayshen-X aa72296be0 feat(figma): Kiwi schema decoder
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)
2026-05-17 15:54:32 +08:00
Kayshen-X 3fd1f20540 feat(figma): binary .fig container + decompression layer
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)
2026-05-17 15:50:15 +08:00
Kayshen-X 6f4ab59313 fix: clear clippy -D warnings across the workspace
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).
2026-05-17 01:26:29 +08:00
Kayshen-X 90c1cc4360 style: apply cargo fmt across the workspace 2026-05-17 00:26:15 +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