Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
da1f1ee
Multi-select: shift-click, marquee, batch delete
avanelsas Apr 29, 2026
93c6b95
Cmd-D duplicate, Cmd-G wrap-in selection
avanelsas Apr 29, 2026
dffd7b3
M2.1: drag-to-scrub on numeric inspector rows
avanelsas Apr 29, 2026
169d217
Scrub: default unset numerics to 0
avanelsas Apr 29, 2026
f9bb7e8
M2.2: Cmd-Opt-C / Cmd-Opt-V copy and paste attributes
avanelsas Apr 29, 2026
6d2971b
Cmd-Opt-C / Cmd-Opt-V: match macOS .code fallback
avanelsas Apr 29, 2026
051858a
M2.3: multi-select inspector edits shared attributes
avanelsas Apr 29, 2026
c933784
M2.3: fix multi-edit enum/boolean event names
avanelsas Apr 29, 2026
e81e49c
M2.4: BareDOM theme tokens autocomplete on colour and length fields
avanelsas Apr 29, 2026
7d91bdf
M2.4 fix: attach datalist to x-search-field shadow input
avanelsas Apr 29, 2026
5bce74f
M2.4 fix: clone datalist into the field's shadow root
avanelsas Apr 29, 2026
afc2e39
M3.3: '?' opens a shortcut + gesture cheat sheet
avanelsas Apr 29, 2026
f8751f0
Cheat sheet: x-modal + x-typography for native theming
avanelsas Apr 29, 2026
b56c650
Cheat sheet: mount inside chrome x-theme so theming inherits
avanelsas Apr 29, 2026
e111511
M3.1: Cmd-K command palette
avanelsas Apr 29, 2026
fbb649b
Command palette: focus the shadow inner input on open
avanelsas Apr 29, 2026
245e0c3
Command palette: rebuild on top of x-command-palette
avanelsas Apr 29, 2026
675cd48
M3.2: keyboard nav in the Layers panel
avanelsas Apr 29, 2026
5f77b46
Layers nav: Up/Down walk siblings, not depth-first
avanelsas Apr 29, 2026
d0104f2
M3.4: inline component patterns in the palette
avanelsas Apr 29, 2026
bfdddf0
M3.6: per-tag empty-slot hints
avanelsas Apr 29, 2026
7d65f4f
README: document the new authoring shortcuts
avanelsas Apr 29, 2026
ba3db25
CHANGELOG: fill in [Unreleased] for the authoring-shortcuts work
avanelsas Apr 29, 2026
24843d1
Rebase fixup: explicit :else in render! cond
avanelsas Apr 29, 2026
8a68d44
cljfmt: reformat 5 files
avanelsas Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 97 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,103 @@ possible" — I won't promise API stability until `1.0.0` lands.

## [Unreleased]

Nothing yet.
Editor authoring quality-of-life. No document-model, project-file,
or export changes — saved projects load identically, every export
target stays at parity. Test count: 571 (up from 486), zero
release-build warnings under Closure Advanced.

### Added

