Skip to content

Inspector + chrome bug-fix pass#55

Merged
avanelsas merged 6 commits into
mainfrom
fix/inspector-x-select-current-value
May 12, 2026
Merged

Inspector + chrome bug-fix pass#55
avanelsas merged 6 commits into
mainfrom
fix/inspector-x-select-current-value

Conversation

@avanelsas
Copy link
Copy Markdown
Owner

Six bug fixes from a manual smoke session. All six surfaced from poking at the inspector / palette / drag interaction; each is its own focused commit so a bisect or revert lands at the right scope.

# Commit Bug it fixes
1 `a00a1d9` x-select pickers (placement, enum, multi-enum) painted blank even with a stored value. BareDOM's x-select renders from the host's `value` attribute, not ``; the three sites only set the latter.
2 `d7aaac9` Fresh `x-button`'s size and type pickers blank because `model/current-value` returns nil for unset attrs and the picker had nothing to display. Fall back to `(:default prop)` at the display layer.
3 `5982fc6` Numeric inspector inputs displayed float-math tails like `939.8698120117188`. New `bareforge.util.coerce/format-decimal` rounds to 2 decimals + trims trailing zeros at the display layer only — stored values untouched. Pinned by 6 unit tests.
4 `e95e765` Promoting `:flow → :free` left `:x / :y` nil. First drag captured `free-initial-x = 0`, snapping the element to canvas origin + cursor-delta. Capture the element's visual position vs its parent at promotion time and commit `:placement :free + :x + :y` atomically.
5 `61426fc` Follow-ups: `update-fields-in-place!`'s `sync-widget-value!` wasn't using `format-decimal`, so build and patch paths displayed different things (the patch update showed raw floats). Also, the captured position was off by parent-border pixels — CSS `position:absolute` is measured from the padding box, not the border-box BCR. Subtract `clientLeft / clientTop`.
6 `a20a23e` Dragging a palette item painted native text selection across other palette labels; the selection survived the drop and blocked the next drag. `user-select: none` on the base `.panel` rule covers palette / layers / inspector chrome; native inputs inside keep allowing text selection.

Net change

Test plan

  • `clj-kondo --lint src test scripts` — 0 errors, 0 warnings
  • `cljfmt check` — clean
  • `npx shadow-cljs compile test` — 0 failures, 0 errors (6 new tests for format-decimal)
  • `npx shadow-cljs release app` — 0 warnings
  • Manual: select a button → variant / size / type all show their current or default values (no blank pickers)
  • Manual: set placement to `:free` → x / y populate with the element's current position, element doesn't shift, first drag follows cursor cleanly with no jump on release
  • Manual: free-drag a node → x / y show as e.g. `257.29`, not `257.29168701171875`
  • Manual: drag from palette → no text selection left behind, next drag starts cleanly

🤖 Generated with Claude Code

avanelsas and others added 6 commits May 12, 2026 16:00
BareDOM's x-select renders its inner native <select> from the host
element's `value` attribute — `<option selected>` on a slotted
option is not consulted, because `sync-options!` clones the option
nodes into the shadow and then `render!` writes `(set! (.-value
select-el) (or value ""))` using the host's attr. Three inspector
sites set `selected=""` on the matching option but never mirrored
the value onto the host, so the picker painted blank even when the
stored value was visible elsewhere in the doc:

- `build-placement-field` (the layout placement picker).
- `build-multi-enum` (the multi-select shared enum row).
- `build-enum` (via `append-enum-options!`).

Add the host-value mirror at each site after appending options.
The previous PR (#53) accidentally introduced the visible
regression on enum rows by routing them through
`build-widget-shell` — the shell set the value *before* options
existed, which works on the first paint but is fragile against
shadow re-renders and never had the explicit post-options mirror
the legacy code lacked. Centralising the fix in
`append-enum-options!` keeps every enum row honest going forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After PR #53's enum host-value mirror, variant pickers painted
correctly because x-button's variant is set on insertion. size and
type were still blank: x-button declares them with :default "md" /
"button", BareDOM uses the default when the attribute is unset,
but model/current-value returns nil for absent :attrs entries —
so the picker had nothing to display.

Lift the display value to `(or stored (:default prop))` inside
append-enum-options! (covers every single-select enum row) and
mirror the same fallback in build-multi-enum's host-value branch
(joint nil + not mixed → show the agreed default). The stored-vs-
default distinction was never useful at the display layer — the
inspector should match what BareDOM is rendering, which is the
default when nothing is explicitly stored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A free-placement node's :layout :x / :y get their values from
(/ (- clientX start-cx) zoom) during a drag, which routinely
produces float-precision artefacts like 257.29168701171875. The
inspector painted these via (str raw), so the input grew a
twelve-decimal tail that the user couldn't ignore.

Add `format-decimal` to bareforge.util.coerce — pure helper that
rounds a number (or a purely-numeric string) to a configurable
number of decimal places (default 2), trims trailing zeros and a
trailing decimal point, and passes non-numeric strings (CSS
lengths like "50%", compound forms like "10px 20px") through
unchanged. Applied in two display sites:

- build-free-coord-field: turns 257.29168701171875 into "257.29"
  and 939.8698120117188 into "939.87".
- build-widget-shell: covers every other inspector input that
  paints a stored value (numeric attrs stored as (str <float>) get
  the same treatment; existing text/enum values pass through).

Storage layer is unchanged on purpose — only display formatting.
The underlying float values stay in the doc so undo / export /
load-roundtrip semantics don't move. If sub-decimal precision ever
becomes load-bearing for a field, callers can pass a higher
decimals count.

Six new tests pin the rounding contract, the integer / trailing-
zero trim, custom precision, numeric-string handling, and CSS-
length passthrough.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a node's placement transitions from :flow (or :background) to
:free, the layout map gains `:placement :free` but `:x`/`:y` are
absent. The reconciler renders the element at its static-position
fallback (or wherever absolute-with-no-coords lands it), so the
user sees the button where it always was. On the very first drag,
`start-from-canvas!` reads `(or (get-in node [:layout :x]) 0)` →
0, captures that as `free-initial-x`, then commits new x = 0 +
cursor-delta. The element snaps to the canvas origin plus the
drag delta — far from where the user clicked.

Fix at promotion time so the layout map is consistent from the
moment :free is selected. `commit-promote-placement!` detects the
flow → free transition, reads the element's current viewport BCR
relative to its rendered parent's BCR, divides by zoom to get
content pixels (same math the drag delta uses), and commits
placement + x + y in a single state/commit!. The follow-up drag
sees real coords and behaves identically to a second drag would.

Implemented inside the inspector's `build-placement-field` event
handler — that's where DOM access and the doc-op already meet, and
the helper stays out of drag.cljs which has no business knowing
about inspector picker transitions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier commit (e95e765) captured an element's visual position
when its layout was promoted to :free, but two issues remained:

1. The inspector x and y fields painted blank instead of the
   captured coordinates. `update-fields-in-place!` flowed through
   `sync-widget-value!` which set the `value` attribute with the
   raw new-v unchanged — and `desired-widget-value` for layout
   widgets returns the raw number (a long-tail float) without
   going through `format-decimal`. The build path uses
   format-decimal, so the build and patch paths displayed
   different things. Route sync-widget-value! through
   format-decimal for non-boolean values, so the patch update
   shows the same rounded form the initial build does.

2. The captured x/y were off by the parent's border width. CSS
   `position: absolute; left: X; top: Y;` is measured from the
   containing block's padding box, not its BCR (which sits at the
   outer edge of the border). Subtracting the parent's clientLeft
   / clientTop — the rendered border width — brings the captured
   offset into the same reference frame the reconciler uses when
   it re-renders the just-promoted element. Without this a parent
   with a 1 px border shifts the element 1 px on commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dragging a palette item across other labels paints the labels with
the native browser selection highlight, and the highlight survives
the drop. Subsequent palette pointerdowns get intercepted by the
selection layer (the browser is mid-selection), so the next drag
can't start until the user clicks somewhere — typically the
search field — to clear the selection.

A few palette sub-elements already had `user-select: none`
(.palette-category-summary, .palette-item-caret) but the .palette-item
itself didn't, so the labels were free-game for selection. Apply
`user-select: none` to the base `.panel` rule so every tool panel
(palette, layers, inspector) inherits the same anti-selection
behaviour on its chrome. Native `<input>` and `<textarea>` elements
inside the panels still allow text selection because the user-agent
stylesheet overrides user-select for form controls — typing into
the inspector search fields and text-areas keeps working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avanelsas avanelsas merged commit 797d0c9 into main May 12, 2026
1 check passed
@avanelsas avanelsas deleted the fix/inspector-x-select-current-value branch May 12, 2026 15:38
@avanelsas avanelsas mentioned this pull request May 12, 2026
9 tasks
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