
Quey Toolbar v2
Frame absorbs Layout, scrub fields for padding and gap, wrapped-row reorder fixes, agent modes, real cancellation, and a toolbar you can actually put away — release notes for 0.4.0.
Shipped as 0.4.0
Somewhere in the last three PRs, the extension's own manifest quietly changed its mind about what Quey is. The name went from “Quey: Visual Editing Feel of Figma/Framer” to “Quey: Visual interface for AI coding agents.” That's not a rebrand — it's an admission of where the actual engineering went this cycle. v2 isn't a new coat of paint on the Style panel; it's the Style panel, the reorder engine, and the agent runtime all getting rebuilt around the same idea: you point, the agent acts, and nothing about that handoff should require a second tool.
Here's what changed and, more importantly, how it works now.
Frame absorbs Layout
Quey used to split shape editing across two tools. The Layout dock tool handled resize, reposition, and rotate with its own overlay (LayoutOverlay.tsx) and its own panel (LayoutPanel.tsx). The Style panel's Frame section handled a different subset of the same element. Both were reading and writing the same thing — the element's CSS transform — through two different UIs that didn't know about each other.
v2 deletes the Layout tool outright: LayoutPanel, LayoutOverlay, its toolbar button, and "layout" are gone from the QueyDockAction/OverlayDockAction unions — about 650 lines removed. Everything it did now lives in Frame, which is laid out as two rows: width, height, and angle on top; X, Y, and horizontal/vertical flip underneath. The angle field lost its slider track in favor of a plain input, because an angle is a value you type once and nudge, not one you drag across a range the way you'd drag opacity.
The result isn't just fewer buttons — it's one panel that can't disagree with itself about where an element is.
Scrub fields for padding and gap
Every numeric field in the Style panel — including padding and gap — used to share one slider-track control, the kind that implies a fixed min/max range. That's the right model for opacity or radius. It's the wrong model for padding, which has no natural ceiling.
v2 introduces QueyScrubField, a dedicated control for Pad X/Y and Gap X/Y: click anywhere on the field and drag to scrub the value up or down, or click directly on the number to type an exact one. It's the same interaction split Quey already uses elsewhere in the panel (the “Dialkit-style” drag-row pattern), now applied to the two fields where an open-ended range actually matters.
Radius, collapsed by default, per-corner on demand
Radius gets its own collapsible section now, with a toggle between a single value driving all four corners and four independent inputs — top-left, top-right, bottom-right, bottom-left. Most elements only need the single value, so that stays the fast path and the default. Per-corner is one click away for the cards and modals that don't fit the common case, instead of permanently taking up four fields' worth of space for something most elements never use.
Flex and grid, and gap you can drag on the page
The Flex section now opens with a mode picker — Flex row, Flex column, Grid — sitting directly above the alignment matrix and distribution buttons, with Gap X/Y aligned into the same two-column layout as the rest of the panel. Same principle as Frame: group the controls that describe one decision next to each other, in the order you'd actually make that decision, instead of scattering them by control type.
The more substantial change is underneath it. The selection overlay (packages/core/src/runtime/selection-overlay.ts) picked up live gap-handle dragging — you can now grab the space between flex or grid children directly on the page and see the gap change in real time before committing, rather than typing a number and checking the result. This is imperative-DOM overlay code, not React, so the preview updates on the same self-terminating requestAnimationFrame loop that already drives every other overlay visual — no extra render cost for the drag.
Reorder finally understands wrapped rows
This is the fix most people won't notice consciously, but will feel: dragging to reorder a wrapped flex row or a multi-row grid used to break in a specific way. The old reorder-controller.ts picked a drop target by comparing the pointer's position on the main axis alone against every child in the container, with no notion of which row the pointer was actually in. Drag over row 2 of a grid, and the controller would resolve the drop target against row 1's children, because row 1 came first in the DOM order it was scanning.
v2 adds groupIntoFlowLines(), which buckets a container's children into rows (or columns, for vertical flow) by comparing their cross-axis bounds — two children are the same line when their top/bottom bands overlap by more than a hairline, which holds up even when items in a row have different heights. pickFlowLine() then picks whichever line the pointer is actually inside, falling back to the nearest one by distance if the pointer is between rows. Drop-target resolution and the drop-indicator caret both now operate within that one line, instead of spanning — and cutting through — every row in the container at once.
Agent modes: Ask, Plan, Build
The Agent panel now has an explicit mode picker with three options, each doing something different with the same capture:
- Ask — read-only, no edits. For “what does this component do” without risking a change.
- Plan — the agent proposes an approach before touching anything.
- Build — the agent makes the edit directly.
Shift+Tab cycles Ask → Plan → Build, the same binding Claude Code uses, so the muscle memory transfers if you already work that way. The selection persists per session too — provider, model, approval mode, and agent mode are all saved through persistAgentSettings(), so switching elements mid-session doesn't reset you back to defaults.
Auto-run flips from opt-in to opt-out
A one-line change with real behavioral consequences: QUEY_AUTO_RUN used to require =1 to enable the MCP runner. Now it's on by default — you have to explicitly set QUEY_AUTO_RUN=0 to turn it off. The safety net that makes this okay is QUEY_CODEX_CWD, which stays a required, explicit setting naming exactly which repo the runner is allowed to touch. Flipping the default doesn't widen what a runner can reach; it just removes a flag most people were setting anyway.
Cancellation that actually cancels
Stopping an agent run used to mean the UI stopped showing it — the run itself kept going server-side, so its “processing” status in history quietly lied about what was actually happening. cancelAgentSession() now distinguishes two cases explicitly:
- Eviction (the concurrent-session cap knocks an old session out to make room for a new one) only untracks it locally. The run keeps going and is still findable in history later.
- The Stop button sets
abortRun: true, which also callspostCaptureCancel()against the MCP bridge — the server-side provider call actually gets aborted, not just hidden from view.
There was a second bug hiding behind the first: a poll request already in flight when you hit Stop could land after cancellation and clobber the state cancellation had just set, resurrecting a session you'd just killed. The fix checks trackedCaptureIds.has(captureId) when the poll result comes back and discards it silently if the capture is no longer tracked — a stale response now has nowhere to write.
A toolbar you can actually put away
Cmd/Ctrl + . now hides the toolbar and every annotation pin, and brings them back. The implementation (setQueyToolbarHidden() in @quey/core/runtime) is a short function doing something slightly unintuitive on purpose: it sets opacity: 0 and pointer-events: none rather than toggling display or visibility. visibility: hidden on a shadow host doesn't reliably hide the shadow tree's content, so it was ruled out. Annotation pins are handled in the same call because they're portaled straight to document.body, outside the shadow root, and would otherwise be left visible while the toolbar itself vanished.
It's deliberately a separate code path from the transient hide/show that already exists around screenshot capture — that one isn't ref-counted, so reusing it here would let a capture finishing while the toolbar is manually hidden incorrectly re-show it. Two different “hidden” states, kept from stepping on each other.
Try it
Update from chrome://extensions, or install fresh from quey.dev. This ships as 0.4.0 — the Frame/Flex redesign is the change you'll see first, but the reorder fix, real cancellation, and the visibility toggle are the ones that'll save you the most friction once you stop noticing them as “new.”
chrome://extensions or install fresh from quey.dev.