- **Multi-select** on the canvas and Layers panel.
- Shift-click extends; drag from empty canvas starts a marquee
rectangle (Shift+drag extends).
- The selection overlay becomes a pool — one 1px border per
selected DOM id. Resize handles only when exactly one node is
selected.
- Esc clears the selection; Delete / Backspace removes every
selected node in one commit via `ops/remove-many`.
- **Multi-select inspector edit.** With more than one node
selected, the Inspector renders the shared-attribute set across
all of them. Editing a row dispatches a single
`ops/set-attrs-many` (or `set-props-many`) commit; mixed values
show with a `Mixed` placeholder and an `is-mixed` class.
- **Cmd-D duplicate** the selection (deep-clone with fresh ids
throughout the subtree).
- **Cmd-G** wraps the selection in an `x-container`;
**Cmd-Shift-G** prompts for `x-grid` / `x-card` / `x-navbar`. New
`ops/wrap-many` keeps a sibling set's document order intact
inside the new wrapper.
- **Cmd-Opt-C / Cmd-Opt-V** copy and paste attributes between
nodes. Paste is filtered to the target tag's supported attrs so
`x-button` → `x-card` silently drops `variant` instead of
stamping unknown attributes. Macro-OS Option-modified key (`ç`,
`√`) falls back to `.code` (`KeyC`, `KeyV`) for cross-platform
parity.
- **Drag-to-scrub** numeric inspector rows. The label of `:number`
kind editors and free-coord `:layout :x / :y / :w / :h` fields
becomes a horizontal drag handle; Shift × 10 step. The whole
drag is one undo entry via `state/commit-coalesced!`.
- **BareDOM theme-token autocomplete.** Colour and length fields
surface every `--x-color-*` / `--x-space-*` / `--x-radius-*` /
`--x-font-size-*` / `--x-border-width` token via a native
`<datalist>` injected into the field's shadow root. New
`bareforge.meta.design-tokens` mirrors the 50 `tk-*` tokens from
`baredom.components.x-theme.model`.
- **`?` keyboard cheat sheet.** Lists every shortcut and gesture
grouped by Editing / Selection / Navigation / File / View. Built
on `x-modal` + `x-typography` so it inherits the active theme
preset. Source of truth is the static `shortcut-info` data;
unit tests assert category coverage.
- **Cmd-K command palette.** Built on the BareDOM
`x-command-palette` web component — owns its own focus, fuzzy
filter, scrim, ARIA roles, and theme inheritance. Curated File /
View / Selection commands plus one entry per registered BareDOM
tag (`Insert <tag>`) plus the four wrap-in targets. Selection
flows through a synthetic-id → run-fn dispatch map.
- **Layers panel keyboard navigation.** Focus the Layers tree, then
↑ / ↓ walk siblings within the parent slot, ← / → step to parent
/ first child, and **Alt+↑ / Alt+↓** reorder within the slot via
`ops/move`. The keydown handler `stopPropagation`s so an arrow on
a free-placed selection no longer simultaneously navigates and
nudges.
- **Inline component patterns.** New `bareforge.meta.patterns`
carries pre-styled named configurations per tag: `x-button` →
primary / secondary / ghost / danger / loading; `x-typography` →
h1 / h2 / h3 / body / caption / code; `x-alert`, `x-badge`,
`x-card`, `x-chip`, `x-grid`, `x-divider`, `x-switch`,
`x-checkbox` covered. Tags with patterns grow a `▾` caret on
their palette tile that toggles an inline flyout of pattern
chips. A coverage warning prints uncovered tags in test output.
- **Per-tag empty-slot hints.** New `bareforge.meta.hints` provides
hint strings (`Drop nav links / actions`, `Drop tiles into the
grid`, `Drop x-tab here`, etc.) for ~20 container tags. The
canvas reconciler stamps `data-bareforge-hint` on creation;
existing CSS reads it via `attr()` to override the generic
`(empty)` placeholder. Drag-time and preview-mode invisibility
fall out of existing rules.

### Changed

- `:selection` in `app-state` is now a vector of node ids. New
pure helpers `state/selected-ids`, `selected?`,
`single-selected-id`; new effectful `select-one!`, `select-clear!`,
`select-toggle!`. Internal-only refactor — single-node consumers
(resize handles, nudge, inspector lookup, inline-edit teardown)
route through `single-selected-id` and degrade gracefully under
multi-select. **Saved project files are unchanged.**
- Action helpers in `ui.shortcuts` (`duplicate!`, `wrap-in!`,
`copy-attrs!`, `paste-attrs!`) become public so the command
palette reuses them instead of re-implementing the
selection → commit → reselect flow.
- Wrap-in whitelist updated to `x-container / x-grid / x-card /
x-navbar`. **`x-flex` is removed** — it isn't a tag in BareDOM
2.4, so the previous Cmd-Shift-G prompt would have inserted an
unknown element. Cmd-G default behaviour (`x-container`) is
unchanged.
- In edit mode, the canvas host gets `user-select: none` so the
Shift-click and marquee-drag gestures don't paint a native
text-selection band over the rendered preview. Inline-text
editing's textarea overlay re-enables `user-select: text`.

## [0.1.1] — 2026-04-28

Expand Down
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,66 @@ I wondered if I could automate some of that and with that the idea for Bareforge
- **Escape to deselect** + full keyboard shortcuts (Cmd-Z / Cmd-Shift-Z /
Delete / arrow-key nudge)

## Authoring shortcuts

A set of power-user gestures that make daily editing faster. Every
keyboard shortcut lives in a press-`?` cheat sheet; every action is
reachable through a Cmd-K command palette.

### Multi-select & bulk ops

