Skip to content

Authoring shortcuts: multi-select, drag-to-scrub, Cmd-K, cheat sheet, patterns, hints#11

Merged
avanelsas merged 25 commits intomainfrom
feat/m1-multi-select
Apr 29, 2026
Merged

Authoring shortcuts: multi-select, drag-to-scrub, Cmd-K, cheat sheet, patterns, hints#11
avanelsas merged 25 commits intomainfrom
feat/m1-multi-select

Conversation

@avanelsas
Copy link
Copy Markdown
Owner

Summary

24 commits of editor authoring quality-of-life. No document-model,
project-file, or export changes — saved projects load identically and
every export plugin stays at full feature parity. Test count moves from
486 → 571; release build remains warning-free under Closure Advanced.

The README's new Authoring shortcuts section and the CHANGELOG's
[Unreleased] entry have the user-facing detail. Highlights:

  • Multi-select on canvas + Layers panel (Shift-click, marquee with
    Shift extend). Inspector renders the shared-attribute set; one edit
    fans out to every selected node in a single undo step.
  • Cmd-D duplicate / Cmd-G wrap-in-x-container /
    Cmd-Shift-G wrap with prompt / Cmd-Opt-C / Cmd-Opt-V copy
    and paste attributes (filtered to the target tag's supported attrs).
  • Drag-to-scrub numeric inspector rows; var(--x-…)
    autocomplete on colour and length fields via shadow-DOM datalist
    injection.
  • ? cheat sheet (x-modal + x-typography) and Cmd-K
    command palette (built on BareDOM's x-command-palette) — every
    shortcut and chrome action one keystroke away, theme-inheriting.
  • Layers keyboard nav (siblings via ↑/↓, parent/child via ←/→,
    Alt+↑/↓ reorder).
  • Palette pattern flyoutx-button / x-typography / x-alert
    / x-card / x-grid / … grow a caret with pre-styled chips.
  • Per-tag empty-slot hintsDrop nav links / actions,
    Drop tiles into the grid, etc.

The only change-not-feature worth calling out: :selection in
app-state becomes a vector of node ids (was {:id …} map).
Internal-only refactor; saved project files are unchanged. The
wrap-in whitelist drops x-flex (not in BareDOM 2.4) and gains
x-navbar.

Test plan

  • npx shadow-cljs compile test — 571 tests, 0 failures.
  • npx shadow-cljs release app — 0 warnings under Closure Advanced.
  • clj-kondo --lint src test scripts — 0 warnings, 0 errors.
  • Manual smoke (browser):
    • Load each starter template; existing templates render unchanged.
    • Multi-select 3 cards via Shift-click → Backspace → Cmd-Z restores all three in one step.
    • Drag from empty canvas → marquee selects overlapping nodes; Shift+drag extends; Esc cancels mid-drag.
    • Multi-edit: select 3 buttons with different variants → set variant from the shared-attr row → all three update.
    • Cmd-D duplicates the selection; Cmd-G wraps into x-container; Cmd-Shift-G prompts for a wrapper tag.
    • Cmd-Opt-C on a styled button → Cmd-Opt-V on a default button copies attrs; on an x-card silently drops variant.
    • Drag the width / padding / free-coord x label horizontally → value scrubs; Shift × 10; Cmd-Z reverses the whole drag in one step.
    • Type var( into a width or colour field → datalist drops down with --x-space-* / --x-color-* tokens.
    • Press ? → cheat sheet modal; press ? again or Esc to close.
    • Press Cmd-K → command palette focuses input; type "card" → matching commands; Enter / click to run.
    • Focus the Layers panel → ↑/↓ walk siblings; ←/→ step parent/child; Alt+↑/↓ reorder within slot.
    • Open the palette → tags with patterns (x-button / x-typography / x-card / x-grid / …) show ▾; expand and pick a chip → component lands pre-styled.
    • Drop an empty x-navbar / x-grid / x-tabs → per-tag hint text appears inside the dashed empty-state region.
    • Save → reload the page → IndexedDB autosave restores the document.

🤖 Generated with Claude Code

avanelsas and others added 25 commits April 29, 2026 22:19
:selection moves from a single {:id ...} map to a vector of node ids.
Pure helpers (selected-ids, selected?, single-selected-id) plus
effectful select-one!, select-clear!, select-toggle! cover the call
sites; single-node consumers (resize handles, nudge, inspector,
inline edit) route through single-selected-id and degrade gracefully
under multi-select.

The selection overlay becomes a pool of N borders, one per selected
DOM id; the primary overlay carries the resize handles and only
exposes them when exactly one node is selected. Shift-click on the
canvas or in the layers panel toggles a node in the selection;
pointerdown on empty canvas (or padding) starts a marquee that paints
a rectangle and adds every overlapped node id on release, with Shift
extending the existing selection. Esc cancels mid-drag.

ops/remove-many is the batch counterpart to ops/remove; the :delete
shortcut now drops the entire selection in one commit, idempotent
against cascaded removals and root.

In edit mode the canvas-host gets user-select: none so Shift-click
and marquee-drag don't paint a native text-selection band over the
rendered preview; .bareforge-inline-edit re-enables user-select: text
so the inline text editor works as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ops/duplicate deep-clones a node and its subtree with fresh ids,
inserting the clone as the next sibling. ops/duplicate-many maps
that over a selection in input order, skipping missing/root ids
silently so an unfiltered selection vector is safe to feed.

ops/wrap-many wraps a set of sibling ids in a fresh container at
the position of the lowest-indexed sibling, preserving original
document order inside the wrapper's default slot. Refuses to wrap
when ids don't share a parent+slot or include root.

Cmd-D in shortcuts/dispatch maps to :duplicate; the perform! handler
runs duplicate-many over the canonicalised selection and selects the
new clones — natural follow-up gesture is to drag the duplicates.

Cmd-G maps to [:wrap-in "x-container"]; Cmd-Shift-G maps to
:wrap-in-prompt, which uses window.prompt restricted to a small
container whitelist (x-container, x-grid, x-card, x-flex). The
wrapper becomes the new selection so the user can immediately
keep editing it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inspector field labels for :number kind editors and the free-coord
:layout fields (x/y/w/h) become horizontal drag handles. Pointerdown
captures the start value; pointermove computes new = start + dx,
optionally x10 with Shift; pointerup releases. The first move of a
drag commits normally so the pre-drag state lands in :past, and
every subsequent move uses commit-coalesced! so the entire scrub
collapses into a single undo entry — the same pattern arrow-key
nudge already uses.

The mechanism is opt-in: each scrubbable widget builder calls
attach-scrub-meta! to stash a {:read-fn :commit-fn! :step} spec on
the widget element; the field-row wrapper detects the spec and
wires pointer-scrub! on the row's label, adding the is-scrubbable
class so CSS can show ew-resize on hover and suppress text
selection. Non-numeric rows are unaffected.

CSS-length-shaped attributes (width/height/padding/margin) and
percentage / rem / em values stay non-scrubbable for now —
preserving the suffix while scrubbing needs its own design and is
deferred to a follow-up. Free-coord layout fields are clean numbers
in the document, so they scrub directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Original M2.1 read-fns returned nil when a numeric attribute was
empty, which short-circuited the pointerdown handler's
`(when (number? v) ...)` guard — the gesture engaged the cursor
hint but nothing else, so dragging an unset min/max/step or a
non-:free x/y/w/h field did nothing visible.

Both scrub-eligible builders now fall back to 0 when the underlying
value is missing or unparseable, so the drag picks up at zero and
populates the field as the user drags. Existing numeric values
behave exactly as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cmd-Opt-C captures the single-selected node's :attrs + :props into
an in-memory clipboard slot under :ui :clipboard :attrs (no history,
no DOM clipboard). Cmd-Opt-V applies that snapshot onto every
canonicalised id in the current selection, filtered by each target
tag's supported attributes — pasting an x-button's `variant` onto
an x-card silently drops it instead of stamping an unknown attr.

The gesture deliberately lives on Cmd-Opt-C/V instead of plain Cmd-C/V
so the browser's native copy/paste keeps working in inspector text
fields and the layers panel; the editable-target guard already
covers most surfaces, but the alt requirement keeps the canvas
gesture out of muscle-memory range of native copy/paste entirely.

ops/set-attrs and ops/set-props are the batch counterparts to set-
attr / set-prop — nil values dispatch to the unset path so blanket-
pasting a sparse clipboard correctly clears keys absent on the
source. They're also the foundation for M2.3 multi-select edit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On macOS US layout Option+C produces 'ç' and Option+V produces '√' —
the literal `.key` value never equalled "c" / "v", so dispatch fell
through and the gesture was silently swallowed. event-descriptor now
also captures `.code`, and dispatch matches either ("c" + "KeyC" /
"v" + "KeyV") so the same gesture works across macOS / Linux /
Windows. Added a regression test for the Option-modified key path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
inspector-model under multi-select now surfaces every selected
node and its tag set instead of a bare count, and the inspector's
multi-mode rebuilds as a "Shared attributes" section: every property
that exists on every selected tag (matched by :name and :kind)
becomes an editable row. Mixed values render with a "Mixed"
placeholder and an .is-mixed class (dashed border, slight opacity)
so it's obvious which rows are uniform versus divergent.

Editing a row dispatches a single ops/set-attrs-many (or set-props-
many for booleans) commit, applying the user's value to every
selected node. The doc-changed watcher branch short-circuits in
multi-mode so per-keystroke commits don't tear down the input the
user is typing into; visual "Mixed" markers may lag until the user
re-enters multi-select, an acceptable v1 trade-off.

Widget coverage in v1: enum, string-short, string-long, number,
boolean. CSS-var, layout, and seed-record editors stay single-
select-only — they need their own multi-edit story (different
sources of truth) and the UX for partial overlap there is not
obviously a win.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-multi-enum subscribed to :x-select-input, but x-select fires
:select-change (mirroring build-enum's single-select wiring). The
selected option also needs the `selected=""` attribute on the
matching <option>, not a separate value= write on the host. With
the wrong event the dropdown captured input but never reached the
commit handler — picking a variant from the multi-select list was
silent.

build-multi-boolean was reading read-event-value off an x-switch
event, but switches expose state via .detail.checked / .target.checked
through read-event-checked. Toggle now propagates to every selected
node.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bareforge.meta.design-tokens mirrors the 50 tk-* tokens defined in
baredom.components.x-theme.model as a {:name :category} vector,
categorised colour / length / font / shadow / motion / z / opacity.
Tests assert prefix, category membership, and uniqueness so a
BareDOM bump that adds tokens shows up as a missing-coverage
failure rather than silent autocomplete drift.

inspector/install-token-datalists! mounts two `<datalist>` elements
on document.body — one per surfaced category (colour and length).
Idempotent so a hot-reload doesn't duplicate them. main/init wires
it up alongside baredom/register! so the lists are present before
the inspector first paints.

Colour-shaped attribute fields (:kind :color) and the layout fields
(width / height / padding / margin) reference the matching datalist
via the native `list=` attribute. Typing `var(` into either now
surfaces every theme token as an autocomplete suggestion. Multi-
select shared-attr search fields pick up the same wiring when their
property is colour-shaped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Native HTML <datalist> autocomplete activates only when list= is on
the actual <input>. Setting it on the custom-element host did
nothing — x-search-field doesn't observe `list` and doesn't forward
it to its shadow inner <input part="input">.

attach-datalist-to-shadow-input! reaches into the open shadow root
on the next animation frame (after connectedCallback has populated
it) and writes list= directly on the inner <input>. A retry loop
handles components whose shadow root is built one frame later.

Wired through build-search-field (colour kind), build-layout-field
(length tokens), and build-multi-search-field for multi-select
parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Body-level <datalist> can't be referenced by list= on an <input>
that lives inside a shadow root — the HTML spec scopes the lookup
to the input's own tree, not the host document. The previous fix
attached list= to the shadow input but the global datalist was
unreachable, so autocomplete never fired.

attach-datalist-to-shadow-input! now also clones the global
datalist into the field's shadow root before wiring list=. The
clone is idempotent: if a datalist with the target id already
exists in the shadow root we skip the second injection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shortcut-info is the single source of display data — every entry
carries a :keys string, a :label, and a :category. dispatch grew a
'?' arm returning :show-shortcuts. perform! invokes a callback the
modal registers on init via shortcuts/set-show-shortcuts!, which
keeps the shortcuts ↔ cheat-sheet wiring acyclic (the modal
requires shortcuts for the static data; the action flows the
other way through an atom).

bareforge.ui.cheat-sheet owns the modal element, mounts it on
document.body, groups rows by category-labels, and intercepts
keydown in capture phase while open so Esc closes the modal
without also clearing the canvas selection. Click on the backdrop,
the close button, or '?' a second time also dismisses.

A coverage test asserts every shortcut-info entry uses one of the
declared categories — drift between the data and category-labels
shows up at compile time. Tests for the new dispatch arms (with /
without modifiers, in / out of editable targets) round out the
gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hand-rolled overlay/panel/header markup is replaced with a
single x-modal element. Backdrop click, focus trap, ARIA roles,
size variants, and the open/close transition come from BareDOM —
no parallel implementations to keep in sync, and theming switches
(default/ocean/forest/…) automatically restyle the modal.

Every text node inside the modal renders through x-typography:
title is `h3`, group labels use `overline`, shortcut keys use the
`kbd` variant, and shortcut descriptions use `body2`. Type scale,
font family, and palette inherit from the live theme.

The cheat-sheet still owns Esc handling: a capture-phase keydown
intercepts Escape before x-modal's internal dismiss handler and
the document-level shortcut layer's :deselect handler fire — so
closing via Esc neither double-handles dismiss nor clears the
canvas selection. Backdrop clicks still flow through x-modal-
dismiss, which our handler routes to hide!.

CSS shrinks to the row+group grid inside the modal body; the
overlay, panel chrome, and header styling are deleted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
document.body sits outside the chrome's <x-theme preset="…"> element
(which wraps #app in index.html), so a modal appended to body never
saw the ocean / forest / sunset / … presets and rendered with the
default token palette.

build-modal! now resolves the theme host as `#app`'s parent node,
which is the chrome's outer x-theme. The modal mounts there as a
sibling of #app and inherits every CSS custom property the preset
sets — typography variants, surface colours, focus rings, all
follow the live theme. document.body remains the fallback if the
index.html shape ever changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A searchable modal that runs every chrome action — File menu, panel
toggles, selection ops, the four wrap-in targets, and `Insert <Tag>`
for every BareDOM tag — from one keyboard surface. Built on x-modal
+ x-typography so it inherits the active theme like the cheat sheet.

The empty-query view is a curated short list (file, view, selection,
wrap). Once the user types, the full pool joins in including the 80+
component inserts, ranked by score: prefix matches outrank in-word
substring hits, ties break alphabetically. The match logic is pure
and unit-tested in `command_palette_test`.

ArrowUp / ArrowDown move the active row, Enter runs it, Esc closes,
clicking a row also runs. The active command runs on a microtask so
modal mutations finish before the action mutates state — important
for actions that move focus or open another modal.

shortcuts.cljs grew a Cmd-K dispatch arm and a
set-show-command-palette! callback (mirroring set-show-shortcuts)
that main/init wires to command-palette/toggle!. Shared chrome
thunks (theme / templates / welcome-tour) flow through
command-palette/install! so the palette and the toolbar can't drift
on what those buttons do.

Action helpers in shortcuts.cljs (duplicate!, wrap-in!, copy-attrs!,
paste-attrs!) lose their defn- and become public so the palette
reuses them instead of duplicating the selection-commit-reselect
flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Calling .focus() on the x-search-field host did nothing because
the component doesn't delegateFocus. x-modal's own focus trap also
landed on the host, so opening Cmd-K left the user one Tab away
from typing.

show! now walks into the field's shadow root on rAF and calls
focus() on the real <input part="input">. A retry loop handles the
case where the shadow root isn't populated yet — same pattern as
the datalist clone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hand-rolled modal lost focus to x-modal's focus trap and to
the x-search-field's host-vs-shadow-input split. Switching to the
x-command-palette BareDOM component lets the component own the
modal chrome, scrim, focus, fuzzy filter, ARIA roles, and keyboard
navigation — all the parts we were re-implementing — and inherits
the active theme through its own shadow CSS.

This namespace is now just the command catalogue and a thin
dispatch shim. all-commands maps each command into the JS shape
the component consumes (id, label, group, keywords); a synthetic
id ('cmd-N') indexes a Clojure id→run! map kept on modal-state so
the select event resolves back to the right thunk. Curated File /
View / Selection commands sit alongside the four wrap-in choices
and one entry per registered BareDOM tag, so 'btn' or 'card'
finds the right insert without typing the full x- name.

show! refreshes the items property and calls .open() on the host;
hide! calls .close(); toggle! flips between the two. The component
emits x-command-palette-select with the chosen item; we look up
its id and run the thunk on a microtask so the palette's own close
work finishes first.

The hand-rolled filter / row-list / focus retry / index state and
the matching command-palette CSS are gone. The pure
filter-commands unit tests are dropped along with them — the
component's filter is BareDOM's responsibility now, covered by its
own tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The layers list becomes a focus stop (tabindex=0). With it focused,
ArrowUp/Down walk the depth-first row order, ArrowLeft selects the
parent, ArrowRight enters the first child. Alt+Up / Alt+Down
reorder the selection within its parent slot via ops/move. The
keydown handler stopPropagation's so the document-level shortcut
layer doesn't also fire — an arrow on a free-placed selection no
longer simultaneously navigates and nudges.

The walk and reorder logic land as pure helpers nav-target /
reorder-target so a node test covers the boundaries (top of list,
root, leaf, edge of slot). Existing flatten-tree feeds them.

shortcut-info gains two rows under :navigation describing the
gesture so the cheat sheet picks them up automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first cut traversed the depth-first row list, so pressing Down
on a parent stepped into its first child instead of moving to the
next sibling. Re-reading the M3.2 plan calls for sibling-only
navigation: Up/Down stay inside the parent slot, ArrowRight is the
explicit "step into children" gesture, and ArrowLeft jumps to the
parent. That matches the standard tree-nav idiom (Finder, VS Code
explorer) and keeps each arrow direction's role distinct.

nav-target's :prev / :next branches now read parent-of and the
parent's slot vector instead of indexing into the flattened rows
list. Edge cases (already at slot boundary, root, leaf) return nil
so the keydown handler is a no-op there. Tests rewritten to assert
sibling boundaries and that Down on a parent does NOT enter its
children.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bareforge.meta.patterns is a per-tag map of named pre-styled
configurations (`{:id :label :overrides}`). Each :overrides map
matches the shape palette/seed-for-tag returns, so a pattern lands
in the document by passing it straight to ops/insert-new — no new
plumbing in ops or the reconciler.

Coverage starts with the most-reached-for tags: x-button gets
primary/secondary/ghost/danger/loading; x-typography spans
h1/h2/h3/body/caption/code; x-alert and x-badge cover their
type/variant axes; x-card carries elevation + interactive variants;
x-grid offers 2/3/4-col and sidebar layouts; x-divider, x-switch,
x-checkbox, x-chip round out the seed set. A coverage warning
prints uncovered tags in test output without failing — gaps stay
visible without blocking BareDOM bumps.

Palette tiles for tags with patterns grow a `▾` caret that toggles
an inline flyout of pattern chips. Clicking a chip inserts the
pattern's overrides via the new 2-arity insert-at-selection!.
Pointerdown on caret/chip stopPropagation's so the surrounding
tile's drag-arm doesn't compete with the click.

The 1-arity insert-at-selection! (used by the dnd palette tap path
and the Cmd-K command palette) is unchanged, so no caller migration
is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bareforge.meta.hints carries a curated string for each container
that's specific enough to suggest what belongs inside ("Drop nav
links / actions" inside an empty x-navbar, "Drop tiles into the
grid" inside x-grid, "Drop x-tab here" inside x-tabs, etc.). Tags
without a curated hint fall back to the existing generic
`<tag>  (empty)` placeholder.

The canvas reconciler stamps each container's chosen hint onto a
`data-bareforge-hint` attribute on creation. A new CSS rule
`[data-bareforge-container][data-bareforge-hint]:empty::before`
overrides the generic placeholder when the attribute is present —
specificity wins, no rule duplication. Drag-time slot strips and
preview-mode invisibility both fall out of the existing rules.

While here, drop x-flex from the wrap whitelist (shortcuts +
command palette) — BareDOM 2.4 doesn't ship that tag, so the
Cmd-Shift-G prompt and palette would silently insert a
non-rendering element. x-navbar takes its place as the fourth
wrap target.

Coverage test asserts every hinted tag is registered and that
hint strings stay under the 28-char footprint cap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an "Authoring shortcuts" section between Features and Fields
& bindings, grouping the new editor surfaces by user concern
(multi-select & bulk ops / inspector ergonomics / discoverability)
rather than by milestone. Each bullet leads with the gesture or
keystroke so a reader scanning for "how do I…" finds it fast.

Updates the Status line's test count from 486 to 571 to reflect
the gates after this branch's work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a detailed Added / Changed log under [Unreleased]. Grouped by
user-visible feature so a release reader can match each entry to a
README bullet without diffing the source. Calls out the three
Changed items that ship as side-effects of new features (selection
shape, public action helpers, wrap whitelist) and notes that none
of them touches the document model, project file format, or any
export plugin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The rebase resolution at 51c6cbc kept the older 2-branch render!
shape (implicit-truthy `[(empty-view)]` as the final cond clause),
but a later replayed commit (M2.3) added a third branch. With the
implicit form, cond ended up with an odd number of forms — caught
immediately by clj-kondo. Restoring the explicit `:else` keeps the
3-branch shape that landed mid-feature originally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's cljfmt check caught five files with formatting drift in the
rebased commits — mostly inconsistent destructure spacing and
column-aligned key/value pairs that cljfmt 0.16's defaults
re-align. Pure whitespace, no semantic change. Verified locally:

- cljfmt check — All source files formatted correctly
- npx shadow-cljs compile test — 571 tests, 0 failures
- npx shadow-cljs release app — 0 warnings
- clj-kondo — 0 warnings, 0 errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avanelsas avanelsas merged commit ce8c0b2 into main Apr 29, 2026
1 check passed
avanelsas added a commit that referenced this pull request Apr 29, 2026
Promotes the [Unreleased] section in CHANGELOG.md to a tagged
[0.2.0] — 2026-04-29 entry, adds the standard ### Verified block,
and refreshes the comparison links at the bottom. package.json's
version bumps 0.1.1 → 0.2.0 to match.

This is a minor (not patch) release because the work in PR #11
adds substantial editor surfaces (multi-select, inspector multi-
edit, drag-to-scrub, copy/paste attributes, var() autocomplete, ?
cheat sheet, Cmd-K command palette, layers keyboard nav, palette
pattern flyout, per-tag empty-slot hints). No breaking changes —
saved project files load identically and every export plugin
stays at full feature parity.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avanelsas avanelsas deleted the feat/m1-multi-select branch April 30, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant