feat(dashboard): right dock resizes wider (max 1280) + Files goes two-pane when the dock is wide

- RIGHT_DOCK_MAX_WIDTH 720 to 1280 (all clamps/persist/aria funnel through it); CSS --right-dock-max-width raised to calc(space-2xl * 40), keeping min(100%, ...).
- The dock now threads its live width into the overflow view render props; the Files entry deterministically forces the LEFT|RIGHT two-pane split when the dock width >= 640 (no longer relying on the flaky @container query that left it stacked top/bottom). Narrow dock keeps the stacked single-panel nav; the expand pop-out stays always two-pane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-22 10:11:56 -07:00
parent 03aeed291f
commit ab9db246bb
4 changed files with 58 additions and 7 deletions

View File

@@ -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);

View File

@@ -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({
<div className="right-dock__title" role="heading" aria-level={3}>{selectedEntry.label}</div>
</div>
<div className="right-dock__body" role="tabpanel" aria-label={selectedEntry.label} data-testid="right-dock-body">
{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 })}
</div>
</>
) : null}

View File

@@ -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(<RightDock open={true} renderProps={renderProps} />);
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(<RightDock open={true} renderProps={renderProps} />);
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(<RightDock open={true} renderProps={renderProps} />);
@@ -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(<RightDock open={true} renderProps={renderProps} />);
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", () => {

View File

@@ -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 (
<PageErrorBoundary>
@@ -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(
<DockFilesView
projectId={props.projectId}
openFile={props.openFile}
layout={props.surface === "expand" ? "two-pane" : "auto"}
layout={
props.surface === "expand"
|| (props.surface === "dock" && (props.dockWidth ?? 0) >= RIGHT_DOCK_FILES_TWO_PANE_MIN_WIDTH)
? "two-pane"
: "auto"
}
/>,
),
},