- Add a headless PropertyGrid primitive with themed app composition
- Keep composite paint details full-width and show remove actions consistently
- Tighten color picker and Design-panel field sizing without clipping values
Drop the spacing/radius sizing tokens (--spacing-control, --spacing-panel-*,
--radius-panel) and their generated classes (h-control, px-panel-x, gap-panel,
rounded-panel, etc.) in favor of direct Tailwind utilities (h-6, px-3, gap-1.5,
rounded). Color tokens are kept since they drive dark/light theming.
Behavior-preserving: 24px controls, 5px radius, identical computed layout.
- Model imported fill, stroke, text, effect, and grid style definitions and references
- Add SDK and app selectors with mixed-selection batching, detach-on-edit, and undo restoration
- Preserve style definitions through .fig export and extracted subgraphs
- Document the shared-style composable and update compatibility coverage
- Add mixed-selection cap, join, and miter-limit controls with one-step undo
- Apply node miter limits across CanvasKit stroke paths and vector outline caches
- Preserve imported and edited stroke geometry through Plugin API and .fig roundtrips
- Rebuild Typography, Variant, Mask, and Page sections with shared labeled fields
- Expose semantic empty states across list sections
- Replace section test hooks with accessible roles and add focused visual coverage
- Share compact item-row and paint-field anatomy across Fill, Stroke, Effects, and Export
- Add COLOR binding transactions with picker rollback and one-step undo
- Replace bespoke panel test hooks with semantic selectors and visual coverage
- Add headless PropertySection and accessible SegmentedControl anatomy
- Make PropertyList controlled and exactly typed with an undo-aware editor adapter
- Consolidate SDK metadata loaders and link untranslated docs to canonical pages
- Remove ScrubInput aliases, app wrappers, test hooks, and translated legacy references
- Share a Tailwind NumberField demo between Storybook and VitePress
- Generate component API tables from Vue metadata and type-check examples with Twoslash
- Upgrade the docs stack to VitePress 2
- Introduce Nuxt UI-style Tailwind Variants themes and 26px panel primitives\n- Align section and field action rails across labeled property rows\n- Add Storybook with dark/light states, accessibility tooling, and CI build coverage\n- Standardize UI acronym casing across app and Vue SDK types
- Forward data-test-id attrs through rendered controls instead of exposing test-id props
- Move shell chrome components into the Shell namespace
- Remove internal planning docs from published VitePress docs
- Add guardrails and contributor guidance for test hooks and component placement
- Replace one-off class props with typed ui objects
- Group renderless slot actions and remove DOM ref callback plumbing
- Make local font access and typography font loading explicit capability objects
- Extract centerline algorithm to packages/core/src/vector/centerline.ts
to stay under max-lines (index.ts 441 lines, centerline.ts 289 lines)
- Fix PathEffect.MakeDash leak: store reference and .delete() after use
- Add aria-label to dash toggle button with i18n (strokeDash)
- Read dash/gap values directly from dashPattern array in template
instead of calling dashState() 4 times
- Add unit tests for fitCircleArc and isClosedThinCrescent
(collinear, non-circular, annular wedge, rectangle, hexagon, open path)
Vector nodes with stroke.dashPattern were rendering as solid lines, and
even when the dash was applied through brute outline-stroke conversion,
closed crescent shapes (e.g. annular wedges) showed two parallel dashed
arcs instead of a single arc along the centerline.
Root cause:
- drawVectorPathStrokes converted stroke to a fill outline before drawing,
leaving no place for PathEffect.MakeDash to apply.
- A closed crescent is a single closed path traced around both the outer
and inner arcs, so dashing the path produces two visible arcs.
Fix:
- packages/core/src/canvas/scene.ts: in drawVectorPathStrokes, when the
stroke has a dashPattern, skip the outline conversion and draw with
strokePaint + PathEffect.MakeDash directly. In renderShapeUncached,
dashed VECTOR nodes with a vectorNetwork now route through a new
centerline path instead of the precomputed strokeGeometry.
- packages/core/src/vector/index.ts: new vectorNetworkToCenterlinePath.
For closed thin crescents (cycle of even length where pairwise opposite
vertex distance is small relative to perimeter and consistent), detect
the two cap segments via vertex direction-change angles, split the
cycle into outer/inner subchains, pair vertices by angle, and emit a
smooth Path.addArc when the midpoints fit a circle (fitCircleArc with
3-point construction + radius tolerance), falling back to a midpoint
polyline otherwise. Open paths use the existing chain walk.
JSX prop wiring so users can author dashed Vectors:
- packages/core/src/design-jsx/tree.ts: add strokeDash?: number[] | boolean.
- packages/core/src/design-jsx/renderer.ts: applyStrokeOverrides reads
strokeDash and writes Stroke.dashPattern; true expands to [w*2, w*2].
- packages/core/src/io/formats/jsx/export.ts: solidStroke serializes
dashPattern back to strokeDash on JSX export.
Editor UI:
- src/components/properties/StrokeSection.vue: dash toggle button plus
ScrubInputs for dash and gap length, writing strokes[0].dashPattern.
bun run check passes. Pre-existing test failures (drop shadow / auto
layout) are unrelated to this change and reproduce on a clean
upstream/master.
- Convert button, input, icon-button, toast, surface from flat-string
functions to slot-based { base: string } return pattern
- Convert section from bare TV exports to useSectionUI() with
{ wrapper, label } slots
- Remove standalone select helpers (selectTrigger, selectContent,
selectItem) in favor of useSelectUI()
- Delete use-component-ui.ts — no longer needed
- Update all 20+ call sites across property panels and chat components
Adds a new scenario to the pen tool specifications defining the behavior for relocating the active vertex with spacebar during a curve drag, including asserting that the cursor remains a crosshair instead of switching to the hand tool.
Made-with: Cursor
Delete 9 shim files from src/composables/ that just re-exported
SDK composables. All 18 consumers now import from '@open-pencil/vue'.
Merge dual useNodeProps()/useMultiProps() calls into single
useNodeProps() in 5 property section components.
src/composables/ now has only 4 app-specific files:
use-chat, use-collab, use-keyboard, use-menu.
- Add IS_BROWSER constant to core/constants.ts, rewrite IS_TAURI to use it
- Replace 9 raw 'typeof window' checks across canvaskit, fig-export,
figma-api, fig-file, fonts, SafariBanner, use-chat
- Promote no-typeof-window-check lint rule from warn to error
- Extract array mutation helpers (updateArrayItem, removeArrayItem,
toggleArrayVisibility) into useMultiProps — DRY up FillSection and
StrokeSection (removes 20+ duplicated for-loops)
- Strip tools/schema.ts from 1831 to 65 lines (remove duplicate tool defs)
- Fix tools/index.ts to export ALL_TOOLS from registry instead of schema
- Fix renderJsx → renderJSX typo in tools/create.ts and structure.ts
- Add geometry.ts with shared rotation/coordinate math utilities
- Add isArrayMixed() helper to use-multi-props.ts, deduplicate FillSection/StrokeSection/EffectsSection
- Fix broken test imports: use @open-pencil/core instead of relative paths into packages/core/src/
- Merge duplicate import statements across all test files
- Export profiler internals and style-run helpers from core index.ts
- Add 6 new test files: color, undo, snap, vector, style-runs, text-editor (98 new tests)
- Update renderer-effects test for renderer/ directory split
Remove 14 src/engine/ shims and entire src/kiwi/ directory.
Only src/engine/fonts.ts remains (has real Tauri font logic).
Replace isTauri() with IS_TAURI constant.