- **Shift-click** any node (canvas or Layers panel) to extend the
selection. **Drag from empty canvas** for marquee select; hold
**Shift** to extend.
- With multiple nodes selected, the Inspector shows the **shared
attributes** across them. Edit a row once, every selected node
updates in a single undo step. Mixed values render with a `Mixed`
placeholder.
- **Cmd-D** duplicates (deep-clone with fresh ids); **Cmd-G** wraps
the selection in an `x-container` (**Cmd-Shift-G** prompts for
`x-grid` / `x-card` / `x-navbar`); **Delete** removes the whole
set in one commit.
- **Cmd-Opt-C / Cmd-Opt-V** copy attributes from the selection and
paste them onto another node, filtered to the target tag's
supported attrs — a paste from `x-button` onto an `x-card`
silently drops `variant` instead of stamping an unknown attr.

### Inspector ergonomics

- **Drag-to-scrub** numeric labels — the `min` / `max` / `step` rows
and the free-coord `:layout :x / :y / :w / :h` rows. Drag the
label horizontally; hold Shift for ×10 steps. The whole drag is
one undo entry.
- **`var(--x-…)` autocomplete.** Type `var(` into a colour or length
field and a native `<datalist>` surfaces every BareDOM theme
token — `--x-color-primary`, `--x-space-md`, `--x-radius-lg`,
and so on, sourced from `x-theme` and resolved live by the
active preset.

### Discoverability

- **`?`** opens a cheat sheet with every keyboard shortcut and
gesture, grouped by category. Built on `x-modal` + `x-typography`
so it inherits the live theme preset.
- **Cmd-K** opens a fuzzy command palette built on
`x-command-palette`. Insert any of the 90 BareDOM tags by typing
a fragment of the name; toggle the theme editor, the templates
panel, preview mode, or the cheat sheet itself — every toolbar
action is one keystroke away.
- **Layers keyboard nav.** Focus the Layers tree, then ↑ / ↓ walk
siblings, ← / → step to parent / first child, **Alt+↑ / Alt+↓**
reorder within the parent slot.
- **Palette pattern flyout.** Components with curated variants —
`x-button` (primary / secondary / ghost / danger / loading),
`x-typography` (h1–h3 / body / caption / code), `x-alert`,
`x-badge`, `x-card`, `x-grid` (2-col / 3-col / 4-col / sidebar),
and more — show a `▾` caret next to their palette tile. Expand,
pick a chip, the component lands pre-styled.
- **Empty-slot hints.** Empty containers in edit mode show a per-tag
prompt — `Drop nav links / actions` inside an empty `x-navbar`,
`Drop tiles into the grid` inside an `x-grid`, `Drop x-tab here`
inside `x-tabs` — so the next move is always obvious.

## Fields & bindings

