Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
d4ea383
feat(app-mode): layout grid primitive + system cells (Phase 1)
eliheuer Apr 16, 2026
96330d5
feat(bento): Phase 2a — per-input cells with uniform width
eliheuer Apr 14, 2026
0d31e3b
feat(bento): Phase 2b — per-output cells + design tokens
eliheuer Apr 14, 2026
43302f8
feat(app-mode): semi-customizable floating panels MVP (Phase 4)
eliheuer Apr 16, 2026
7da9659
style(app-mode): Phase 4 polish — typography, spacing, panel chrome
eliheuer Apr 16, 2026
451752b
refactor(app-mode): rename bento/ -> layout/
eliheuer Apr 16, 2026
1d16073
feat(app-mode): welcome screen — wordmark, copy, Run pill
eliheuer Apr 16, 2026
dde524f
feat(app-mode): unified chrome — shared sidebar + panel-matched cells
eliheuer Apr 16, 2026
8f8aa62
style(app-mode): flush left-dock + surface Run errors as toast
eliheuer Apr 16, 2026
a4addaa
i18n(app-mode): route output-thumb alt + block-drag aria-label throug…
eliheuer Apr 16, 2026
e28af8c
feat(app-builder): WYSIWYG floating panel shared with App Mode
eliheuer Apr 17, 2026
d9ae0dd
feat(app-mode): hoist Run cluster out of panel + mirror welcome copy
eliheuer Apr 17, 2026
c9b8e53
Merge branch 'main' into app-mode-semi-customizable-layout
DrJKL Apr 21, 2026
299292a
refactor(app-mode): address PR review feedback
eliheuer Apr 22, 2026
d436f54
fix(app-mode): second-round CodeRabbit feedback
eliheuer Apr 22, 2026
385b611
fix(app-mode): third-round CodeRabbit feedback
eliheuer Apr 22, 2026
9657403
Merge branch 'main' into app-mode-semi-customizable-layout
eliheuer Apr 22, 2026
3361a66
refactor(app-mode): unify App Mode and Builder chrome + panel
eliheuer Apr 23, 2026
3b70a54
refactor(app-mode): one grid system — fixed-gutter flex zones
eliheuer Apr 23, 2026
551a702
polish(app-builder): grid-aligned chrome bars + unified blue / orange…
eliheuer Apr 23, 2026
76dc717
polish(app-builder): highlight selectable widgets in builder:inputs
eliheuer Apr 23, 2026
550d855
polish(app-builder): output-selection parity + footer cleanup
eliheuer Apr 23, 2026
9adcea8
polish(app-mode): go/stop semantic cluster + run progress bar
eliheuer Apr 23, 2026
916ad31
refactor(app-mode): route go/stop colors through local CSS vars
eliheuer Apr 23, 2026
d621c7d
chore(app-mode): pre-squash CR-pattern cleanup
eliheuer Apr 23, 2026
69e1bff
Merge remote-tracking branch 'upstream/main' into app-mode-semi-custo…
eliheuer Apr 23, 2026
82f1980
chore(app-mode): pre-squash cleanup from .agents/checks audit
eliheuer Apr 23, 2026
fd022de
refactor(app-mode): unify App Builder and App Mode into one WYSIWYG s…
eliheuer Apr 23, 2026
e8c7076
chore: drop unused type exports flagged by knip
eliheuer Apr 23, 2026
38b197c
polish(app-mode): pre-draft-flip panel UX pass
eliheuer Apr 23, 2026
adc1db4
chore(app-mode): pre-review code-quality cleanup pass
eliheuer Apr 24, 2026
fbddb28
Merge remote-tracking branch 'upstream/main' into app-mode-semi-custo…
eliheuer Apr 24, 2026
d1f29f0
polish(app-mode): workspace pan/zoom + nav cluster + welcome touch-up
eliheuer Apr 24, 2026
32ab581
polish(app-mode): panel-block reorder — architecture + visuals
eliheuer Apr 24, 2026
583ba29
polish(app-mode): phase-3 drag demo + accent sweep
eliheuer Apr 26, 2026
0628acf
feat(app-mode): output preview as a movable window
eliheuer Apr 26, 2026
7c0d078
feat(app-mode): migrate run chrome into OutputWindow
eliheuer Apr 26, 2026
ccf166d
chore(app-mode): drop dead Interrupt/Progress cells
eliheuer Apr 26, 2026
4083442
feat(app-mode): multi-window output workspace (scaffolding)
eliheuer Apr 26, 2026
f067835
refactor(app-mode): drop the thumbnail history strip
eliheuer Apr 27, 2026
fc8e2f3
feat(app-mode): multi-window polish — asset resolution + close + clea…
eliheuer Apr 27, 2026
09d6ff6
polish(app-mode): UI pass — panel/window chrome, widget outlines, aut…
eliheuer Apr 27, 2026
8f3b9be
polish(app-mode): output overlay + widget chrome refinements
eliheuer Apr 27, 2026
d2e637b
polish(app-mode): floating-panel layout fixes + pre-review cleanup
eliheuer Apr 27, 2026
98f57a9
polish(app-mode): pan reliability + drag perf + dot-grid polish
eliheuer Apr 27, 2026
8c3fb37
polish(app-mode): address outstanding review nits
eliheuer Apr 27, 2026
a4128c8
docs(app-mode): defer agent design-system discoverability work
eliheuer Apr 27, 2026
ea0601a
chore(app-mode): comment trim pass for review (-343 lines)
eliheuer Apr 27, 2026
201fc1e
polish(app-mode): progress bar tracks sampler + window snap during drag
eliheuer Apr 27, 2026
bd4f9ef
chore(app-mode): trim small net-new files (-82 lines)
eliheuer Apr 27, 2026
17ecf38
refactor(app-mode): extract shared PanelHeader component
eliheuer Apr 27, 2026
895c0c1
refactor(app-mode): extract usePointerDrag base hook
eliheuer Apr 28, 2026
56bbd35
chore(app-mode): trim PR-added comments in modified files (-15 lines)
eliheuer Apr 28, 2026
d8355f4
chore(app-mode): drop remaining `!` Tailwind in chrome cells
eliheuer Apr 28, 2026
543f4b3
chore(app-mode): address CR review nits (batch 1/3)
eliheuer Apr 28, 2026
75b77b0
chore(app-mode): address CR review minors (batch 2/3)
eliheuer Apr 28, 2026
d676184
fix(app-mode): address CR review majors (batch 3/3)
eliheuer Apr 28, 2026
ed93de5
docs(app-mode): drop stale check count in discoverability note
eliheuer Apr 28, 2026
36c23a6
test(app-mode): fix LinearWelcome tests broken by run-pill addition
eliheuer Apr 28, 2026
ff60b57
polish(builder): step badges + Preview-step layout
eliheuer Apr 28, 2026
1289e61
refactor(app-mode): drop temp accent/active tokens for design-system
eliheuer Apr 28, 2026
d4dc570
refactor(builder): unify selection chrome, drop zoom-dependent sizing
eliheuer Apr 28, 2026
f6a29be
refactor(design-system): fold layout tokens into style.css
eliheuer Apr 28, 2026
c497c8c
refactor(design-system): consolidate App Mode tokens, drop README
eliheuer Apr 28, 2026
90f140b
chore(app-mode): trim noisy comments across new files
eliheuer Apr 28, 2026
563742f
feat(app-mode): resize OutputWindow, smooth fly-to, grid-edge spawn
eliheuer Apr 29, 2026
1d16598
feat(app-mode): align panel widgets + metadata-driven subtitle
eliheuer Apr 29, 2026
61ff478
refactor(app-mode): extract FLIP composable, persist panel layout
eliheuer Apr 29, 2026
d434a0d
chore(app-mode): comment sweep + LinearArrange refactor
eliheuer Apr 29, 2026
91bd183
refactor(app-mode): center welcome card; drop side-flip machinery
eliheuer Apr 29, 2026
b007a55
fix(app-mode): drop double focus-ring on widget pills
eliheuer Apr 29, 2026
676a443
Merge branch 'main' into app-mode-semi-customizable-layout
DrJKL Apr 29, 2026
13b3e09
feat(app-mode): no-zoom dashboard mode with adaptive bento layout
eliheuer Apr 30, 2026
6f91765
feat(app-mode): add slicing-tree bento candidate
eliheuer Apr 30, 2026
2a21d12
fix(app-mode): snap dashboard tiles to chrome cell grid
eliheuer Apr 30, 2026
c274133
feat(app-mode): fill canvas via slicing-tree + stretch + cover-fit
eliheuer Apr 30, 2026
0d18fe0
feat(app-mode): asymmetric bento stretch — newest tile uncropped
eliheuer May 1, 2026
8f00acb
feat(app-mode): cap dashboard at 9 tiles, full slicing-tree always
eliheuer May 1, 2026
5edcdf8
fix(app-mode): drop the 8px body padding around output images
eliheuer May 1, 2026
274d32c
fix(app-mode): N=1 stays uncropped, anchored opposite the panel
eliheuer May 1, 2026
152653a
refactor(app-mode): rebuild dashboard layout for visual fidelity
eliheuer May 1, 2026
1f0cfea
feat(app-mode): bento templates fill avail rect, hide header on small…
eliheuer May 1, 2026
de52814
refactor(app-mode): clean up output header chrome + fix panel-drag fr…
eliheuer May 1, 2026
4754980
style(app-mode): match input-cell label/subtitle type, breathe out te…
eliheuer May 1, 2026
681a959
feat(app-mode): theme toggle, zoom-mode parity, gradient skeletons
eliheuer May 2, 2026
989754e
Merge remote-tracking branch 'upstream/main' into app-mode-semi-custo…
eliheuer May 6, 2026
4fb60b8
refactor(app-mode): use palette tokens instead of local --app-mode-* …
eliheuer May 6, 2026
2746856
refactor(app-mode): tighten design-system conformance
eliheuer May 6, 2026
9c171c1
refactor(app-mode): conformance pass per AGENTS.md
eliheuer May 6, 2026
055e46e
refactor(app-mode): minor cleanups from deep audit
eliheuer May 7, 2026
2640d09
lint(autofix): tailwind class order in files using foreground tokens
eliheuer May 7, 2026
c6cf567
test(app-mode): include layout.panelRows in linearData expectations
eliheuer May 7, 2026
fd75632
test(app-mode): unit coverage for new store math
eliheuer May 7, 2026
1acdb42
refactor(app-mode): scope foreground tokens to App Mode via arbitrary…
eliheuer May 7, 2026
a4d4145
Revert "lint(autofix): tailwind class order in files using foreground…
eliheuer May 7, 2026
1a7300b
lint(autofix): canonical class names in pre-existing files
eliheuer May 7, 2026
7e5a95a
fix(app-mode): evict by createdSeq, not mutable zIndex
eliheuer May 7, 2026
6b9dfe8
fix(test): pass ComfyPage to reSaveAs instead of dereferencing Locator
eliheuer May 7, 2026
f357c57
fix(app-mode): spread graphNodes shallowRef on every assignment
eliheuer May 7, 2026
c668618
fix(app-mode): disable manual drag/resize on output windows in noZoom…
eliheuer May 7, 2026
4c140cb
fix(app-mode): don't silently rerun current graph when output has no …
eliheuer May 7, 2026
1a1025b
fix(app-mode): scope wheel-zoom to the workspace, leave panel scroll …
eliheuer May 7, 2026
c48d731
fix(app-mode): keep full-viewport backdrop hit-testable for pan/zoom
eliheuer May 7, 2026
5a31405
fix(app-mode): address 5 minor CodeRabbit findings
eliheuer May 7, 2026
2f7e90a
fix(app-mode): address 5 load-bearing CodeRabbit findings (round 2)
eliheuer May 8, 2026
7a5e375
docs(adr): add 0009 App Mode + Builder unification; format pass
eliheuer May 9, 2026
c997195
fix(app-mode): address round-3 CodeRabbit findings (5 follow-ons + 4 …
eliheuer May 9, 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
Prev Previous commit
Next Next commit
polish(app-mode): pan reliability + drag perf + dot-grid polish
- Pan: hoist wheel + pointerdown handlers from the transformed
  bgRef to the full-viewport `.layout-view` so zoom-out can't
  shrink the hit area and silently break pan/zoom; panel-header
  drag stops propagation.
- Drag perf: drop FloatingPanel backdrop-blur during drag, drive
  OutputWindow position via translate3d (compositor) instead of
  top/left (layout + paint), trim transition list.
- Dot grid: share workspace pivot (kills cursor-anchored parallax),
  two-layer LOD with inverse fade (no edge bleed at zoom-in), dot
  radius scales with zoom floored at 0.6x with a pinned 0.5px AA
  ring so dots stay crisp.
  • Loading branch information
eliheuer committed Apr 27, 2026
commit 98f57a91244492436a0ecbb608fb4b033658b0b8
160 changes: 111 additions & 49 deletions src/components/appMode/layout/LayoutView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,17 @@ watch(
)

// --- Workspace pan/zoom handlers -----------------------------------
// Bound to `.layout-view` (always viewport-sized) rather than the
// inner transformed bgRef, so that zoom-out doesn't shrink the hit
// area and silently break pan/zoom. The math is rect-invariant under
// this swap because the transform is around bgRef's center, which
// coincides with .layout-view's center.
const bgRef = useTemplateRef<HTMLElement>('bgRef')

function handleWheel(e: WheelEvent) {
const el = bgRef.value
if (!el) return
e.preventDefault()
appModeStore.zoomAt(
e.clientX,
e.clientY,
e.deltaY,
el.getBoundingClientRect()
)
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
appModeStore.zoomAt(e.clientX, e.clientY, e.deltaY, rect)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Defer pointer capture until the pointer actually moves past a
Expand Down Expand Up @@ -101,23 +100,38 @@ const workspaceTransform = computed(
`scale(${viewportScale.value})`
)

// Dot-grid LOD — the visible grid pitch scales with content while we
// zoom in (fewer dots per viewport = feels like zooming in), but as
// we zoom out we double the pitch whenever it would fall below a
// density threshold so the viewport doesn't fill with visual noise.
// Doubling keeps alignment with the previous level (every other dot
// survives), so a "world point" under a dot stays under a dot after
// the snap. Matches the CSS `--spacing-layout-dot` (24px) at scale=1.
// Dot-grid LOD via two stacked layers (CSS handles the rendering in
// `.layout-view`'s `background-image`). Both layers tile linearly with
// the workspace scale, so they stay locked to the transformed contents
// at all times — the parallax that the old discrete-doubling LOD
// produced is gone. Density management is opacity-based instead: the
// fine 1×-pitch layer fades out as you zoom out, leaving the coarse
// 2×-pitch layer (whose dot positions are a subset of the fine layer's)
// to carry the visual at small scales — so the user perceives
// progressively fewer dots without any of them ever shifting.
const DOT_SIZE_PX = 24
const MIN_GRID_SPACING_PX = 16

const gridSpacing = computed(() => {
let s = DOT_SIZE_PX * viewportScale.value
if (!(s > 0)) return DOT_SIZE_PX
while (s < MIN_GRID_SPACING_PX) s *= 2
return s
const s = DOT_SIZE_PX * viewportScale.value
return s > 0 ? s : DOT_SIZE_PX
})

const gridFineAlpha = computed(() => {
const s = viewportScale.value
if (s >= 1) return 1
if (s <= 0.5) return 0
return (s - 0.5) * 2
})

// Coarse layer is the inverse of fine — fully opaque when fine has
// faded out, fully transparent when fine carries the grid. Without
// this, both layers stack at full opacity above scale=1; the fine
// dots cover the coarse ones, but their anti-aliased edges let the
// coarse dot underneath bleed through, making every-other dot read
// slightly darker than the fine-only ones (two visible values where
// only one was wanted).
const gridCoarseAlpha = computed(() => 1 - gridFineAlpha.value)

// Per-input resolution + block-layout state is shared with the builder
// via useAppPanelLayout — both views read the same `panelRows` from the
// store so WYSIWYG holds across mode switches.
Expand Down Expand Up @@ -148,26 +162,34 @@ const panelSide = computed(() => resolvePanelSide(panelPreset.value))
'--viewport-scale': viewportScale,
'--viewport-offset-x': `${viewportOffsetX}px`,
'--viewport-offset-y': `${viewportOffsetY}px`,
'--grid-spacing': `${gridSpacing}px`
'--grid-spacing': `${gridSpacing}px`,
'--grid-fine-alpha': gridFineAlpha,
'--grid-coarse-alpha': gridCoarseAlpha
}"
@wheel="handleWheel"
@pointerdown="handlePointerDown"
@dragstart.prevent
>
<!-- Workspace layer: the viewport transform lives here, so the
LinearPreview contents (welcome, image, arrange) zoom + pan
together. Wheel + pointerdown handlers are attached to this
wrapper; the chrome + panel are siblings (not descendants),
so their events never trigger workspace navigation.
The dot grid is painted on `.layout-view` (always viewport-
sized) with dynamic background-size + background-position
driven by the viewport vars above, so the grid pattern
visually matches the transformed content without the
element itself needing to be scaled. -->
<!-- Workspace layer: the viewport transform lives on this inner
div so the LinearPreview contents (welcome, image, arrange)
zoom + pan together. Wheel + pointerdown handlers are bound
to the OUTER `.layout-view` (always viewport-sized) so pan
and zoom work even when the user has zoomed out and the
transformed inner div has shrunk to a fraction of the
viewport — otherwise clicks in the empty area around the
shrunken workspace miss the listener entirely and pan stops
responding until a recenter. Chrome + panel are siblings of
this inner div but children of `.layout-view`; OutputWindow
and panel headers stop pointerdown propagation so their own
drags don't double-fire as pans.
The dot grid is painted on `.layout-view` itself with a
dynamic background-size + background-position driven by the
viewport vars above, so the grid pattern visually matches
the transformed content without the element needing to scale. -->
<div
ref="bgRef"
class="layout-view__background"
:style="{ transform: workspaceTransform }"
@wheel="handleWheel"
@pointerdown="handlePointerDown"
@dragstart.prevent
>
<LinearPreview hide-chrome />
</div>
Expand Down Expand Up @@ -204,21 +226,61 @@ const panelSide = computed(() => resolvePanelSide(panelPreset.value))
position: absolute;
inset: 0;
background-color: var(--color-layout-canvas);
/* Dot grid — stays on the viewport-sized element (never scales
itself) but its background-size scales with `--viewport-scale`
so the pattern visually matches the transformed content, and
background-position shifts with `--viewport-offset-{x,y}` so it
pans with the content. That way the grid always covers the
viewport regardless of zoom level, and lines up with whatever
LinearPreview is rendering inside the transformed workspace. */
background-image: radial-gradient(
circle,
var(--color-layout-grid-dot) 1px,
transparent 1.5px
);
background-size: var(--grid-spacing, var(--spacing-layout-dot))
var(--grid-spacing, var(--spacing-layout-dot));
background-position: var(--viewport-offset-x, 0) var(--viewport-offset-y, 0);
/* Dot grid — two stacked layers tile across the viewport so the
grid always covers it regardless of zoom. The fine layer (1×
pitch) sits on top and fades with `--grid-fine-alpha` as you zoom
out; the coarse layer (2× pitch) is always at full opacity and
carries the visual at small scales. The coarse layer's positions
are a subset of the fine's, so fading the fine out leaves a
subset of the same dots — no jump. Both `background-position`
entries use `calc(50% + offset)` so the grid shares the same
pivot as the workspace `transform` (which is `transform-origin:
center` plus the same offset). With percent-based positioning,
CSS auto-compensates for the differing image sizes so each
layer's center dot lands at the same viewport point — making the
coarse layer's positions perfectly align with every-other fine
dot. */
/* Dot radii scale with `--viewport-scale` so the dot-to-tile ratio
stays constant. Floored at 0.6× so the dots stay visible at
extreme zoom-out instead of dissolving into sub-pixel noise. The
anti-aliasing ring (the gap between the solid stop and the
transparent stop) is pinned to a constant 0.5px regardless of
zoom — without that pin, zooming in scaled up the AA halo too,
and the dots read as soft/blurry instead of crisp. */
--dot-scale: max(0.6, var(--viewport-scale, 1));
--dot-radius: calc(1px * var(--dot-scale));
--dot-fade-radius: calc(var(--dot-radius) + 0.5px);
background-image:
radial-gradient(
circle,
color-mix(
in srgb,
var(--color-layout-grid-dot) calc(100% * var(--grid-fine-alpha, 1)),
transparent
)
var(--dot-radius),
transparent var(--dot-fade-radius)
),
radial-gradient(
circle,
color-mix(
in srgb,
var(--color-layout-grid-dot) calc(100% * var(--grid-coarse-alpha, 0)),
transparent
)
var(--dot-radius),
transparent var(--dot-fade-radius)
);
background-size:
var(--grid-spacing, var(--spacing-layout-dot))
var(--grid-spacing, var(--spacing-layout-dot)),
calc(var(--grid-spacing, var(--spacing-layout-dot)) * 2)
calc(var(--grid-spacing, var(--spacing-layout-dot)) * 2);
background-position:
calc(50% + var(--viewport-offset-x, 0))
calc(50% + var(--viewport-offset-y, 0)),
calc(50% + var(--viewport-offset-x, 0))
calc(50% + var(--viewport-offset-y, 0));
/* Clip the transformed workspace (+ any other absolute-positioned
children) to the viewport box so panning out doesn't paint above
the top workflow-tabs bar. */
Expand Down
11 changes: 9 additions & 2 deletions src/components/appMode/layout/OutputWindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,15 @@ const combinedMenuEntries = computed<MenuItem[]>(() => {
maximized
? { inset: '0px', zIndex: zIndex ?? 30 }
: {
left: `${wx}px`,
top: `${wy}px`,
// Position via translate3d (compositor-only) instead of
// top/left (layout + paint). Updating top/left every
// pointermove during a drag shares the main thread with
// the latent-preview decode, which is what made dragging
// an OutputWindow feel laggy mid-run. transform keeps the
// move on the GPU, freeing main-thread budget. The `0`
// forces a 3D layer so the browser allocates a composite
// layer up-front rather than promoting on first move.
transform: `translate3d(${wx}px, ${wy}px, 0)`,
width: `${width}px`,
zIndex: zIndex ?? 30,
// Drop height when collapsed (header-only) or when
Expand Down
30 changes: 16 additions & 14 deletions src/components/appMode/layout/panels/FloatingPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import { useAppModeStore } from '@/stores/appModeStore'

import PanelDragPreview from './PanelDragPreview.vue'
import { PANEL_PRESET_CLASSES } from './panelPresetClasses'
import { isDockPreset, isFloatBottom, panelSide } from './panelTypes';
import type { PanelPreset } from './panelTypes';
import { isDockPreset, isFloatBottom, panelSide } from './panelTypes'
import type { PanelPreset } from './panelTypes'
import { usePanelDrag } from './usePanelDrag'
import { usePanelResize } from './usePanelResize'

Expand Down Expand Up @@ -118,22 +118,24 @@ const sectionClass = computed(() =>
// presets fall back to the default --panel-dock-width token.
!isDocked.value && 'w-(--panel-dock-width,440px)',
'max-w-[calc(100vw-var(--spacing-layout-outer)*2)]',
'rounded-[10px] border border-white/8 bg-layout-cell backdrop-blur-sm',
'rounded-[10px] border border-white/8 bg-layout-cell',
// Drop backdrop-blur while the panel is being dragged. Backdrop-
// filter is the most expensive CSS effect we use, and when the
// layer behind the panel is repainting every frame (latent
// preview / progress bar during a run), the GPU has to recompose
// the blur each frame, which tanks drag framerate. Idle keeps the
// glassy look; dragging drops it for smoothness.
!isDragging.value && 'backdrop-blur-sm',
'shadow-[0_2px_4px_rgb(0_0_0/0.4),0_16px_48px_rgb(0_0_0/0.45)]',
'duration-layout ease-layout',
// Split the transition property list by drag state: while dragging,
// only opacity tweens (position is driven by pointer, not CSS) so
// the multi-property transition below would cause a trailing easing
// pop when the commit lands.
//
// Fade the live panel heavily while dragging so the PanelDragPreview
// (which may land at the same preset the panel is already docked
// at — e.g., mousing around the right half while docked-right)
// reads as the dominant blue outline rather than being masked by
// the live panel's own contents.
// While dragging only opacity tweens (position is driven by the
// pointer, not CSS) so the multi-property transition below would
// cause a trailing easing pop when the commit lands. Fade the live
// panel heavily so the PanelDragPreview reads as the dominant blue
// outline rather than being masked by the live panel's contents.
movable && isDragging.value
? 'opacity-[0.15] transition-opacity'
: 'transition-[top,bottom,left,right,max-height,height,opacity]',
: 'transition-[top,bottom,left,right,opacity]',
PANEL_PRESET_CLASSES[preset.value],
// Collapsed: release the off-corner anchor + any cap so the section
// shrinks to header-only. Top-anchored presets stay pinned at the
Expand Down
4 changes: 4 additions & 0 deletions src/components/appMode/layout/panels/usePanelDrag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ export function usePanelDrag(opts: UsePanelDragOptions) {
// isDragging + snapTarget stay unchanged until the movement
// threshold is crossed — so a plain click on the header is a no-op.
e.preventDefault()
// Stop bubbling so LayoutView's pan handler (now bound to the
// outer `.layout-view`) doesn't also start a workspace pan from
// this same press.
e.stopPropagation()
}

return {
Expand Down