From 5d9184d38030b08037e14c9c9d10f27f2c5cf3e4 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 27 Jun 2026 22:55:40 -0700 Subject: [PATCH] FN-7172: add right dock pin toggle Add a persisted right-dock pin control that switches between overlay and push layouts.\n\n- Add a toolbar pin button with accessible labels and persisted pinned state.\n- Switch pinned docks into the flex row so the main project content narrows instead of being overlaid.\n- Cover pin persistence, layout classes, header toggling, mobile behavior, and expanded modal independence in tests.\n- Document the overlay/push workflow and add a minor changeset for the published CLI package.\n\nFiles changed:\n .changeset/fn-7172-right-dock-pin-push-mode.md | 7 +\n docs/dashboard-guide.md | 8 +-\n packages/dashboard/app/components/RightDock.css | 9 ++\n packages/dashboard/app/components/RightDock.tsx | 45 +++++-\n .../app/components/__tests__/RightDock.test.tsx | 174 +++++++++++++++++++--\n .../app/components/useRightDockController.tsx | 21 ++-\n 6 files changed, 241 insertions(+), 23 deletions(-) Fusion-Task-Id: FN-7172 Fusion-Task-Lineage: 1608fead-74cd-4a1e-87be-1ce27f388f34 Co-authored-by: Fusion (runfusion.ai) --- .../fn-7172-right-dock-pin-push-mode.md | 7 + docs/dashboard-guide.md | 8 +- .../dashboard/app/components/RightDock.css | 9 + .../dashboard/app/components/RightDock.tsx | 45 ++++- .../components/__tests__/RightDock.test.tsx | 174 ++++++++++++++++-- .../app/components/useRightDockController.tsx | 21 ++- 6 files changed, 241 insertions(+), 23 deletions(-) create mode 100644 .changeset/fn-7172-right-dock-pin-push-mode.md diff --git a/.changeset/fn-7172-right-dock-pin-push-mode.md b/.changeset/fn-7172-right-dock-pin-push-mode.md new file mode 100644 index 0000000000..ab654e8452 --- /dev/null +++ b/.changeset/fn-7172-right-dock-pin-push-mode.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add a pin toggle to the right sidebar to push content aside instead of overlaying it. +category: feature +dev: New persisted localStorage flag `fusion:right-dock-pinned` (default false). When pinned, `.right-dock` switches from absolute overlay to in-flow (`right-dock--pinned`, position: relative) so the shell flex layout reflows `.project-content`; unpinned restores overlay. Toggle lives in the right-dock toolbar. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 8280bbb6f2..a1b54490ad 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -50,7 +50,7 @@ On mobile viewports (`<=768px`), the sidebar is not rendered even when the defau The **Right Dock Panel** experiment is enabled by default. To disable it, open **Settings → Experimental Features** and turn off **Right Dock Panel**. -When enabled on desktop or tablet project screens, the right dock is a persistent far-right tools sidebar in the project content row. Use the in-dock collapse control to switch between the full tool panel and the compact far-right rail; the selected tool, expanded/collapsed state, width, and expanded modal size persist across reloads. +When enabled on desktop or tablet project screens, the right dock is a persistent far-right tools sidebar in the project content row. By default it opens as an overlay so the main content does not reflow. Use the dock toolbar pin action to switch into push mode, where the dock becomes an in-flow pane that shrinks the main content beside it; unpinning returns to overlay mode. The selected tool, open/closed state, pinned push-mode state, width, and expanded modal size persist across reloads. The dock toolbar has built-in inline tool panels for **Activity**, **Activity Log**, **Git Manager**, **Files**, and project tool launchers such as **Import from GitHub** / **Import Tasks** workflow entry points and **Automation** actions when available. **Activity**, **Activity Log**, **Git Manager**, and **Files** render in embedded mode inside the dock instead of opening fixed popup overlays; **Files** opens by default and is the fallback when browser storage points at a removed dock key. Inline dock views have an expand button that opens the same view in a resizable modal for more room. The right-dock **Files** viewer and its expanded pop-out match the Files modal for browser-previewable file types: image, video/movie, audio, and PDF selections render as native browser previews, while editable text files keep the editor and save flow. Plugin overflow views may add additional right-dock tool tabs, except plugin destinations that explicitly belong in the left sidebar. @@ -64,8 +64,10 @@ Use the desktop/tablet right dock this way: Expected outcome: the dock width changes within its min/max bounds and is saved for future reloads. 4. Select the dock expand action. Expected outcome: the same inline tool opens in a resizable modal while the dock remains the source navigation surface. -5. Use the dock's own collapse control. - Expected outcome: the far-right surface switches between full panel and compact rail without creating duplicate left-sidebar destinations; mobile viewports never render the right dock. +5. Select the dock pin action. + Expected outcome: pinned mode pushes the main content narrower, unpinned mode overlays the page without reserving space, and the preference is saved for future reloads. +6. Use the Header right-sidebar toggle. + Expected outcome: the far-right surface opens or closes without creating duplicate left-sidebar destinations; mobile viewports never render or reserve space for the right dock. Content views such as Artifacts, Research, Insights, Skills, Memory, Evals, Goals, Dev Server, **Workflows**, **Import Tasks**, and **Automations** live in the left sidebar (or compact mobile navigation) rather than the right dock. On desktop/tablet, GitHub import lives under **Import Tasks**; mobile keeps compact GitHub import entries in the More surfaces. diff --git a/packages/dashboard/app/components/RightDock.css b/packages/dashboard/app/components/RightDock.css index 257041fb5b..dfa0cfa1c7 100644 --- a/packages/dashboard/app/components/RightDock.css +++ b/packages/dashboard/app/components/RightDock.css @@ -34,6 +34,15 @@ The right dock OVERLAYS the page content (floats over the right edge) instead of box-shadow: var(--shadow-lg); } +/* +FNXC:RightDockPin 2026-06-27-00:00: +Pinned mode is CSS-only: the project shell is already a flex row, so switching the dock from absolute overlay to relative in-flow reserves the dock width and shrinks `.project-content` without an App shell class. Keep position:relative (not static) so the absolute resize handle remains anchored to the dock, and remove the floating shadow so the pinned dock reads as a sibling pane with the existing tokenized divider. The mobile `.right-dock { display:none }` media guard still wins because this modifier does not override display. +*/ +.right-dock--pinned { + position: relative; + box-shadow: none; +} + .right-dock--with-footer { padding-bottom: var(--executor-footer-height); } diff --git a/packages/dashboard/app/components/RightDock.tsx b/packages/dashboard/app/components/RightDock.tsx index b0e44324cc..a5706bbe05 100644 --- a/packages/dashboard/app/components/RightDock.tsx +++ b/packages/dashboard/app/components/RightDock.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from "react"; -import { Maximize2 } from "lucide-react"; +import { Maximize2, Pin, PinOff } from "lucide-react"; import { useTranslation } from "react-i18next"; import { findOverflowViewEntry, @@ -21,6 +21,7 @@ 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"; +export const RIGHT_DOCK_PINNED_STORAGE_KEY = "fusion:right-dock-pinned"; function clampRightDockWidth(width: number): number { return Math.max(RIGHT_DOCK_MIN_WIDTH, Math.min(RIGHT_DOCK_MAX_WIDTH, width)); @@ -46,6 +47,23 @@ export function persistRightDockOpen(open: boolean): void { } } +/* +FNXC:RightDockPin 2026-06-27-00:00: +Right-dock push mode is a local, reversible UI preference. Default missing/invalid storage to unpinned so existing overlay behavior remains unchanged, and keep the same SSR-safe localStorage pattern used by the dock open flag. +*/ +export function readStoredRightDockPinned(): boolean { + if (typeof window === "undefined") return false; + return window.localStorage.getItem(RIGHT_DOCK_PINNED_STORAGE_KEY) === "true"; +} + +export function persistRightDockPinned(pinned: boolean): void { + try { + window.localStorage.setItem(RIGHT_DOCK_PINNED_STORAGE_KEY, String(pinned)); + } catch { + // Ignore storage errors. + } +} + function isInlineOverflowViewKey(key: string, options: OverflowViewVisibilityOptions): key is OverflowViewKey { const entry = findOverflowViewEntry(key as OverflowViewKey, options); return Boolean(entry?.render); @@ -79,6 +97,8 @@ export interface RightDockProps { visibilityOptions?: OverflowViewVisibilityOptions; onExpand?: (key: OverflowViewKey) => void; footerVisible?: boolean; + pinned: boolean; + onTogglePin: () => void; } /* @@ -100,6 +120,8 @@ export function RightDock({ visibilityOptions = {}, onExpand, footerVisible = false, + pinned, + onTogglePin, }: RightDockProps) { const { t } = useTranslation("app"); const entries = useMemo(() => getVisibleOverflowViewEntries(visibilityOptions), [visibilityOptions]); @@ -204,10 +226,14 @@ export function RightDock({ const SelectedIcon = selectedEntry.icon; const dockWidth = `${width}px`; const expandSelectedViewLabel = t("rightDock.expandView", "Expand {{label}}", { label: selectedEntry.label }); + const pinLabel = pinned + ? t("rightDock.unpin", "Unpin sidebar (overlay content)") + : t("rightDock.pin", "Pin sidebar (push content)"); + const PinIcon = pinned ? PinOff : Pin; return (