openpencil/packages/docs/user-guide/checking-designs.md

63 lines
4.4 KiB
Markdown
Raw Normal View History

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
---
title: Checking Designs
description: Find accessibility, consistency, and structure issues with the Lint panel and canvas issue markers.
---
# Checking Designs
OpenPencil checks the current page as you work and points out layers that break common design rules: text that is hard to read, controls that are too small to tap, colors that should use a variable, and spacing off the scale.
## Lint Panel
Open the **Lint** tab in the right panel. Its badge shows how many errors and warnings the page has.
- **Page** lists every issue on the current page; **Selection** narrows the list to the selected layers and everything inside them; **Document** lists every page's issues, tagging the ones on other pages. Clicking one of those switches to its page. Pages of a large `.fig` file that have not been opened yet are checked once you open them.
- Issues are grouped by rule, most severe first. Errors and warnings start expanded; suggestions start collapsed.
- The severity buttons under the scope switch filter errors, warnings, and suggestions on and off.
- Hover a group title to read what the rule checks and why.
Hover an issue to highlight its layer on the canvas. Click it to select the layer; if it is off screen, the canvas pans to it, and zooms out only when the layer does not fit.
## Fixing Issues
Rows that can be fixed in one step show a button on hover:
- A hardcoded color that matches one of the document's color variables binds to it (link button). The row names the variable.
- Subpixel positions and sizes round to whole pixels. Values that auto layout or text resizing sets, and vector artwork and the parts of groups, are left alone.
- Off-scale corner radius and spacing change to the nearest scale value, and text below the minimum size grows to it (wand button). These change the design, so they apply one row at a time.
feat(lint): suggest group-to-frame and hidden-layer fixes; keep canvas edits in code previews (#870) * feat(lint): suggest converting groups to frames and deleting hidden layers no-groups and no-hidden-layers now carry suggestions the Lint panel, the lint_fix tool and editor.applyLintFixes can apply. A group becomes a frame in place, keeping its id, children, bounds and look; a hidden layer is deleted with its children. Neither is offered for locked layers or inside components and instances, and both are checked again when applied. The editor gains convertGroupToFrame and deleteNodes, which deleteSelected now uses, and applies lint fixes through a bridge so structure changes and property updates share one undo step. lint_fix becomes a document mutation because atomic tools cannot remove layers. * fix(code): keep canvas edits made while replaced code waits to render Replacing all the code drops its layer links until the preview links it again, so a canvas edit in the preview delay could not be patched into the code and the preview drew over it. Edits made while code waits to render are now applied again once the preview has linked the code, in the same undo step, and reach the code like any other canvas change. * fix(figma-api): default paint opacity and visibility like Figma Plugin scripts may leave out a paint's opacity and visible; Figma reads them back as 1 and true. The fills and strokes setters stored the paint as given, so the Design panel received an undefined opacity. * build(dev): forward only errors from the browser console Vite forwards browser logs when an agent starts the dev server. Serializing a Vue warning's component props walks the editor state and freezes the tab, so warnings stay in the browser console. * fix(code): follow values while they are dragged or scrubbed Live previews change layers without a new scene version, so the code kept the old value until the gesture ended. The Code tab now follows preview updates once per frame, as the Design panel does, and goes back when the gesture is cancelled. * fix(code): keep canvas edits when the replaced code fails to preview A failed preview took the canvas edits waiting for it and dropped them, and stopped recording new ones, so correcting the code drew over them. The edits now wait for the next preview.
2026-10-04 12:46:06 +00:00
- A group converts to a frame in place, keeping its layers, position, and look (frame button), and a hidden layer can be deleted (trash button). Neither is offered for locked layers or layers inside a component or instance, whose structure belongs to the component.
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
Binding colors and rounding pixels keep the design as it looks, so their groups also offer **Bind all** or **Fix all**. Every fix is a single undo step.
Other issues show the value that needs attention, such as a contrast ratio or a touch target size, so you can fix them in the Design panel.
## Canvas Markers
Layers with errors and warnings carry a marker at their top-right corner: red for errors, amber for warnings. Suggestions appear only in the Lint panel.
- Hover a marker to see its issues. Click it to select the layer and open its issues in the Lint panel.
- Markers that would overlap merge into one showing their combined count.
- When a layer is too small to see at the current zoom, its marker moves to the nearest enclosing layer large enough to point at.
- Errors and warnings outside the visible canvas are pinned to its edge, pointing toward them. Hover a pin to see them; click it to bring the nearest of the most severe into view and open it in Lint.
- Hidden layers and layers clipped out of view by a frame keep their issues in the panel but get no marker.
Pages with errors or warnings show their count in the page list, checked in the background while you work. The Layers panel marks the same layers: a layer with errors or warnings shows the most severe as an icon, and a collapsed layer with issues inside it shows a dot.
Turn markers and Layers panel marks on or off with **View → Design issues**, or with **Show issues on canvas** in the Lint panel's settings menu.
feat(code): link the Code tab to canvas layers and sync both ways (#805) * feat(code): link code to canvas layers and underline design issues Code in the Code tab and layers on the canvas were unrelated: finding the element behind a layer, or the layer behind an element, meant reading names. Generated Design JSX and Tailwind JSX report the layer behind each element in the order elements open, and edited Design JSX keeps the source line of every element through the sandbox and renderer, so hovering an element highlights its layer, Cmd/Ctrl-click brings it into view without changing the selection the code shows, and errors and warnings from the design check are underlined on the property that causes them. * feat(code): explain the Code tab when nothing is selected With no selection the editor showed a starter frame that read like a real layer. The tab now says it shows the selected layers' code and offers Write JSX, which opens the editor focused on the starter template. * refactor(code): group code-to-layer linking into its own domain Layer link types, issue mapping, and the hover and reveal behavior move from the Code panel and a component file into src/app/code/layers, with useCodeLayers as the panel's entry point, so app code no longer imports types from components. * fix(code): underline off-scale gaps after the spacing rule renamed its property * feat(code): mark the layer of the element around the cursor Hover highlighting and ⌘-click reveal replaced by one model: the element around the cursor marks its opening and closing tag names and outlines its layer on the canvas while the editor has focus. ⌘-click also collided with CodeMirror's add-a-cursor gesture. Read-only Tailwind JSX now takes a cursor so it links the same way. Leaving the editor now ends a live Design JSX edit as one undo step. Before, canvas edits made after typing never reached the code until the tab was reopened, and their undo entries landed before the edit's. * feat(code): sync the Code tab and the canvas both ways by patching Canvas edits now patch the Design JSX a person wrote instead of waiting for them to leave the editor: each linked element remembers the layer as Design JSX last wrote it, and a canvas change rewrites only the attributes, text and child elements that differ from that base, as CodeMirror changes that keep the cursor, comments, formatting and history. Attributes written as expressions are never overwritten; the code marks them when the canvas now differs. Untouched code is regenerated with a minimal text change. Code edits update layers in place: the new render is reconciled into the existing layers (reconcileRenderedLayers), which keep their ids, so links, selection and canvas edits survive typing. Each edit is one coalesced undo step, replacing the restore-and-rerender preview and the commit on blur. * feat(code): patch reordered layers and aliased properties in edited code Reordering layers on the canvas now moves their elements in code a person wrote: each child element and the blank lines and comments above it form a block kept as written, and the children are written again in the new order, staying linked. Children that cannot move safely, such as a loop between them, keep their order and are marked. Properties accepted under several names now come from one alias table in the Design JSX schema, which the renderer resolves through and the patcher and issue underlines use, so a canvas change to `w` patches `width` where the person wrote that, instead of adding a second attribute. * feat(code): keep the cursor in moved code and patch values written in style A reorder rewrites the children span in one change, which collapsed a cursor or out-of-sync marker inside a moved element to the span's edge. The patch now carries where each block moved and places selections and markers inside it at their new position. Properties the renderer also reads from style={{ … }} come from a table in the Design JSX schema instead of a hand-written list, keeping the rule that an attribute under any of its names wins. The patcher uses it to update a value written in style where it is, as a number or a px string as written; values the renderer cannot read, such as '50%', are marked. The layer patcher is split by concern: syntax helpers, attribute and style patches, child patches, out-of-sync state and transaction assembly. * feat(code): show the code's layer on the canvas as a tinted box The layer of the element around the cursor used the canvas hover slot, so moving the pointer over the canvas replaced it and the two read the same. It now has its own shared editor state, codeFocusNodeId, drawn as the hover outline over a light tint in every pane: hover stays an outline and the selection keeps its handles, without borrowing the dashed outlines that already mean component sets, drag parents and ghosts. * fix(code): write added and removed layers when a reorder cannot move the code When children could not be moved, such as two written on one line, the patch marked the order and returned before adding or removing elements, so a layer created in the same change never reached the code. It now marks the order and still writes additions and removals. * refactor(design-jsx): format the rebased layer description and stroke aliases * feat(code): mount the layer-linked code editor through useCodeMirror Master moved the code editor onto the shared useCodeMirror composable. Its layer links, issue underlines, canvas patches, minimal text updates, autofocus and read-only cursor now sit on that composable instead of a hand-mounted view.
2026-10-04 10:08:40 +00:00
## In the Code Panel
The Code tab underlines errors and warnings on the Design JSX or Tailwind JSX of their layers, on the property that causes the issue when there is one, such as `size={10}` for small text. Hover the underline to read the issue. The underlines follow your edits while you change the code live.
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
## Rules
The settings menu in the Lint panel switches between rule presets:
- **Recommended** — balanced defaults for everyday work.
- **Strict** — every rule as a warning or error.
- **Accessibility** — contrast, touch target size, and text size only.
Turn off a single rule from its group's menu. **Turn on turned-off rules** in the settings menu brings them back. Presets and turned-off rules are saved between sessions.
The same rules run from the command line with `openpencil lint`; see [Inspecting Files](/programmable/cli/inspecting).