openpencil/src/app.css

170 lines
4.3 KiB
CSS
Raw Normal View History

2026-02-28 19:41:34 +00:00
@import 'tailwindcss';
@import 'tw-animate-css';
@import 'vue-stream-markdown/index.css' layer(components);
@import './theme/chat/markdown.css';
@custom-variant motion-safe {
@media (prefers-reduced-motion: no-preference) {
&:where(:root:not([data-motion='off']), :root:not([data-motion='off']) *) {
@slot;
}
}
}
@custom-variant motion-reduce {
@media (prefers-reduced-motion: reduce) {
@slot;
}
&:where([data-motion='off'], [data-motion='off'] *) {
@slot;
}
}
@utility scrollbar-none {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
@utility scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: var(--color-border) transparent;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--color-muted);
}
}
@theme {
--color-panel: #2a2a2a;
--color-panel-secondary: #242424;
--color-panel-field: #383838;
--color-panel-field-hover: #404040;
--color-panel-focus: #3b82f6;
--color-panel-selected: #2f6fdd;
--color-panel-selected-muted: #3d4450;
--color-canvas: #1e1e1e;
--color-border: #3a3a3a;
--color-hover: #353535;
--color-accent: #3b82f6;
--color-surface: #f0f0f0;
--color-muted: #a6a6a6;
--color-input: #1e1e1e;
--color-component: #9747ff;
feat: create, fill, and edit slots (#862) * feat(app): add slot property controls and a shared picker AppPicker is a searchable, grouped list that opens beside the properties panel, built on Reka's popover and listbox so search keeps arrow-key navigation. It has comfortable rows with a thumbnail and description and compact rows for plain names, plus an optional footer action. The slot property row shows whether an instance's slot is Default or Modified, its item count, its limits with a checklist popover, Add instances on AppPicker, and Reset slot and Delete contents. These are presentational; wiring them to the editor follows. Strings are English only until the locale files catch up. * feat(app): open variable, style, and instance-swap choices in the shared picker The variable binding picker, the shared style fields, and instance-swap properties now open AppPicker: a titled panel beside the properties panel with search that keeps arrow-key navigation, a check on the current choice, and footer actions. Variable binding keeps its detach and create-variable actions. Instance-swap choices list the property's preferred components first; instanceSwapOptions keeps the preferred flag it already computed. AppPickerField gives select-shaped fields the same picker with a combobox trigger. * feat(core): edit instance slot content Only an instance's slots take layers now. slotScope classifies a parent as free, a slot of an instance, or the locked rest of an instance. Moves, reorders, layer-panel drops, paste, duplicate, and instance creation claim an untouched slot first, as Figma does on the first edit, and refuse the locked part; drops over it land in the instance's parent. Claiming keeps the layers but unlinks them from the component and moves the instance's overrides on nested instances onto those instances. Reset slot, Delete contents, and Add instance are editor actions, each one undo step that restores the instance's subtree. A canvas drop or reorder that claims a slot undoes together with the move. * feat(app): show and edit slot properties of the selected instance The component properties section lists each slot of the selected instance with its state, item count and limits, and adds instances, resets or clears its content through the editor's slot actions. The slot model lives in the Vue SDK as useSlotProperties. * feat(app): outline slots on the canvas and mark them in the layers panel Hovering or selecting a component, an instance, or a layer inside a slot draws its slots with a dashed pink outline and tints empty ones. Slot frames are selected and hovered in pink and show a dashed-square icon in the layers panel. * test(app): cover slot outlines with canvas snapshots * feat(app): translate slot and picker strings * docs: note slot editing and the shared picker * refactor: share slot test and story setup * refactor(app): name the picker's header prop heading * feat(core): create, configure, and remove slots on main components A frame of a main component becomes a slot through a SLOT property named after it; other sibling layers are first wrapped in an auto layout frame. Slot settings and removal are single undo steps. Instances now follow the component's property bindings on sync, so a slot created or removed on the component reaches existing instances. Slot helpers move to a slots domain folder in Scene Graph and Core. * feat(app): create and configure slots from the menu and properties panel Create slot joins the canvas context menu for layers of a main component. A Slots section on main components and their frames renames slots, sets their description, layer limits, and preferred components, and removes them. * fix: keep slot claims in the same undo step and refuse wraps inside instances Creating an instance and pasting HTML now batch the slot claim with the edit. Undoing an added slot instance restores the previous selection. Grouping or wrapping layers in the locked part of an instance is refused, and a section that cannot move no longer claims a slot. The picker clears its search however it closes, and its close and slot actions labels are translated. * feat(core): create and inspect slots through the plugin API component.createSlot() adds a 100x100 frame named Slot, Slot 2, and so on, bound to a new SLOT property, as live Figma does. Slot frames read type SLOT, resetSlot() restores an instance slot's component content, and limitViolations reports BELOW_MIN, ABOVE_MAX, and HAS_NON_PREFERRED for instance slots. addComponentProperty and editComponentProperty take a description and slotSettings, a cloned slot is a plain frame, and componentPropertyReferences uses property keys in both directions. The limit checks move to Scene Graph so the Vue SDK and the plugin API share them. * fix: keep nested slot content across swaps and read nested instance properties A nested instance points at the instance it was cloned from, so its component properties resolved to nothing. Properties now resolve through those links to the main component, and a swap or variant switch parks the slot content nested instances own and restores it into nested instances of the same names, as live Figma does. Plugin appendChild and insertChild claim the slot they add to and refuse the locked part of an instance with Figma's error. * test(core): record resetSlot on a main component slot; fix the Slots roadmap row * fix(core): refuse deleting layers outside an instance's slots As in Figma, delete and plugin remove() leave an instance's own layers and its slot frames alone, while removing slot content claims the slot in the same undo step as the delete. * test(app): wait for the bulk rename dialog to close before the next shortcut * feat(app): unify add, remove, and settings controls in the properties panel A section's + adds an item and a row's - removes it everywhere: grid tracks and variant properties now follow the fill and effect lists, and the header + of a component set adds Property 1 ready to rename instead of an inline form. Removing a variant is Delete, as for any layer. Row settings share the sliders icon, the variables button opens with its own icon, every icon button requires a label, and the instance header buttons use sentence case. Create Slot joins the app menu. * docs: note the unified properties panel controls * feat(app): animate floating panels alike and share the severity icon Popovers, menus, selects, comboboxes, and pickers now fade and grow in from the side they open on and fade out on close, from one motion preset that respects reduced motion; tooltips keep the tooltip preset. SeverityIcon and its colours move from the design check to shared feedback UI, and slot limits use them, so a broken limit reads like a design-check warning. * docs: note consistent popover and menu animation * feat(app): give every floating panel one surface and close it at once Popovers, menus, selects, comboboxes, pickers, presence cards, chat history, and the issue tooltip share one rounded surface with a 1px ring that outlines it in both themes; one-line tooltips keep a compact shape with the same edge. The select theme's radius and elevation options and local shadow overrides are gone. Panels still grow in from their side but now close immediately: a fading modal menu kept blocking the canvas and shortcuts until it unmounted. * fix(app): keep a reopened context menu open and name option-drag undo Duplicate Closing the canvas menu hands focus back to the canvas; when that landed just after a quick reopen, the new menu closed as focus moved outside it. Shortcuts no longer wait for a panel that is already closing, and an option-drag duplicate that claims a slot is undone as Duplicate rather than Move. * test(app): wait for menus and popovers to close before the next key
2026-10-04 17:02:28 +00:00
--color-slot: #f24bbc;
2026-04-30 13:18:43 +00:00
--color-warning-bg: rgb(245 158 11 / 0.1);
--color-warning-border: rgb(245 158 11 / 0.3);
--color-warning-text: #fde68a;
--color-warning-action: #fcd34d;
--color-success: #4ade80;
--color-primary: #60a5fa;
--color-inverted: #1f2328;
--color-error: #f87171;
feat: check designs live with a Lint panel, canvas markers, and fixes (#804) * feat: check designs live with a Check panel and canvas issue markers Design lint only ran from the CLI and AI tools, and its rules were too noisy to show continuously: on a real imported page 786 of 888 layers had a warning. The rules now report where a finding is actionable (a hardcoded color only when a variable matches it, nesting only where the limit is crossed, instance sublayers through their main component) and carry structured data, and Recommended keeps warnings for likely problems. The app checks the current page after edits settle. The Check tab groups issues by rule with hover highlighting, reveal on click, and one-step variable binding. Errors and warnings are marked on the canvas with clustered markers that roll up to visible ancestors when zoomed out; markers explain themselves on hover, open Check on click, and toggle with View > Design issues. * fix: keep the right panel and markers stable The Check tab made the right-panel tab row overflow at common window widths, so focusing the zoom menu scrolled the row and shifted the panel. Code and AI tabs now drop their labels to screen readers when the row is narrow. Touch target names are matched as whole words: "Rectangle" contained "cta" and marked every rectangle. Markers also stay drawn during interactive edits instead of blinking while a value is scrubbed. * fix(ui): show right panel tab labels whenever they fit * fix(ui): name the design check tab Lint and keep panel tabs consistent The tab was an unlabelled icon between labelled Code and AI tabs. It is now Lint, with the same icon and label anatomy as its neighbours, and its icon takes the severity color instead of a count badge. All labelled tabs show their labels when the row fits and drop them together when it does not. * refactor(ui): build the Lint panel from shared components Issue groups use AppCollapsible, actions use AppButton, and the severity filters are a Reka toggle group with keyboard navigation. Issue rows no longer nest a button inside a button. Panel state, visibility and the focused-issue scroll live in useDesignCheckPanel, the rules menu is its own component, and rule preferences change through preference actions. Severity ordering reuses Core's ranking, detail numbers follow the app language, and the check debounce uses useTimeoutFn. * fix(lint): check the WCAG AA touch target size in the Recommended preset Recommended flagged a 394 × 39 input because it required the 44 × 44 AAA size. It now checks the 24 × 24 AA minimum through a minSize option; Strict and Accessibility keep 44 × 44. * feat(lint): fix design issues from rules, the Lint panel, the CLI, and agents Rules attach fixes as data: a safe fix keeps the design as it looks (bind a color to the variable it matches, round subpixel geometry that layout does not own), a suggestion changes values (snap radius and spacing to the scale, raise small text to the minimum). One Core applier re-validates each fix against the current graph and merges changes per layer. The Lint panel offers a fix per row and Fix all for safe fixes as one undo step; openpencil lint --fix writes the fixed document; the lint and lint_fix tools expose the same to MCP and AI chat. The design-check spec's Close button is now 24 x 20: at 24 x 24 it passes the WCAG AA touch target size that Recommended checks. * feat(lint): pin issues outside the view to the canvas edge Errors and warnings on layers outside the viewport had no marker, so a check could report issues nobody could see. They are now pinned to the canvas edge where a ray from the viewport center toward them leaves it, with a chevron pointing their way; pins in one direction merge like markers. Hovering lists them under the direction they lie in, and clicking reveals and opens the most severe, nearest one. Pins keep clear of UI floating over the canvas: the toolbar marks itself with data-canvas-obstacle, and canvases report such rectangles to the renderer through getOverlayObstacles each frame. * feat(lint): mark layers with design issues in the Layers panel Like an IDE marks files with problems and the folders holding them, a layer with errors or warnings shows the most severe as an icon, and a collapsed layer with issues inside it shows a dot in that color. Suggestions stay in the Lint panel, as on the canvas, and the marks follow the View → Design issues toggle. * feat(lint): show issues per page and across the document Loaded pages beyond the current one are now checked in the background, one page at a time while the editor is idle, and checked again only when an edit touches them; pages a large .fig file has not loaded are left alone until opened rather than forced in. The page list shows each page's errors and warnings like an IDE's problem count, and the Lint panel gains a Document scope that lists every page's issues, tags the ones on other pages, and switches to a row's page when it is opened. * test(lint): use the core-tests alias and no comma operator in lint tests Master now rejects ../../ imports and the comma operator in tests. * refactor(app): create the Lint session with the editor store modules The composition root passed its line budget once master added recent pages; the Lint session belongs with the other per-editor services that the modules factory creates and disposes. * docs(changelog): keep master's latest Unreleased entries
2026-10-04 10:08:39 +00:00
--color-issue-error: #f24822;
--color-issue-warning: #ffb314;
--color-issue-info: #a6a6a6;
feat(code): add live JSX and HTML/CSS editing (#525) * feat(code): isolate Design JSX execution - Transform JSX with the existing Sucrase dependency and execute it in a disposable opaque-origin iframe worker - Block ambient network capabilities and enforce source, timeout, output, depth, and element limits - Validate that only bounded plain structured data returns to the application * feat(code): add editable Design JSX - Add a lazy CodeMirror editor with JSX syntax support, completion, diagnostics, and bounded scrolling - Convert validated sandbox output into trusted Design JSX helpers before rendering - Apply or insert JSX as one undoable graph transaction while preserving dirty drafts * feat(code): localize JSX editor actions - Add translated-message fallbacks for editing, applying, inserting, and draft state - Document the editable JSX workflow in the unreleased changelog * fix(code): satisfy typed sandbox validation - Keep the sandbox document terminator literal and preserve optional selection handling under type-aware lint * test(code): resolve sandbox probes through app aliases * fix(code): bound sandbox results before cloning - Enforce string, array, object, depth, element, and byte limits inside the disposable worker - Retain host-side validation as a second structured-data boundary * fix(code): keep JSX and HTML editing modes separate - Switch generated Tailwind output back to OpenPencil JSX before editing - Close the JSX editor when opening HTML/CSS import and avoid stacking both editors * feat(code): support authored Design JSX programs - Allow local constants, function components, arrays, conditionals, fragments, and multiple roots - Preserve replacement positions for multiple selected roots and reject mixed-parent or locked selections - Cover multi-root undo, redo, and all-or-nothing rollback * feat(code): add explicit JSX view mode - Let authors leave CodeMirror without applying a draft - Keep the editable surface and HTML/CSS importer mutually exclusive * feat(code): diagnose unknown JSX vocabulary - Warn on OpenPencil elements and properties that are absent from the canonical schema - Surface diagnostics inline through CodeMirror lint markers * test(code): accept WebKit isolation diagnostics - Cover the sandbox architecture against Playwright WebKit - Accept engine-specific wording while preserving the same unavailable-window assertion * refactor(code): consolidate Design JSX vocabulary - Drive renderer warnings, completion, and diagnostics from one supported-property schema - Recognize locally declared components and add focused schema and transform tests - Replace CodeMirror basicSetup with an explicit feature set and remove the umbrella package * fix(code): support Design JSX variable helpers * refactor(code): unify JSX sandbox validation * fix(code): account for complete sandbox output * fix(code): preserve locked JSX descendants * fix(code): preserve JSX sibling order * fix(code): recompute JSX parent layouts * feat(code): add live code previews * test(code): centralize graph assertions * fix(code): harden live preview sessions * docs: describe live code editing * fix(code): update editor accessibility labels * fix(code): use theme-aware error colors * fix(dev): stop Vite disconnect error loops * fix(code): clarify live preview status * fix(ui): avoid tooltip attribute warnings * test(code): assert semantic preview status * refactor(code): remove obsolete editor messages * fix(code): harden preview concurrency and isolation * fix(code): cancel stale reset previews
2026-08-15 06:48:42 +00:00
--color-error-bg: rgb(248 113 113 / 0.1);
--color-error-border: rgb(248 113 113 / 0.4);
--color-success-bg: #16a34a;
--color-success-bg-hover: #15803d;
--color-code-tag: #7dd3fc;
--color-code-attribute: #c4b5fd;
--color-code-string: #86efac;
--color-code-number: #fca5a5;
--color-code-punctuation: #888888;
--color-checkerboard: #3a3a3a;
--color-checkerboard-muted: #4a4a4a;
2026-04-30 14:50:20 +00:00
--color-ruler-bg: rgb(36, 36, 36);
--color-ruler-tick: rgb(102, 102, 102);
--color-ruler-text: rgb(140, 140, 140);
--color-ruler-label: rgb(255, 255, 255);
}
2026-04-30 12:52:59 +00:00
html[data-theme='light'] {
--color-panel: #ffffff;
--color-panel-secondary: #f7f8fa;
--color-panel-field: #f0f1f3;
--color-panel-field-hover: #e4e7ea;
--color-panel-focus: #2563eb;
--color-panel-selected: #2563eb;
--color-panel-selected-muted: #e5e7eb;
2026-04-30 12:52:59 +00:00
--color-canvas: #f3f4f6;
--color-border: #d8dce3;
--color-hover: #eef1f5;
--color-accent: #2563eb;
--color-primary: #2563eb;
--color-inverted: #ffffff;
--color-surface: #1f2328;
2026-04-30 12:52:59 +00:00
--color-muted: #6b7280;
--color-input: #ffffff;
--color-component: #7c3aed;
feat: create, fill, and edit slots (#862) * feat(app): add slot property controls and a shared picker AppPicker is a searchable, grouped list that opens beside the properties panel, built on Reka's popover and listbox so search keeps arrow-key navigation. It has comfortable rows with a thumbnail and description and compact rows for plain names, plus an optional footer action. The slot property row shows whether an instance's slot is Default or Modified, its item count, its limits with a checklist popover, Add instances on AppPicker, and Reset slot and Delete contents. These are presentational; wiring them to the editor follows. Strings are English only until the locale files catch up. * feat(app): open variable, style, and instance-swap choices in the shared picker The variable binding picker, the shared style fields, and instance-swap properties now open AppPicker: a titled panel beside the properties panel with search that keeps arrow-key navigation, a check on the current choice, and footer actions. Variable binding keeps its detach and create-variable actions. Instance-swap choices list the property's preferred components first; instanceSwapOptions keeps the preferred flag it already computed. AppPickerField gives select-shaped fields the same picker with a combobox trigger. * feat(core): edit instance slot content Only an instance's slots take layers now. slotScope classifies a parent as free, a slot of an instance, or the locked rest of an instance. Moves, reorders, layer-panel drops, paste, duplicate, and instance creation claim an untouched slot first, as Figma does on the first edit, and refuse the locked part; drops over it land in the instance's parent. Claiming keeps the layers but unlinks them from the component and moves the instance's overrides on nested instances onto those instances. Reset slot, Delete contents, and Add instance are editor actions, each one undo step that restores the instance's subtree. A canvas drop or reorder that claims a slot undoes together with the move. * feat(app): show and edit slot properties of the selected instance The component properties section lists each slot of the selected instance with its state, item count and limits, and adds instances, resets or clears its content through the editor's slot actions. The slot model lives in the Vue SDK as useSlotProperties. * feat(app): outline slots on the canvas and mark them in the layers panel Hovering or selecting a component, an instance, or a layer inside a slot draws its slots with a dashed pink outline and tints empty ones. Slot frames are selected and hovered in pink and show a dashed-square icon in the layers panel. * test(app): cover slot outlines with canvas snapshots * feat(app): translate slot and picker strings * docs: note slot editing and the shared picker * refactor: share slot test and story setup * refactor(app): name the picker's header prop heading * feat(core): create, configure, and remove slots on main components A frame of a main component becomes a slot through a SLOT property named after it; other sibling layers are first wrapped in an auto layout frame. Slot settings and removal are single undo steps. Instances now follow the component's property bindings on sync, so a slot created or removed on the component reaches existing instances. Slot helpers move to a slots domain folder in Scene Graph and Core. * feat(app): create and configure slots from the menu and properties panel Create slot joins the canvas context menu for layers of a main component. A Slots section on main components and their frames renames slots, sets their description, layer limits, and preferred components, and removes them. * fix: keep slot claims in the same undo step and refuse wraps inside instances Creating an instance and pasting HTML now batch the slot claim with the edit. Undoing an added slot instance restores the previous selection. Grouping or wrapping layers in the locked part of an instance is refused, and a section that cannot move no longer claims a slot. The picker clears its search however it closes, and its close and slot actions labels are translated. * feat(core): create and inspect slots through the plugin API component.createSlot() adds a 100x100 frame named Slot, Slot 2, and so on, bound to a new SLOT property, as live Figma does. Slot frames read type SLOT, resetSlot() restores an instance slot's component content, and limitViolations reports BELOW_MIN, ABOVE_MAX, and HAS_NON_PREFERRED for instance slots. addComponentProperty and editComponentProperty take a description and slotSettings, a cloned slot is a plain frame, and componentPropertyReferences uses property keys in both directions. The limit checks move to Scene Graph so the Vue SDK and the plugin API share them. * fix: keep nested slot content across swaps and read nested instance properties A nested instance points at the instance it was cloned from, so its component properties resolved to nothing. Properties now resolve through those links to the main component, and a swap or variant switch parks the slot content nested instances own and restores it into nested instances of the same names, as live Figma does. Plugin appendChild and insertChild claim the slot they add to and refuse the locked part of an instance with Figma's error. * test(core): record resetSlot on a main component slot; fix the Slots roadmap row * fix(core): refuse deleting layers outside an instance's slots As in Figma, delete and plugin remove() leave an instance's own layers and its slot frames alone, while removing slot content claims the slot in the same undo step as the delete. * test(app): wait for the bulk rename dialog to close before the next shortcut * feat(app): unify add, remove, and settings controls in the properties panel A section's + adds an item and a row's - removes it everywhere: grid tracks and variant properties now follow the fill and effect lists, and the header + of a component set adds Property 1 ready to rename instead of an inline form. Removing a variant is Delete, as for any layer. Row settings share the sliders icon, the variables button opens with its own icon, every icon button requires a label, and the instance header buttons use sentence case. Create Slot joins the app menu. * docs: note the unified properties panel controls * feat(app): animate floating panels alike and share the severity icon Popovers, menus, selects, comboboxes, and pickers now fade and grow in from the side they open on and fade out on close, from one motion preset that respects reduced motion; tooltips keep the tooltip preset. SeverityIcon and its colours move from the design check to shared feedback UI, and slot limits use them, so a broken limit reads like a design-check warning. * docs: note consistent popover and menu animation * feat(app): give every floating panel one surface and close it at once Popovers, menus, selects, comboboxes, pickers, presence cards, chat history, and the issue tooltip share one rounded surface with a 1px ring that outlines it in both themes; one-line tooltips keep a compact shape with the same edge. The select theme's radius and elevation options and local shadow overrides are gone. Panels still grow in from their side but now close immediately: a fading modal menu kept blocking the canvas and shortcuts until it unmounted. * fix(app): keep a reopened context menu open and name option-drag undo Duplicate Closing the canvas menu hands focus back to the canvas; when that landed just after a quick reopen, the new menu closed as focus moved outside it. Shortcuts no longer wait for a panel that is already closing, and an option-drag duplicate that claims a slot is undone as Duplicate rather than Move. * test(app): wait for menus and popovers to close before the next key
2026-10-04 17:02:28 +00:00
--color-slot: #db2777;
2026-04-30 13:18:43 +00:00
--color-warning-bg: rgb(245 158 11 / 0.14);
--color-warning-border: rgb(245 158 11 / 0.32);
--color-warning-text: #92400e;
--color-warning-action: #78350f;
--color-success: #15803d;
--color-error: #b91c1c;
feat: check designs live with a Lint panel, canvas markers, and fixes (#804) * feat: check designs live with a Check panel and canvas issue markers Design lint only ran from the CLI and AI tools, and its rules were too noisy to show continuously: on a real imported page 786 of 888 layers had a warning. The rules now report where a finding is actionable (a hardcoded color only when a variable matches it, nesting only where the limit is crossed, instance sublayers through their main component) and carry structured data, and Recommended keeps warnings for likely problems. The app checks the current page after edits settle. The Check tab groups issues by rule with hover highlighting, reveal on click, and one-step variable binding. Errors and warnings are marked on the canvas with clustered markers that roll up to visible ancestors when zoomed out; markers explain themselves on hover, open Check on click, and toggle with View > Design issues. * fix: keep the right panel and markers stable The Check tab made the right-panel tab row overflow at common window widths, so focusing the zoom menu scrolled the row and shifted the panel. Code and AI tabs now drop their labels to screen readers when the row is narrow. Touch target names are matched as whole words: "Rectangle" contained "cta" and marked every rectangle. Markers also stay drawn during interactive edits instead of blinking while a value is scrubbed. * fix(ui): show right panel tab labels whenever they fit * fix(ui): name the design check tab Lint and keep panel tabs consistent The tab was an unlabelled icon between labelled Code and AI tabs. It is now Lint, with the same icon and label anatomy as its neighbours, and its icon takes the severity color instead of a count badge. All labelled tabs show their labels when the row fits and drop them together when it does not. * refactor(ui): build the Lint panel from shared components Issue groups use AppCollapsible, actions use AppButton, and the severity filters are a Reka toggle group with keyboard navigation. Issue rows no longer nest a button inside a button. Panel state, visibility and the focused-issue scroll live in useDesignCheckPanel, the rules menu is its own component, and rule preferences change through preference actions. Severity ordering reuses Core's ranking, detail numbers follow the app language, and the check debounce uses useTimeoutFn. * fix(lint): check the WCAG AA touch target size in the Recommended preset Recommended flagged a 394 × 39 input because it required the 44 × 44 AAA size. It now checks the 24 × 24 AA minimum through a minSize option; Strict and Accessibility keep 44 × 44. * feat(lint): fix design issues from rules, the Lint panel, the CLI, and agents Rules attach fixes as data: a safe fix keeps the design as it looks (bind a color to the variable it matches, round subpixel geometry that layout does not own), a suggestion changes values (snap radius and spacing to the scale, raise small text to the minimum). One Core applier re-validates each fix against the current graph and merges changes per layer. The Lint panel offers a fix per row and Fix all for safe fixes as one undo step; openpencil lint --fix writes the fixed document; the lint and lint_fix tools expose the same to MCP and AI chat. The design-check spec's Close button is now 24 x 20: at 24 x 24 it passes the WCAG AA touch target size that Recommended checks. * feat(lint): pin issues outside the view to the canvas edge Errors and warnings on layers outside the viewport had no marker, so a check could report issues nobody could see. They are now pinned to the canvas edge where a ray from the viewport center toward them leaves it, with a chevron pointing their way; pins in one direction merge like markers. Hovering lists them under the direction they lie in, and clicking reveals and opens the most severe, nearest one. Pins keep clear of UI floating over the canvas: the toolbar marks itself with data-canvas-obstacle, and canvases report such rectangles to the renderer through getOverlayObstacles each frame. * feat(lint): mark layers with design issues in the Layers panel Like an IDE marks files with problems and the folders holding them, a layer with errors or warnings shows the most severe as an icon, and a collapsed layer with issues inside it shows a dot in that color. Suggestions stay in the Lint panel, as on the canvas, and the marks follow the View → Design issues toggle. * feat(lint): show issues per page and across the document Loaded pages beyond the current one are now checked in the background, one page at a time while the editor is idle, and checked again only when an edit touches them; pages a large .fig file has not loaded are left alone until opened rather than forced in. The page list shows each page's errors and warnings like an IDE's problem count, and the Lint panel gains a Document scope that lists every page's issues, tags the ones on other pages, and switches to a row's page when it is opened. * test(lint): use the core-tests alias and no comma operator in lint tests Master now rejects ../../ imports and the comma operator in tests. * refactor(app): create the Lint session with the editor store modules The composition root passed its line budget once master added recent pages; the Lint session belongs with the other per-editor services that the modules factory creates and disposes. * docs(changelog): keep master's latest Unreleased entries
2026-10-04 10:08:39 +00:00
--color-issue-error: #dc3412;
--color-issue-warning: #b86e00;
--color-issue-info: #6b7280;
feat(code): add live JSX and HTML/CSS editing (#525) * feat(code): isolate Design JSX execution - Transform JSX with the existing Sucrase dependency and execute it in a disposable opaque-origin iframe worker - Block ambient network capabilities and enforce source, timeout, output, depth, and element limits - Validate that only bounded plain structured data returns to the application * feat(code): add editable Design JSX - Add a lazy CodeMirror editor with JSX syntax support, completion, diagnostics, and bounded scrolling - Convert validated sandbox output into trusted Design JSX helpers before rendering - Apply or insert JSX as one undoable graph transaction while preserving dirty drafts * feat(code): localize JSX editor actions - Add translated-message fallbacks for editing, applying, inserting, and draft state - Document the editable JSX workflow in the unreleased changelog * fix(code): satisfy typed sandbox validation - Keep the sandbox document terminator literal and preserve optional selection handling under type-aware lint * test(code): resolve sandbox probes through app aliases * fix(code): bound sandbox results before cloning - Enforce string, array, object, depth, element, and byte limits inside the disposable worker - Retain host-side validation as a second structured-data boundary * fix(code): keep JSX and HTML editing modes separate - Switch generated Tailwind output back to OpenPencil JSX before editing - Close the JSX editor when opening HTML/CSS import and avoid stacking both editors * feat(code): support authored Design JSX programs - Allow local constants, function components, arrays, conditionals, fragments, and multiple roots - Preserve replacement positions for multiple selected roots and reject mixed-parent or locked selections - Cover multi-root undo, redo, and all-or-nothing rollback * feat(code): add explicit JSX view mode - Let authors leave CodeMirror without applying a draft - Keep the editable surface and HTML/CSS importer mutually exclusive * feat(code): diagnose unknown JSX vocabulary - Warn on OpenPencil elements and properties that are absent from the canonical schema - Surface diagnostics inline through CodeMirror lint markers * test(code): accept WebKit isolation diagnostics - Cover the sandbox architecture against Playwright WebKit - Accept engine-specific wording while preserving the same unavailable-window assertion * refactor(code): consolidate Design JSX vocabulary - Drive renderer warnings, completion, and diagnostics from one supported-property schema - Recognize locally declared components and add focused schema and transform tests - Replace CodeMirror basicSetup with an explicit feature set and remove the umbrella package * fix(code): support Design JSX variable helpers * refactor(code): unify JSX sandbox validation * fix(code): account for complete sandbox output * fix(code): preserve locked JSX descendants * fix(code): preserve JSX sibling order * fix(code): recompute JSX parent layouts * feat(code): add live code previews * test(code): centralize graph assertions * fix(code): harden live preview sessions * docs: describe live code editing * fix(code): update editor accessibility labels * fix(code): use theme-aware error colors * fix(dev): stop Vite disconnect error loops * fix(code): clarify live preview status * fix(ui): avoid tooltip attribute warnings * test(code): assert semantic preview status * refactor(code): remove obsolete editor messages * fix(code): harden preview concurrency and isolation * fix(code): cancel stale reset previews
2026-08-15 06:48:42 +00:00
--color-error-bg: rgb(185 28 28 / 0.08);
--color-error-border: rgb(185 28 28 / 0.28);
--color-success-bg: #16a34a;
--color-success-bg-hover: #15803d;
--color-code-tag: #0369a1;
--color-code-attribute: #6d28d9;
--color-code-string: #15803d;
--color-code-number: #b91c1c;
--color-code-punctuation: #64748b;
--color-checkerboard: #d1d5db;
--color-checkerboard-muted: #e5e7eb;
--color-ruler-bg: rgb(250, 250, 251);
--color-ruler-tick: rgb(163, 171, 182);
--color-ruler-text: rgb(90, 99, 112);
2026-04-30 14:50:20 +00:00
--color-ruler-label: rgb(255, 255, 255);
2026-04-30 12:52:59 +00:00
}
html,
body,
#app {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
2026-02-28 19:41:34 +00:00
font-family:
Inter,
system-ui,
-apple-system,
sans-serif;
font-size: 13px;
color: var(--color-surface);
background: var(--color-canvas);
user-select: none;
-webkit-user-select: none;
}
2026-02-28 19:41:34 +00:00
input[type='number'] {
appearance: textfield;
-moz-appearance: textfield;
}
2026-02-28 19:41:34 +00:00
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
display: none;
}