diff --git a/packages/dashboard/app/components/RightDock.css b/packages/dashboard/app/components/RightDock.css index 38309ebd0c..c84145f4c3 100644 --- a/packages/dashboard/app/components/RightDock.css +++ b/packages/dashboard/app/components/RightDock.css @@ -15,7 +15,11 @@ The right dock OVERLAYS the page content (floats over the right edge) instead of display: flex; flex-direction: column; min-width: min(100%, var(--right-dock-min-width, calc(var(--space-2xl) * 8))); - max-width: min(100%, var(--right-dock-max-width, calc(var(--space-2xl) * 22))); + /* + FNXC:RightDock 2026-06-23-00:50: + Raised the right-dock max-width default from calc(var(--space-2xl) * 22) (~704px) to calc(var(--space-2xl) * 40) (~1280px) so the dock can be dragged MUCH wider (matching the raised RIGHT_DOCK_MAX_WIDTH JS clamp) and the Files view has room for its tree|viewer two-pane split. The min(100%, ...) wrapper is kept so the dock can never exceed the viewport regardless of the larger cap. + */ + max-width: min(100%, var(--right-dock-max-width, calc(var(--space-2xl) * 40))); min-height: 0; background: var(--surface); border-left: thin solid var(--border); diff --git a/packages/dashboard/app/components/RightDock.tsx b/packages/dashboard/app/components/RightDock.tsx index 76788a53dc..5a076639b6 100644 --- a/packages/dashboard/app/components/RightDock.tsx +++ b/packages/dashboard/app/components/RightDock.tsx @@ -12,7 +12,11 @@ import "./RightDock.css"; export const RIGHT_DOCK_DEFAULT_WIDTH = 360; export const RIGHT_DOCK_MIN_WIDTH = 280; -export const RIGHT_DOCK_MAX_WIDTH = 720; +/* +FNXC:RightDock 2026-06-23-00:50: +The right-dock resize cap was raised 720 -> 1280 so the user can drag the dock MUCH wider (e.g. to run the Files view as a true two-pane tree|viewer split). The clamp and the persisted-width read both funnel through clampRightDockWidth/RIGHT_DOCK_MAX_WIDTH, so a single constant governs the drag clamp, the keyboard-step clamp, the stored-width read, and the resize-handle aria-valuemax. The CSS still wraps the rendered width in min(100%, ...), so the dock can never exceed the viewport even at the larger cap. +*/ +export const RIGHT_DOCK_MAX_WIDTH = 1280; export const RIGHT_DOCK_WIDTH_STORAGE_KEY = "fusion:right-dock-width"; export const RIGHT_DOCK_VIEW_STORAGE_KEY = "fusion:right-dock-view"; export const RIGHT_DOCK_OPEN_STORAGE_KEY = "fusion:right-dock-open"; @@ -261,7 +265,11 @@ export function RightDock({
{selectedEntry.label}
- {selectedEntry.render?.(renderProps)} + {/* + FNXC:RightDockFiles 2026-06-23-00:50: + Thread the live dock width down to registry render functions as `dockWidth` (alongside surface="dock") so a view can deterministically choose its wide layout from the actual dock size. The Files entry uses this to force two-pane when the dock is wide enough, sidestepping the @container query that never reliably fired in the narrow-vs-wide dock body. + */} + {selectedEntry.render?.({ ...renderProps, surface: "dock", dockWidth: width })}
) : null} diff --git a/packages/dashboard/app/components/__tests__/RightDock.test.tsx b/packages/dashboard/app/components/__tests__/RightDock.test.tsx index 2c5bbca504..2d7bfb98a3 100644 --- a/packages/dashboard/app/components/__tests__/RightDock.test.tsx +++ b/packages/dashboard/app/components/__tests__/RightDock.test.tsx @@ -74,6 +74,22 @@ describe("RightDock", () => { expect(screen.getByTestId("right-dock-tab-git-manager")).toHaveAttribute("aria-selected", "true"); }); + /* + FNXC:RightDockFiles 2026-06-23-00:50: + Deterministic dock two-pane decision: the dock threads its measured width to the Files registry render as `dockWidth`, and the Files entry forces DockFilesView layout="two-pane" once that width crosses 640px (no @container gate). A narrow dock (default 360px) stays layout="auto" (stacked single-panel). Assert both via the data-layout attribute the view exposes. + */ + it("forces the Files two-pane layout when the dock is dragged wide, and stays stacked when narrow", () => { + // Narrow default width (360px) -> stacked single-panel. + const { unmount } = render(); + expect(screen.getByTestId("right-dock-files-view")).toHaveAttribute("data-layout", "auto"); + unmount(); + + // Wide persisted width (>= 640px) -> deterministic LEFT|RIGHT two-pane. + window.localStorage.setItem(RIGHT_DOCK_WIDTH_STORAGE_KEY, "900"); + render(); + expect(screen.getByTestId("right-dock-files-view")).toHaveAttribute("data-layout", "two-pane"); + }); + it("falls back to Files when storage points at a removed right-dock view", () => { window.localStorage.setItem(RIGHT_DOCK_VIEW_STORAGE_KEY, "documents"); render(); @@ -160,17 +176,21 @@ describe("RightDock", () => { expect(screen.getByTestId("right-dock-files-view")).toBeInTheDocument(); }); + /* + FNXC:RightDock 2026-06-23-00:50: + The resize clamp + persisted-width read both funnel through RIGHT_DOCK_MAX_WIDTH, raised to 1280 so the dock drags MUCH wider. Drag far past the cap (startWidth 360 + 2000 px of leftward travel) and assert it clamps to the new 1280 max, then a keyboard step down lands one shift-step (48px) below the cap. This proves the new cap governs both the pointer drag and the keyboard path. + */ it("clamps then persists resize width while open", () => { render(); const handle = screen.getByTestId("right-dock-resize-handle"); - fireEvent.pointerDown(handle, { pointerId: 1, clientX: 900 }); + fireEvent.pointerDown(handle, { pointerId: 1, clientX: 2000 }); fireEvent.pointerMove(document, { pointerId: 1, clientX: 0 }); fireEvent.pointerUp(document, { pointerId: 1, clientX: 0 }); - expect(window.localStorage.getItem(RIGHT_DOCK_WIDTH_STORAGE_KEY)).toBe("720"); + expect(window.localStorage.getItem(RIGHT_DOCK_WIDTH_STORAGE_KEY)).toBe("1280"); fireEvent.keyDown(handle, { key: "ArrowRight", shiftKey: true }); - expect(window.localStorage.getItem(RIGHT_DOCK_WIDTH_STORAGE_KEY)).toBe("672"); + expect(window.localStorage.getItem(RIGHT_DOCK_WIDTH_STORAGE_KEY)).toBe("1232"); }); it("restores persisted width on mount", () => { diff --git a/packages/dashboard/app/components/overflowViewRegistry.tsx b/packages/dashboard/app/components/overflowViewRegistry.tsx index fead4b2a0c..3e0ed7f406 100644 --- a/packages/dashboard/app/components/overflowViewRegistry.tsx +++ b/packages/dashboard/app/components/overflowViewRegistry.tsx @@ -58,6 +58,11 @@ export interface OverflowViewRenderProps { The compact right-dock body leaves this undefined ("dock"); the RightDockExpandModal sets `surface="expand"` so DockFilesView forces its LEFT|RIGHT two-pane layout regardless of measured container width. */ surface?: "dock" | "expand"; + /* + FNXC:RightDockFiles 2026-06-23-00:50: + Measured outer width (px) of the compact right dock body host, threaded from RightDock so a registry render function can deterministically pick a wide layout from the actual dock size. Only set on the "dock" surface; the expand pop-out leaves it undefined (it already forces its wide layout via surface="expand"). + */ + dockWidth?: number; addToast: (message: string, type?: ToastType) => void; settingsLoaded?: boolean; readinessVersion?: number; @@ -100,6 +105,12 @@ export interface OverflowViewVisibilityOptions { pluginDashboardViews?: PluginDashboardViewEntry[]; } +/* +FNXC:RightDockFiles 2026-06-23-00:50: +When the dock body is at least this wide there is clearly room for the Files tree|viewer two-pane split, so the dock forces DockFilesView layout="two-pane" deterministically instead of relying on the unreliable @container dock-files query (its root content-box often measured under the breakpoint and kept the view stacked). Matched to the CSS @container dock-files (min-width: 640px) breakpoint; compared against the threaded outer dock width (the dock chrome padding is small relative to 640px of content, so 640 outer width safely implies enough body width for two panes). +*/ +const RIGHT_DOCK_FILES_TWO_PANE_MIN_WIDTH = 640; + function wrapOverflowView(node: ReactNode): ReactNode { return ( @@ -129,12 +140,20 @@ export const STATIC_OVERFLOW_VIEW_ENTRIES: readonly OverflowViewEntry[] = [ /* FNXC:RightDockFiles 2026-06-22-15:00: Map the host surface to a deterministic DockFilesView layout. The expand pop-out gets `layout="two-pane"` so the tree+viewer render LEFT|RIGHT without depending on the @container query matching inside the modal body. The compact dock keeps `layout="auto"` (the container-query single-panel stack). + + FNXC:RightDockFiles 2026-06-23-00:50: + Extend the deterministic approach to the DOCK itself: when the dock body is dragged wide (threaded `dockWidth` >= 640px) force the same LEFT|RIGHT two-pane split deterministically, NOT via the unreliable @container dock-files query (which kept the wide dock stacked because the root content-box measured under the breakpoint). Below the threshold the narrow dock keeps the single-panel stacked nav. The expand pop-out is always two-pane. */ render: (props) => wrapOverflowView( = RIGHT_DOCK_FILES_TWO_PANE_MIN_WIDTH) + ? "two-pane" + : "auto" + } />, ), },