From dc0064bd15c28ddd3c8f59c785effe3ef0d6ca5f Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 22 Jun 2026 01:27:21 -0700 Subject: [PATCH] feat(dashboard): right-dock inline tools, left-sidebar views, embedded-view polish - Right dock: Files first/default; usage/activity-log/git-manager render inline; embedded Git Manager uses a container query (compact horizontal tab strip in the dock, full two-pane in the wide pop-out); Files inline viewer + pop-out. - Import Tasks layout fits its container (stacked when narrow, two-pane when wide); Import Tasks uses the GitHub mark. - Planning embeds full-area and works on mobile; planning shows the board WorkflowSwitcher. - Automations screen uses theme color tokens. - Workflow selector matches the project selector height/font. - Left sidebar: uniform spacing across the primary/secondary boundary. Adds a changeset for @runfusion/fusion (minor). Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/sidebar-panel-redesign.md | 10 ++ .../app/components/GitHubImportModal.css | 55 +++++++++++ .../app/components/LeftSidebarNav.css | 6 +- .../app/components/LeftSidebarNav.tsx | 15 ++- .../app/components/PlanningModeModal.css | 43 +++++++-- .../dashboard/app/components/RoutineCard.tsx | 17 +++- .../dashboard/app/components/ScriptsModal.css | 91 +++++++++++-------- .../app/components/WorkflowSwitcher.css | 6 ++ .../app/components/overflowViewRegistry.tsx | 15 +-- 9 files changed, 197 insertions(+), 61 deletions(-) create mode 100644 .changeset/sidebar-panel-redesign.md diff --git a/.changeset/sidebar-panel-redesign.md b/.changeset/sidebar-panel-redesign.md new file mode 100644 index 0000000000..e66c4f4545 --- /dev/null +++ b/.changeset/sidebar-panel-redesign.md @@ -0,0 +1,10 @@ +--- +"@runfusion/fusion": minor +--- + +Dashboard navigation and panel redesign (desktop/tablet; mobile unchanged): + +- **Right sidebar**: a single show/hide toggle now lives in the top header (replacing the tablet overflow menu); the dock is hidden when closed and no longer keeps a persistent icon rail or in-dock collapse button. Its tools (Files — now the default/first tab, Activity, Activity Log, Git Manager) render inline inside the dock instead of opening popup modals. Files opens inline with a pop-out to the resizable file modal. The embedded Git Manager adapts to its width (compact horizontal tab strip in the dock, full two-pane in the wide pop-out). The dependency graph no longer appears in the dock. +- **Left sidebar**: New Task button matches the item-highlight box; footer spacing between Collapse and Settings; divider before the secondary section removed with uniform row spacing. New main-content destinations — Workflows, Import Tasks (GitHub import, with the GitHub mark), and Automations (two-pane, Command Center styling) — render in the main panel instead of as modals. +- **Embedded views**: Planning Mode embeds without modal chrome (no header/close/shadow), fills the full content area, and renders correctly on mobile; the board WorkflowSwitcher is available in Planning. Dev Server header matches Command Center. Insights header wraps so actions don't overlap. List view's left pane can be dragged much narrower with two-line title wrapping. +- **Other**: the docked terminal no longer blurs or blocks the page behind it; the footer Terminal button renders as plain text like the running-state trigger; the workflow selector matches the project selector's styling, height, and font size; the Automations screen uses theme color tokens. diff --git a/packages/dashboard/app/components/GitHubImportModal.css b/packages/dashboard/app/components/GitHubImportModal.css index c2661d53c4..6f7ae71bb6 100644 --- a/packages/dashboard/app/components/GitHubImportModal.css +++ b/packages/dashboard/app/components/GitHubImportModal.css @@ -962,4 +962,59 @@ The embedded root is a plain flow box that fills the host; the inner shell sheds resize: none; } +/* +FNXC:RightDockEmbedding 2026-06-22-12:30: +Embedded "Import Tasks" main-content view must fit on screen and react to its OWN width, not the viewport. +The shared two-pane code sets the list pane width via an inline flex-basis (default 360px) whenever the VIEWPORT is wide (canResizePanes => innerWidth > 860). In the embedded main area the host can be far narrower than the viewport, so that inline 360px list pane plus the preview overflowed horizontally. +Fix (embedded variant only — modal path untouched): turn the embedded root into a query container (container-type: inline-size) and drive the layout off @container width. +- Narrow container (default): stack list ABOVE preview in a single column; cap the list height and override the inline desktop flex-basis so nothing forces horizontal overflow. Long titles/repo names already truncate via .issue-title ellipsis / .issue-main min-width:0, and labels/branch info wrap. +- Wide container (>= 720px): restore the two-pane row, but bound the list pane to a sane share of the container (clamp) instead of trusting the viewport-derived inline width. +*/ +.github-import-embedded.right-dock-embedded-view { + container-type: inline-size; + container-name: github-import-embedded; +} + +/* Default (narrow container): single stacked column. */ +.github-import-modal--embedded .github-import-workspace { + flex-direction: column; +} + +/* Override the inline viewport-derived flex-basis so the list never forces overflow when stacked. */ +.github-import-modal--embedded .github-import-list-pane { + flex: 0 0 auto !important; + width: 100%; + max-height: 40cqh; + padding-right: 0; +} + +.github-import-modal--embedded .github-import-preview-pane { + flex: 1 1 auto; + width: 100%; + min-width: 0; +} + +/* Hide the col-resize handle when stacked; it only makes sense in the side-by-side layout. */ +.github-import-modal--embedded .github-import-workspace__resize-handle { + display: none; +} + +@container github-import-embedded (min-width: 720px) { + .github-import-modal--embedded .github-import-workspace { + flex-direction: row; + } + + /* Side-by-side again: bound the list pane to a share of the CONTAINER width, overriding the inline viewport width. */ + .github-import-modal--embedded .github-import-list-pane { + flex: 0 1 clamp(240px, 38cqi, 420px) !important; + width: auto; + max-height: none; + padding-right: var(--space-md); + } + + .github-import-modal--embedded .github-import-workspace__resize-handle { + display: block; + } +} + diff --git a/packages/dashboard/app/components/LeftSidebarNav.css b/packages/dashboard/app/components/LeftSidebarNav.css index 42e9b2751f..025c2c76d4 100644 --- a/packages/dashboard/app/components/LeftSidebarNav.css +++ b/packages/dashboard/app/components/LeftSidebarNav.css @@ -85,11 +85,11 @@ With the secondary divider removed the nav reads as one continuous list, so the } /* -FNXC:Navigation 2026-06-22-00:00: -The secondary section keeps its top spacing but drops the divider line before the first secondary entry (Goals/Evals); the rule reads as one continuous nav list instead of two bordered groups. +FNXC:Navigation 2026-06-22-00:30: +The secondary section has no divider and no extra top padding, so the gap across the primary/secondary boundary (e.g. Compound -> Workflows) equals the --space-xs list/row rhythm and the nav reads as one continuous list. */ .left-sidebar-nav__section--secondary { - padding-top: var(--space-sm); + padding-top: 0; } .left-sidebar-nav__item { diff --git a/packages/dashboard/app/components/LeftSidebarNav.tsx b/packages/dashboard/app/components/LeftSidebarNav.tsx index df2b92b476..ea76614936 100644 --- a/packages/dashboard/app/components/LeftSidebarNav.tsx +++ b/packages/dashboard/app/components/LeftSidebarNav.tsx @@ -14,7 +14,6 @@ import { Clock, FileText, Gauge, - GitPullRequestArrow, Lightbulb, LayoutGrid, List, @@ -35,6 +34,18 @@ import type { TaskView } from "../hooks/useViewState"; import { buildPluginTaskViewId } from "../plugins/pluginViewRegistry"; import { getPluginNavIcon } from "./pluginNavIcon"; +/* +FNXC:Navigation 2026-06-22-00:30: +Import Tasks uses the GitHub brand mark. lucide-react in this repo does not export a `Github` icon, so render the octocat glyph as a LucideProps-compatible component (size defaults to 16, currentColor fill) usable wherever a sidebar entry icon is expected. +*/ +function GithubIcon({ size = 16, ...props }: LucideProps) { + return ( + + ); +} + export interface LeftSidebarExperimentalFeatures { insights?: boolean; memoryView?: boolean; @@ -367,7 +378,7 @@ export function LeftSidebarNav({ label: t("nav.importTasks", "Import Tasks"), view: "import-tasks" as TaskView, isActive: view === "import-tasks", - icon: GitPullRequestArrow, + icon: GithubIcon, testId: "sidebar-nav-import-tasks", onSelect: () => onChangeView("import-tasks"), }, diff --git a/packages/dashboard/app/components/PlanningModeModal.css b/packages/dashboard/app/components/PlanningModeModal.css index c83f62ef72..a8d3b395e6 100644 --- a/packages/dashboard/app/components/PlanningModeModal.css +++ b/packages/dashboard/app/components/PlanningModeModal.css @@ -56,12 +56,19 @@ FN-6886 promotes Planning Mode into the main app content area. The embedded shel .planning-view { height: 100%; min-height: 0; + width: 100%; + flex: 1; display: flex; padding: var(--space-lg); overflow: hidden; } +/* +FNXC:PlanningMode 2026-06-22-15:30: +FN-6886 full-view fix: embedded planning must fill the entire main-content pane with no awkward gaps. The flex/height chain is planning-view (flex:1, height:100%) -> planning-modal--embedded (flex:1, height:100%) -> planning-modal-body (flex:1, min-height:0). flex:1 + min-width:0 + min-height:0 on the embedded panel lets it consume all remaining width/height inside the flex .planning-view wrapper instead of collapsing to the base .modal 480px width. +*/ .planning-modal--embedded { + flex: 1; width: 100%; max-width: none; min-width: 0; @@ -383,17 +390,26 @@ The embedded planning title must read like other embedded-view titles (Command C box-shadow: var(--focus-ring-strong); } -/* Mobile: stack — only one pane visible at a time */ -@media (max-width: 768px) { +/* Mobile: stack — only one pane visible at a time. + FNXC:PlanningMode 2026-06-22-15:30 covers both the landscape phone case + (max-height: 480px exceeds 768px wide) and portrait. */ +@media (max-width: 768px), (max-height: 480px) { /* Full-screen sheet — drop overlay padding so the modal fills the viewport instead of being pushed below it, and disable resize since - touchscreen users can't drag the corner grip anyway. */ + touchscreen users can't drag the corner grip anyway. + FNXC:PlanningMode 2026-06-22-15:30: scope the viewport-takeover rules to + the NON-embedded (dialog) presentation only. The embedded panel + (.planning-modal--embedded) must NOT grab 100vw/100dvh — it lives inside + the main-content pane, so forcing full-viewport sizing made it overflow + the content area on mobile. The :not(.planning-modal--embedded) guard + keeps the modal full-screen sheet intact while letting the embedded view + fill only its own pane (handled by the .planning-view rules below). */ .modal-overlay:has(.planning-modal) { padding-top: 0; align-items: stretch; justify-content: stretch; } - .modal.planning-modal { + .modal.planning-modal:not(.planning-modal--embedded) { width: 100vw; min-width: 0; max-width: 100vw; @@ -405,12 +421,23 @@ The embedded planning title must read like other embedded-view titles (Command C border-radius: 0; resize: none; } - .modal.planning-modal[style*="--keyboard-overlap"] { + .modal.planning-modal:not(.planning-modal--embedded)[style*="--keyboard-overlap"] { height: var(--vv-height, 100dvh); max-height: var(--vv-height, 100dvh); transform: translateY(var(--vv-offset-top, 0px)); will-change: transform; } + /* FNXC:PlanningMode 2026-06-22-15:30: on mobile the embedded view should use + the full width of the (already-narrow) content pane — drop the outer + padding so the session list and detail panes are edge-to-edge, and keep + the height/flex chain filling the pane. */ + .planning-view { + padding: var(--space-sm); + } + .planning-view .planning-modal--embedded { + width: 100%; + height: 100%; + } .planning-modal-body--split { flex-direction: column; } @@ -1374,7 +1401,11 @@ The embedded planning title must read like other embedded-view titles (Command C /* Responsive */ @media (max-width: 768px) { - .planning-modal { + /* FNXC:PlanningMode 2026-06-22-15:30: this legacy full-viewport sheet sizing + is for the dialog presentation only. The :not(.planning-modal--embedded) + guard prevents the embedded view from being yanked to 100vw/100dvh, which + would overflow the main-content pane it lives in on mobile. */ + .planning-modal:not(.planning-modal--embedded) { width: 100vw; height: 100vh; height: 100dvh; diff --git a/packages/dashboard/app/components/RoutineCard.tsx b/packages/dashboard/app/components/RoutineCard.tsx index a95f552363..ba3c0c0060 100644 --- a/packages/dashboard/app/components/RoutineCard.tsx +++ b/packages/dashboard/app/components/RoutineCard.tsx @@ -46,11 +46,20 @@ function relativeTime(iso: string): string { return `${Math.floor(diffMs / 86_400_000)}d ago`; } +/* +FNXC:Automations 2026-06-22-12:00: +Trigger-type badge colors must use the design system's THEME COLOR TOKENS so the Automations screen follows the +active theme (including light theme) instead of fixed hex literals. The previous values referenced undefined tokens +(--color-blue/-purple/-green/-gray) with hardcoded hex fallbacks that never resolved to a real token and never +adapted to the theme. Mapped to the closest defined semantic tokens from styles.css: cron→--todo (blue status), +webhook→--accent (brand purple), api→--color-success (green), manual→--text-muted (neutral). The badge applies this +to both border and text via inline style on .routine-trigger-badge. +*/ const TRIGGER_TYPE_COLORS: Record = { - cron: "var(--color-blue, #3b82f6)", - webhook: "var(--color-purple, #a855f7)", - api: "var(--color-green, #22c55e)", - manual: "var(--color-gray, #6b7280)", + cron: "var(--todo)", + webhook: "var(--accent)", + api: "var(--color-success)", + manual: "var(--text-muted)", }; const TRIGGER_TYPE_LABELS: Record = { diff --git a/packages/dashboard/app/components/ScriptsModal.css b/packages/dashboard/app/components/ScriptsModal.css index 75cb7b60f1..54f587b0c6 100644 --- a/packages/dashboard/app/components/ScriptsModal.css +++ b/packages/dashboard/app/components/ScriptsModal.css @@ -1289,9 +1289,15 @@ when supported, falling back to a min-width media breakpoint, that collapses to border-color: var(--accent); } +/* +FNXC:Automations 2026-06-22-12:00: +Selected automation row uses the theme accent token. --accent-subtle is not a defined token; fall back to a +theme-derived subtle accent tint (color-mix house style) so the active state follows the active theme (incl. light) +instead of resolving to a flat --card with no accent emphasis. +*/ .automation-list-row.active { border-color: var(--accent); - background: var(--accent-subtle, var(--card)); + background: var(--accent-subtle, color-mix(in srgb, var(--accent) 10%, transparent)); } .automation-list-row-name { @@ -1964,53 +1970,60 @@ The embedded host fills its right-dock container; the inner shell drops overlay- border-radius: 0; resize: none; position: static; + /* Drive the dock-vs-expand layout off the embedded host's own width, not the viewport. */ + container-type: inline-size; + container-name: gm-embedded; } /* -FNXC:GitManager 2026-06-22-00:10: -The embedded Git Manager renders inside the narrow right dock, so it must use the mobile single-column layout (section tabs as a horizontal strip above a full-width content pane) regardless of viewport width, mirroring the max-width:768px rules. +FNXC:GitManager 2026-06-22-00:25: +The embedded Git Manager adapts to its container width, not the viewport, so the SAME embedded render works in both the narrow right dock and the wide pop-out (expand) modal: +- Wide container (expand modal): inherits the default desktop layout — vertical section sidebar + content (two-pane, like before). +- Narrow container (dock, < 560px): section tabs become a horizontal strip above a full-width content pane, and each tab is compact (min-width:0, tight padding, inline icon+label) so MORE tabs fit in the strip at once. The content collapses to a single column. */ -.gm-modal--embedded .gm-layout { - flex-direction: column; -} +@container gm-embedded (max-width: 560px) { + .gm-modal--embedded .gm-layout { + flex-direction: column; + } -.gm-modal--embedded .gm-sidebar { - flex: 0 0 auto; - flex-direction: row; - width: 100%; - min-width: 0; - min-height: calc(var(--space-2xl) + var(--space-md)); - border-right: none; - border-bottom: 1px solid var(--border); - overflow-x: auto; - overflow-y: hidden; - padding: var(--space-xs) var(--space-sm); - gap: var(--space-xs); -} + .gm-modal--embedded .gm-sidebar { + flex: 0 0 auto; + flex-direction: row; + width: 100%; + min-width: 0; + min-height: 0; + border-right: none; + border-bottom: 1px solid var(--border); + overflow-x: auto; + overflow-y: hidden; + padding: var(--space-xs); + gap: calc(var(--space-xs) / 2); + } -.gm-modal--embedded .gm-nav-item { - flex: 0 0 auto; - flex-direction: column; - gap: calc(var(--space-xs) / 2); - padding: var(--space-xs) var(--space-sm); - border-left: none; - border-bottom: 2px solid transparent; - min-width: calc(var(--space-2xl) + var(--space-xl)); - text-align: center; - justify-content: center; -} + .gm-modal--embedded .gm-nav-item { + flex: 0 0 auto; + flex-direction: row; + gap: var(--space-xs); + padding: calc(var(--space-xs) / 2) var(--space-xs); + border-left: none; + border-bottom: 2px solid transparent; + min-width: 0; + font-size: var(--font-size-xs); + white-space: nowrap; + } -.gm-modal--embedded .gm-nav-item.active { - border-left-color: transparent; - border-bottom-color: var(--todo); -} + .gm-modal--embedded .gm-nav-item.active { + border-left-color: transparent; + border-bottom-color: var(--todo); + } -.gm-modal--embedded .gm-status-grid { - grid-template-columns: 1fr; -} + .gm-modal--embedded .gm-status-grid { + grid-template-columns: 1fr; + } -.gm-modal--embedded .gm-create-form { - flex-wrap: wrap; + .gm-modal--embedded .gm-create-form { + flex-wrap: wrap; + } } /* Main layout: sidebar + content */ diff --git a/packages/dashboard/app/components/WorkflowSwitcher.css b/packages/dashboard/app/components/WorkflowSwitcher.css index 653d853031..8428da4e68 100644 --- a/packages/dashboard/app/components/WorkflowSwitcher.css +++ b/packages/dashboard/app/components/WorkflowSwitcher.css @@ -28,6 +28,12 @@ The workflow selector trigger must match the project selector trigger styling: t border-radius: var(--radius-md); color: var(--text-muted); font: inherit; + /* + FNXC:WorkflowSwitcher 2026-06-22-00:10: + Match the project selector trigger height and font size: the project label uses 13px / line-height 1, so set the same here (the parent .workflow-switcher font-size is smaller). With identical padding + font-size + line-height the two triggers render the same height. + */ + font-size: 13px; + line-height: 1; text-align: left; transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast); } diff --git a/packages/dashboard/app/components/overflowViewRegistry.tsx b/packages/dashboard/app/components/overflowViewRegistry.tsx index e6244e0e80..b37ea03637 100644 --- a/packages/dashboard/app/components/overflowViewRegistry.tsx +++ b/packages/dashboard/app/components/overflowViewRegistry.tsx @@ -99,6 +99,14 @@ FNXC:Navigation 2026-06-22-00:00: Right-dock tools render INLINE inside the dock container, not as popup modals: usage, activity-log, and git-manager use each modal's `presentation="embedded"` mode instead of launching an overlay. (github-import and automation remain launcher actions here only until their left-sidebar/main destinations land, then they leave the dock.) */ export const STATIC_OVERFLOW_VIEW_ENTRIES: readonly OverflowViewEntry[] = [ + /* FNXC:Navigation 2026-06-22-00:20: Files is the first/default right-dock tool. */ + { + key: "files", + label: "Files", + icon: Folder, + testId: "right-dock-tab-files", + render: (props) => wrapOverflowView(), + }, { key: "usage", label: "Activity", @@ -140,13 +148,6 @@ export const STATIC_OVERFLOW_VIEW_ENTRIES: readonly OverflowViewEntry[] = [ />, ), }, - { - key: "files", - label: "Files", - icon: Folder, - testId: "right-dock-tab-files", - render: (props) => wrapOverflowView(), - }, ]; function buildPluginOverflowViewEntries(pluginDashboardViews: PluginDashboardViewEntry[] = []): OverflowViewEntry[] {