diff --git a/packages/docs/development/kiwi-package-plan.md b/packages/docs/development/kiwi-package-plan.md new file mode 100644 index 000000000..f29c9755d --- /dev/null +++ b/packages/docs/development/kiwi-package-plan.md @@ -0,0 +1,144 @@ +# `@open-pencil/kiwi` extraction plan + +`@open-pencil/kiwi` should be the first file-format package split after `@open-pencil/dom-css`. The first migration must keep `.fig` import/export behavior inside `@open-pencil/core` while moving only scene-graph-agnostic Kiwi runtime code behind a standalone package boundary. + +## Scope boundary + +### Move first + +| Current path | Target path | Why it belongs in `@open-pencil/kiwi` | Preconditions | +|---|---|---|---| +| `packages/core/src/kiwi/schema-runtime/**` | `packages/kiwi/src/schema-runtime/**` | Pure Kiwi schema parsing, validation, binary encode/decode, byte-buffer utilities. Imports only local runtime modules. | Add package-local schema runtime tests and dist smoke. | +| `packages/core/src/kiwi/fig/codec/schema/fig.kiwi` | `packages/kiwi/src/fig/schema/fig.kiwi` | Static Figma Kiwi schema text. It is data, not SceneGraph policy. | Keep raw-md bundler support in the new package build. | +| `packages/core/src/kiwi/fig/codec/schema/index.ts` | `packages/kiwi/src/fig/schema.ts` | Parses and validates the static schema. Currently only depends on schema runtime plus raw schema text. | Rewrite imports to package-local runtime paths. | +| `packages/core/src/kiwi/fig/codec/protocol.ts` | `packages/kiwi/src/fig/protocol.ts` | Low-level Figma multiplayer/Kiwi container byte inspection. No SceneGraph dependency. | Add fixtures for message-type and compression detection. | +| `packages/core/src/kiwi/fig/codec/variable-bindings.ts` | `packages/kiwi/src/fig/variable-bindings.ts` | Low-level variable binding binary encode/decode helper. It should not need SceneGraph. | Replace `#core/bytes/hex` / `#core/types` imports with local helpers/types. | +| `packages/core/src/kiwi/fig/codec/index.ts` | `packages/kiwi/src/fig/codec.ts` | Encodes/decodes Figma Kiwi messages. Mostly schema/runtime/protocol logic. | Remove `#core/color` dependency or make color parsing caller-owned; replace core shared types with local exported structural types. | + +### Keep in `@open-pencil/core` for now + +| Current path | Reason it stays | +|---|---| +| `packages/core/src/kiwi/fig/import.ts` | Creates `SceneGraph`, imports variables/components/pages, applies OpenPencil source metadata. | +| `packages/core/src/kiwi/fig/lazy-import.ts` | Stores lazy import context in `SceneGraph` weak maps. | +| `packages/core/src/kiwi/fig/node-change/**` | Converts between Figma `NodeChange` records and OpenPencil `SceneNode`, text, vector, paint, font, and layout types. This is `.fig` policy, not raw Kiwi. | +| `packages/core/src/kiwi/fig/instance-overrides/**` | Resolves Figma component/instance override semantics into `SceneGraph`. | +| `packages/core/src/kiwi/fig/parse/**` | `parse/core.ts` can move later to `@open-pencil/fig`; `transfer.ts` and `worker.ts` serialize `SceneGraph`, so they stay until `.fig` package extraction. | +| `packages/core/src/kiwi/fig/container/**` | `.fig`/Kiwi container framing policy. Move later with `@open-pencil/fig`, unless a pure byte-container helper is split out deliberately. | +| `packages/core/src/kiwi/fig/file.ts` | Re-export of core `.fig` I/O. | +| `packages/core/src/io/formats/fig/**` | App/CLI-facing `.fig` read/write policy and renderer-backed export behavior. | + +## Dependency blockers to remove before moving + +- `fig/codec/index.ts` imports `parseColor` from `#core/color` for convenience factories. Options: + - remove string color parsing from the low-level package, or + - accept a caller-supplied `parseColor` option, or + - move only structural encode/decode first and leave convenience constructors in core. +- `fig/codec/index.ts` and `variable-bindings.ts` import `Color`, `GUID`, `Matrix`, and `Vector` from `#core/types`. The Kiwi package should define minimal structural types locally and let core adapt them. +- `variable-bindings.ts` imports `hexToBytes` from `#core/bytes/hex`. Copy a tiny local helper or expose it from the Kiwi package; do not depend on core. +- Raw `.kiwi` loading currently relies on the root raw markdown/schema tooling. The new package build must prove raw schema import works from `packages/kiwi` in build and smoke tests. + +## Initial package shape + +```text +packages/kiwi/ + package.json + tsconfig.json + tsconfig.test.json + tsdown.config.ts + scripts/smoke-dist.ts + src/ + index.ts + schema-runtime/ + fig/ + codec.ts + protocol.ts + schema.ts + schema/fig.kiwi + variable-bindings.ts + types.ts + tests/ + schema-runtime.test.ts + fig-schema.test.ts + fig-codec.test.ts + protocol.test.ts + variable-bindings.test.ts +``` + +Public exports should stay narrow at first: + +```json +{ + ".": "./dist/index.js", + "./schema-runtime": "./dist/schema-runtime/index.js", + "./fig": "./dist/fig/index.js" +} +``` + +`@open-pencil/core/kiwi` should keep compatibility re-exports during the first migration so app, CLI, MCP, and external consumers do not need to change immediately. + +## Package-local test plan + +Add these tests in `packages/kiwi/tests/**` before moving production callers: + +1. **Schema runtime smoke** + - Parse a small inline Kiwi schema. + - Validate field numbers and enum values. + - Compile it, encode a message, decode it back. + +2. **Bundled Figma schema guard** + - Validate the bundled `fig.kiwi` schema. + - Assert stable high-value facts already covered in `tests/engine/kiwi/schema-runtime.test.ts`: + - definition count is `605` + - `NodeChange` exists + - `InteractiveSlideElementChange` exists + - `Paint.colorVar` field number is `21` + - `NodeChange.pageType` field number is `397` + - `VariableField.OVERRIDDEN_SYMBOL_ID` enum value is `37` + +3. **Protocol helpers** + - `isZstdCompressed()` returns true for Zstd magic bytes and false for Kiwi payload bytes. + - `getKiwiMessageType()` reads a message type from a minimal encoded payload. + - Malformed payloads fail predictably without throwing unexpected internal errors. + +4. **Variable binding binary helpers** + - Varint encode/decode round-trips representative values: `0`, `1`, `127`, `128`, `255`, `16384`. + - GUID hex/byte conversion round-trips a known Figma GUID. + - Color variable binding encode output stays byte-stable for a known fixture. + +5. **Figma message codec** + - `initCodec()` is idempotent. + - `getSchemaBytes()` returns non-empty stable schema bytes. + - A minimal `FigmaMessage` encode/decode round-trips. + - A `NodeChange` with variable bindings uses the custom node-change path and decodes back to equivalent data. + +6. **Dist smoke** + - Import `@open-pencil/kiwi` built output from `dist`. + - Validate schema, initialize codec, encode/decode one minimal message. + - Assert no import of `@open-pencil/core` is required by the package dist. + +Repo-level tests under `tests/engine/io/fig/**`, `tests/engine/kiwi/serialize-fixes/**`, and Figma oracle fixtures should remain in place. Package-local tests prove package isolation; repo-level tests prove OpenPencil `.fig` behavior did not regress. + +## Migration sequence + +1. Add `packages/kiwi` with copied runtime/schema files and package-local tests, but do not update production imports yet. +2. Make `@open-pencil/core/kiwi` re-export from `@open-pencil/kiwi` for schema runtime and codec-only APIs. +3. Update internal core imports that only need schema runtime or codec APIs to public `@open-pencil/kiwi` exports. +4. Keep `.fig` import/export, node-change conversion, and instance override policy in core until `@open-pencil/fig` exists. +5. Run: + +```sh +cd packages/kiwi && bun run check +bun test tests/engine/kiwi/schema-runtime.test.ts tests/engine/io/fig/roundtrip/basic.test.ts +bun run check +``` + +6. Only after the package is stable, start the `@open-pencil/fig` split for `.fig` container/read/write and SceneGraph conversion policy. + +## Do not do yet + +- Do not move `NodeChange` ⇄ `SceneNode` conversion into `@open-pencil/kiwi`. +- Do not make `@open-pencil/kiwi` depend on `@open-pencil/core`. +- Do not widen app/CLI imports to private package source paths. +- Do not guess or rewrite Figma schema fields during the split. +- Do not remove `@open-pencil/core/kiwi` compatibility exports in the first migration. diff --git a/packages/docs/development/package-split.md b/packages/docs/development/package-split.md index 230b2f7d9..0ae0c1c0b 100644 --- a/packages/docs/development/package-split.md +++ b/packages/docs/development/package-split.md @@ -15,6 +15,8 @@ OpenPencil currently keeps file-format internals in `@open-pencil/core` while `@ - `@open-pencil/dom-css` owns DesignDOM, CSS runtimes, HTML/JSX/Tailwind projection, and SceneGraph ⇄ DesignDOM conversion. - App, CLI, MCP, and Vue SDK consume packages through public workspace exports only. +See [`kiwi-package-plan.md`](./kiwi-package-plan.md) for the detailed `@open-pencil/kiwi` inventory, dependency blockers, and package-local test plan. + ## Candidate packages ### `@open-pencil/kiwi`