Files
fusion/packages/dashboard/app/components/FloatingWindow.css
gsxdsm c6adac6e7c fix(dashboard): stop mobile task-detail panel shifting left
The full-screen mobile task-detail sheet hides all resize handles, so
FN-8015's `margin-inline-end: var(--space-lg)` gutter on the shared
`.floating-window__body` (added to keep the scrollbar clear of desktop
resize hot zones) only added dead space on the right and shifted the
entire panel left. Zero it for `.floating-window--task-detail` inside
the mobile breakpoint so `.detail-body`'s own padding defines both
insets equally; desktop resize-handle clearance is untouched.

Refined the FN-8015 invariant test to enforce the desktop hot-zone gutter
media-aware (strips @media blocks) and added a regression guard for the
mobile zeroing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 08:35:44 -07:00

318 lines
12 KiB
CSS

/*
FNXC:FloatingWindow 2026-06-22-20:45:
FloatingWindow is a non-blocking floating window (generalized from RightDockExpandModal). The overlay is a full-viewport, transparent, NON-dimming, NON-blurring, click-through layer: `pointer-events: none` lets every click pass through to the app and to other windows behind it. Only the panel re-enables `pointer-events: auto`. Multiple overlays/panels coexist with no mutual blocking — z-stacking is driven by inline `z-index` from the component's per-window counter.
FNXC:FloatingWindow 2026-06-27-00:00:
Click-through overlays cannot implement backdrop clicks in CSS/DOM structure. Outside-click dismissal is therefore a component-level opt-in document listener for transient windows such as Quick Chat; persistent task and terminal pop-outs keep the default non-dismissable page-click behavior.
*/
.floating-window-overlay {
position: fixed;
inset: 0;
background: transparent;
backdrop-filter: none;
pointer-events: none;
}
/*
FNXC:FloatingWindow 2026-06-22-20:45:
Floating panel positioned by state-driven inline `left/top/width/height` and stacked by inline `z-index`. min/max keep the panel usable and on-screen. `resize: none` because resizing is handled by the corner/edge handles. `pointer-events: auto` re-enables interaction on the panel only.
*/
.floating-window {
--floating-window-shadow: var(--shadow-lg);
position: fixed;
display: flex;
flex-direction: column;
min-width: calc(var(--space-2xl) * 7.5);
min-height: calc(var(--space-2xl) * 5.83);
max-width: calc(100vw - (var(--space-lg) * 2));
max-height: calc(100dvh - (var(--space-lg) * 2));
overflow: hidden;
background: var(--surface);
border: thin solid var(--border);
border-radius: var(--radius-lg);
/*
FNXC:FloatingWindow 2026-06-23-23:25:
Floating modals need a gentle, theme-controlled drop shadow. Use a local token with the app's existing shadow fallback instead of the undefined --shadow-xl so themes can soften, strengthen, or remove modal elevation intentionally.
*/
box-shadow: var(--floating-window-shadow, var(--shadow-lg));
color: var(--text);
resize: none;
pointer-events: auto;
}
/*
FNXC:FloatingWindow 2026-06-22-20:45:
Header is the drag handle. `touch-action: none` (matching the resize handles) hands the whole gesture to the pointer handlers so touch dragging stays smooth and never scrolls the page behind it. A comfortable min-height makes a forgiving touch target. `user-select: none` protects the drag from selecting header text.
*/
.floating-window__header {
display: flex;
flex-shrink: 0;
align-items: center;
gap: var(--space-sm);
min-height: 44px;
padding: var(--space-sm) var(--space-md);
border-bottom: thin solid var(--border);
background: var(--surface-2, var(--surface));
cursor: grab;
user-select: none;
touch-action: none;
}
.floating-window__header:active {
cursor: grabbing;
}
/*
FNXC:FloatingWindow 2026-06-22-12:20:
Headerless task pop-outs still need a visible drag affordance. The embedded task-detail modal header becomes the grab handle, matching the one-header "Open task" modal while preserving FloatingWindow's drag and resize behavior.
*/
.floating-window--headerless .floating-window__body {
overflow: hidden;
}
.floating-window--headerless .task-detail-content--embedded {
border-radius: inherit;
overflow: hidden;
}
.floating-window--headerless .task-detail-content--embedded > .modal-header {
cursor: grab;
user-select: none;
touch-action: none;
}
.floating-window--headerless .task-detail-content--embedded > .modal-header:active {
cursor: grabbing;
}
.floating-window--chat.floating-window--headerless .floating-window__body {
overflow: hidden;
}
.floating-window--chat .chat-view {
border-radius: inherit;
overflow: hidden;
}
/*
FNXC:ChatModal 2026-06-22-14:49:
On mobile/narrow app viewports, opening Quick Chat should present the full Chat modal as a full-screen sheet instead of a small draggable desktop window. Scope this to the chat FloatingWindow and override the inline desktop geometry only at the mobile breakpoint; desktop pop-out behavior remains movable/resizable.
*/
@media (max-width: 768px) {
/*
FNXC:FloatingWindow 2026-07-12-17:35:
Mobile keeps the global `styles.css` pan-y lockdown so the dashboard cannot drift, but movable FloatingWindow headers must still resolve to an effective `touch-action: none`. Reassert the drag-handle contract at the mobile breakpoint, excluding full-screen sheet variants, so a single-finger header drag stays on the captured pointermove stream instead of being intersected back into page pan by the ancestor chain. Desktop drag/resize and mobile sheet variants are unchanged.
*/
.floating-window:not(.floating-window--chat):not(.floating-window--github-import-detail):not(.floating-window--task-detail):not(.floating-window--workflow-editor):not(.floating-window--automation):not(.floating-window--mission-interview):not(.floating-window--file-browser):not(.floating-window--pr-create):not(.artifacts-gallery-window) .floating-window__header {
touch-action: none;
}
.floating-window--chat {
inset: 0 !important;
width: 100vw !important;
height: 100dvh !important;
min-width: 0 !important;
min-height: 0 !important;
max-width: 100vw !important;
max-height: 100dvh !important;
border: none;
border-radius: 0;
box-shadow: none;
}
.floating-window--chat .floating-window__resize-handle {
display: none;
}
/*
FNXC:QuickAddAttachments 2026-07-16-00:00:
Pending task-attachment images open in a movable, resizable FloatingWindow on desktop. On narrow touch viewports the dedicated image-preview variant must instead fill the screen and remove desktop resize handles.
*/
.floating-window--image-preview {
inset: 0 !important;
width: 100vw !important;
height: 100dvh !important;
min-width: 0 !important;
min-height: 0 !important;
max-width: 100vw !important;
max-height: 100dvh !important;
border: none;
border-radius: 0;
box-shadow: none;
}
.floating-window--image-preview .floating-window__resize-handle {
display: none;
}
/*
FNXC:GitHubImport 2026-07-15-16:00:
Import details use FloatingWindow for desktop drag and resize; on mobile this scoped variant becomes a full-screen sheet
and hides desktop resize affordances.
*/
.floating-window--github-import-detail {
inset: 0 !important;
width: 100vw !important;
height: 100dvh !important;
min-width: 0 !important;
min-height: 0 !important;
max-width: 100vw !important;
max-height: 100dvh !important;
border: none;
border-radius: 0;
box-shadow: none;
}
.floating-window--github-import-detail .floating-window__resize-handle {
display: none;
}
.floating-window--chat .chat-view {
border-radius: 0;
}
/*
FNXC:MobileTaskPopups 2026-06-29-00:00:
Mobile board-card popup opens reuse the existing task-detail FloatingWindow, but the shell must behave like a full-screen mobile sheet. Scope the override to task-detail pop-outs, hide resize handles, and remove drag/resize cursors so no invisible desktop affordances remain on touch layouts.
*/
.floating-window--task-detail {
inset: 0 !important;
width: 100vw !important;
height: 100dvh !important;
min-width: 0 !important;
min-height: 0 !important;
max-width: 100vw !important;
max-height: 100dvh !important;
border: none;
border-radius: 0;
box-shadow: none;
}
.floating-window--task-detail .floating-window__resize-handle {
display: none;
}
/*
FNXC:MobileTaskPopups 2026-07-17-08:20:
FN-8015 reserves `margin-inline-end: var(--space-lg)` on the shared
`.floating-window__body` so a hosted scrollbar clears the desktop
east/north-east/south-east resize hot zones. The mobile task-detail sheet is
full-screen and hides every resize handle (above), so that gutter protects
nothing and instead shifts the whole panel left — leaving an uneven right
inset on every row. Zero it here so `.detail-body`'s own symmetric padding
defines both insets equally. Desktop resize-handle clearance is untouched
(this rule is mobile-only).
*/
.floating-window--task-detail .floating-window__body {
margin-inline-end: 0;
}
.floating-window--task-detail .task-detail-content--embedded,
.floating-window--task-detail .task-detail-content--embedded > .modal-header {
border-radius: 0;
cursor: default;
touch-action: auto;
}
}
/*
FNXC:FloatingWindow 2026-07-15-18:20:
Long titles (e.g. the GitHub import detail's "#2159 — <issue title>") must truncate with an ellipsis rather than hard-cut mid-word against the close button.
display MUST stay block, not flex: every caller passes a plain string, so under flex the text became an anonymous flex item, which text-overflow cannot act on — the ellipsis declared here silently never rendered. A block box makes the existing overflow/text-overflow/white-space trio work as written.
Reintroducing flex here (e.g. to lay out an element title) re-breaks truncation for all callers; give the title an inner element instead.
*/
.floating-window__title {
display: block;
flex: 1;
min-width: 0;
overflow: hidden;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.floating-window__close {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--space-xs);
border: none;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-muted);
cursor: pointer;
}
.floating-window__close:hover {
background: var(--status-todo-bg, var(--surface));
color: var(--text);
}
/*
FNXC:FloatingWindow 2026-06-22-20:45:
Body is a flex host that lets its single child stretch to the full panel width/height (min-width/min-height:0 so a wide child cannot collapse the flex line, and the child's own overflow can engage).
FNXC:FloatingWindow 2026-07-15-00:00:
FN-8015 / issue #2140 requires every shared FloatingWindow caller to keep its hosted vertical scrollbar clear of the right-edge and right-corner resize hot zones. Reserve the corner-width gutter on the body rather than specializing task detail: this applies equally when the body itself scrolls and when a headerless caller delegates scrolling to its full-size child. The panel border and its right handles remain available for desktop resizing, while the scrollbar is always inboard of the east, north-east, and south-east hit targets.
*/
.floating-window__body {
display: flex;
flex: 1;
min-width: 0;
min-height: 0;
margin-inline-end: var(--space-lg);
overflow: auto;
}
.floating-window__body > * {
flex: 1;
min-width: 0;
min-height: 0;
min-block-size: 0;
}
/*
FNXC:FloatingWindow 2026-06-22-20:45:
Edge + corner resize handles. touch-action:none keeps the drag from being hijacked by scroll/gestures so resizing stays smooth.
*/
.floating-window__resize-handle {
position: absolute;
z-index: 2;
touch-action: none;
}
.floating-window__resize-handle--n,
.floating-window__resize-handle--s {
left: var(--space-sm);
right: var(--space-sm);
height: var(--space-sm);
cursor: ns-resize;
}
.floating-window__resize-handle--n { top: 0; }
.floating-window__resize-handle--s { bottom: 0; }
.floating-window__resize-handle--e,
.floating-window__resize-handle--w {
top: var(--space-sm);
bottom: var(--space-sm);
width: var(--space-sm);
cursor: ew-resize;
}
.floating-window__resize-handle--e { right: 0; }
.floating-window__resize-handle--w { left: 0; }
.floating-window__resize-handle--ne,
.floating-window__resize-handle--nw,
.floating-window__resize-handle--se,
.floating-window__resize-handle--sw {
width: var(--space-lg);
height: var(--space-lg);
}
.floating-window__resize-handle--ne { top: 0; right: 0; cursor: nesw-resize; }
.floating-window__resize-handle--nw { top: 0; left: 0; cursor: nwse-resize; }
.floating-window__resize-handle--se { bottom: 0; right: 0; cursor: nwse-resize; }
.floating-window__resize-handle--sw { bottom: 0; left: 0; cursor: nesw-resize; }