Give a container a **name** and it becomes a component group with its
Expand Down Expand Up @@ -452,7 +512,7 @@ structure, then customise content, theme, and layout in the editor.
**Early alpha.** Feature-complete: 90 BareDOM components in the
palette, four export plugins at full feature parity (HTML, bundle,
CLJS, vanilla-JS), nine starter templates, first-run welcome tour,
doc-level XSS sanitiser, CSP + SRI on every export. 486 tests / 0
doc-level XSS sanitiser, CSP + SRI on every export. 571 tests / 0
release-build warnings under Closure Advanced. Expect rough edges
on less-common BareDOM components until their augment entries are
hand-tuned. See [`CHANGELOG.md`](./CHANGELOG.md) for what's in
Expand Down
100 changes: 100 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@
overflow: auto;
position: relative;
}
/* In edit mode, suppress native text selection inside the canvas:
Shift-click and marquee-drag would otherwise paint a browser
text-selection band over the rendered preview, fighting our own
multi-select. Inline text editing has its own textarea overlay,
which we re-enable below. Preview mode leaves selection untouched
so users can interact with their built page normally. */
.chrome[data-mode="edit"] .canvas-host {
user-select: none;
-webkit-user-select: none;
}
.bareforge-inline-edit {
user-select: text;
-webkit-user-select: text;
}
.bareforge-selection-overlay {
position: absolute;
pointer-events: none;
Expand Down Expand Up @@ -157,6 +171,30 @@
.bareforge-selection-handle[data-handle="s"] { bottom: -5px; left: 50%; margin-left: -5px; cursor: s-resize; }
.bareforge-selection-handle[data-handle="sw"] { bottom: -5px; left: -5px; cursor: sw-resize; }
.bareforge-selection-handle[data-handle="w"] { top: 50%; left: -5px; margin-top: -5px; cursor: w-resize; }
.bareforge-marquee {
position: absolute;
pointer-events: none;
z-index: 99;
border: 1px dashed var(--x-color-primary, #4f46e5);
background: rgba(79, 70, 229, 0.08);
}
/* --- cheat sheet ----------------------------------------------- */
/* x-modal carries its own backdrop / panel chrome; we only style
the row + group layout inside the modal body. Typography (kbd,
overline, body2, h3) inherits from the active theme. */
.cheat-group + .cheat-group { margin-top: 18px; }
.cheat-row {
display: grid;
grid-template-columns: 240px 1fr;
gap: 14px;
padding: 4px 0;
align-items: baseline;
}

/* x-command-palette owns its own modal chrome and shadow-DOM
styling — no host-level CSS needed. */
.bareforge-marquee[data-hidden] { display: none; }
.chrome[data-mode="preview"] .bareforge-marquee { display: none; }
.bareforge-inline-edit {
position: absolute;
z-index: 101;
Expand Down Expand Up @@ -226,12 +264,50 @@
.palette-item:active {
background: var(--x-color-surface-active, rgba(127, 127, 127, 0.2));
}
.palette-item-header {
display: grid;
grid-template-columns: 1fr auto auto;
column-gap: 8px;
align-items: baseline;
}
.palette-item-label { font-size: 13px; font-weight: 500; }
.palette-item-tag {
font-size: 11px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
color: var(--x-color-text-muted, rgba(127, 127, 127, 0.7));
}
/* Pattern caret + inline flyout (M3.4). The caret rotates when
open so the affordance reads as expand/collapse. */
.palette-item-caret {
font-size: 11px;
color: var(--x-color-text-muted, rgba(127, 127, 127, 0.7));
cursor: pointer;
padding: 0 4px;
user-select: none;
transition: transform 120ms ease;
}
.palette-item-caret[data-open] { transform: rotate(180deg); }
.palette-patterns {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 6px;
}
.palette-patterns[data-hidden] { display: none; }
.palette-pattern {
font-size: 11px;
padding: 3px 8px;
border-radius: 999px;
background: var(--x-color-surface, rgba(127, 127, 127, 0.1));
color: var(--x-color-text);
border: 1px solid var(--x-color-border, rgba(127, 127, 127, 0.2));
cursor: pointer;
user-select: none;
}
.palette-pattern:hover {
background: var(--x-color-surface-hover, rgba(127, 127, 127, 0.18));
border-color: var(--x-color-primary);
}
.palette-empty {
font-size: 12px;
color: var(--x-color-text-muted, rgba(127, 127, 127, 0.7));
Expand Down Expand Up @@ -317,7 +393,24 @@
color: var(--x-color-text-muted, rgba(127, 127, 127, 0.7));
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* Numeric labels become drag-to-scrub handles. The `is-scrubbable`
class is added by `pointer-scrub!` only when the widget exposes
a scrub spec, so non-numeric labels keep their default cursor. */
.inspector-field-label.is-scrubbable {
cursor: ew-resize;
user-select: none;
-webkit-user-select: none;
touch-action: none;
}
.inspector-field-widget { width: 100%; }
/* Multi-select shared-attr widgets render the .is-mixed class
when values disagree across the selected nodes. A subtle
border tint plus muted text on the placeholder makes the
state legible without shouting at the user. */
.inspector-field-widget.is-mixed {
opacity: 0.85;
border-style: dashed;
}
/* Compact x-color-picker for the Inspector. Halves the main
colour-area height and tightens the strip heights + gap so a
picker with alpha still fits where the default (alpha-less)
Expand Down Expand Up @@ -671,6 +764,13 @@
pointer-events: none;
user-select: none;
}
/* Per-tag drop hint (M3.6) — overrides the generic placeholder
when the canvas reconciler stamped a `data-bareforge-hint`
attribute (see meta/hints.cljs for the data table). The more
specific selector wins over the generic rule above. */
.canvas-host [data-bareforge-container][data-bareforge-hint]:empty::before {
content: attr(data-bareforge-hint);
}
/* Containers that host an absolute :background child get this
class from the reconciler so they form a positioned ancestor
without touching their own inline style. `isolation: isolate`
Expand Down
Loading
Loading