Make the right dock a persistent far-right surface with its own collapse control. - Default the right dock to visible and preserve a collapsed rail instead of hiding it entirely.\n- Remove Header right-dock toggle props and routing so More views remains a menu and left-sidebar mode has no duplicate Header toggle.\n- Update docs, changeset, and regression tests for persistent dock behavior.\n\nFiles changed:\n .changeset/fn-6899-click-revealed-right-dock.md | 5 ++\n docs/dashboard-guide.md | 6 +-\n packages/dashboard/app/App.tsx | 1 -\n .../mobile-feature-access-regression.test.tsx | 32 ++------\n packages/dashboard/app/components/Header.tsx | 57 +++-----------\n packages/dashboard/app/components/RightDock.css | 18 +++++\n packages/dashboard/app/components/RightDock.tsx | 86 ++++++++++++----------\n .../app/components/__tests__/Header.test.tsx | 57 +++-----------\n .../app/components/__tests__/RightDock.test.tsx | 31 ++++++--\n .../app/components/useRightDockController.tsx | 4 +-\n 10 files changed, 132 insertions(+), 165 deletions(-) Fusion-Task-Id: FN-6899 Fusion-Task-Lineage: 96c4ba70-eb73-4034-8254-6a192402d905
137 lines
5.8 KiB
TypeScript
137 lines
5.8 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
|
|
import type { Task, TaskDetail, WorkflowStep } from "@fusion/core";
|
|
import { isNearDuplicateCanonicalInactive } from "../../../core/src/near-duplicate-canonical";
|
|
import type { ToastType } from "../hooks/useToast";
|
|
import type { DetailTaskTab } from "../hooks/useModalManager";
|
|
import { fetchTaskDetail } from "../api";
|
|
import { TaskCard } from "./TaskCard";
|
|
import { RightDock, persistRightDockOpen, readStoredRightDockOpen } from "./RightDock";
|
|
import { RightDockExpandModal } from "./RightDockExpandModal";
|
|
import type { OverflowViewKey, OverflowViewRenderProps, OverflowViewVisibilityOptions } from "./overflowViewRegistry";
|
|
|
|
export interface RightDockControllerInput {
|
|
active: boolean;
|
|
projectId?: string;
|
|
addToast: (message: string, type?: ToastType) => void;
|
|
settingsLoaded: boolean;
|
|
researchReadinessVersion: number;
|
|
goalAnchorId?: string;
|
|
tasks: Array<Task | TaskDetail>;
|
|
workflowSteps: WorkflowStep[];
|
|
subscribePluginEvents: (pluginId: string, onEvent: (event: { event: string; payload: unknown }) => void) => () => void;
|
|
openDetailTask: (task: Task | TaskDetail, initialTab?: DetailTaskTab) => void;
|
|
openFileInBrowser: (path: string, opts?: { workspace?: string; line?: number; col?: number }) => void;
|
|
openSettings: (section?: string) => void;
|
|
onOpenUsage?: (anchorRect?: DOMRect | null) => void;
|
|
onOpenActivityLog?: () => void;
|
|
onOpenGitHubImport?: () => void;
|
|
onOpenGitManager?: () => void;
|
|
onOpenSchedules?: () => void;
|
|
onSendSelectionToTask: (description: string) => void;
|
|
onCreateTaskFromInsight: (payload: { insightId: string; title: string; description: string }) => Promise<void> | void;
|
|
onNavigateToMission: (missionId: string) => void;
|
|
onTaskCreated: (task: Task) => void;
|
|
workflowStepNameLookup: Map<string, string>;
|
|
prAuthAvailable: boolean;
|
|
autoMerge: boolean;
|
|
visibilityOptions: OverflowViewVisibilityOptions;
|
|
footerVisible: boolean;
|
|
}
|
|
|
|
export interface RightDockController {
|
|
open: boolean;
|
|
toggle: () => void;
|
|
dock: ReactNode;
|
|
modal: ReactNode;
|
|
}
|
|
|
|
/*
|
|
FNXC:Navigation 2026-06-21-23:40:
|
|
The right dock is visible by default and collapses from inside the dock. Keep the persisted open/collapsed state in this controller so App and Header do not need duplicate right-dock toggle wiring.
|
|
*/
|
|
export function useRightDockController(input: RightDockControllerInput): RightDockController {
|
|
const [open, setOpen] = useState(readStoredRightDockOpen);
|
|
const [expandedView, setExpandedView] = useState<OverflowViewKey | null>(null);
|
|
|
|
const setPersistedOpen = useCallback((nextOpen: boolean) => {
|
|
setOpen(nextOpen);
|
|
persistRightDockOpen(nextOpen);
|
|
if (!nextOpen) setExpandedView(null);
|
|
}, []);
|
|
const toggle = useCallback(() => {
|
|
setOpen((current) => {
|
|
const next = !current;
|
|
persistRightDockOpen(next);
|
|
if (!next) setExpandedView(null);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!input.active) setExpandedView(null);
|
|
}, [input.active]);
|
|
|
|
const renderTaskCard = useCallback((task: Task | TaskDetail) => (
|
|
<TaskCard
|
|
task={task}
|
|
projectId={input.projectId}
|
|
onOpenDetail={(value: Task | TaskDetail) => input.openDetailTask(value)}
|
|
addToast={input.addToast}
|
|
workflowStepNameLookup={input.workflowStepNameLookup}
|
|
disableDrag={true}
|
|
prAuthAvailable={input.prAuthAvailable}
|
|
autoMergeEnabled={input.autoMerge}
|
|
nearDuplicateCanonicalInactive={typeof task.sourceMetadata?.nearDuplicateOf === "string"
|
|
? isNearDuplicateCanonicalInactive(input.tasks.find((candidate) => candidate.id === task.sourceMetadata?.nearDuplicateOf))
|
|
: undefined}
|
|
/>
|
|
), [input]);
|
|
|
|
const renderProps = useMemo<OverflowViewRenderProps>(() => ({
|
|
projectId: input.projectId,
|
|
addToast: input.addToast,
|
|
settingsLoaded: input.settingsLoaded,
|
|
readinessVersion: input.researchReadinessVersion,
|
|
anchorGoalId: input.goalAnchorId,
|
|
tasks: input.tasks,
|
|
workflowSteps: input.workflowSteps,
|
|
pluginContext: {
|
|
projectId: input.projectId,
|
|
tasks: input.tasks as Task[],
|
|
workflowSteps: input.workflowSteps,
|
|
subscribePluginEvents: input.subscribePluginEvents,
|
|
openTaskDetail: (task: Task | TaskDetail, initialTab?: DetailTaskTab) => input.openDetailTask(task, initialTab),
|
|
openFile: input.openFileInBrowser,
|
|
renderTaskCard,
|
|
addToast: input.addToast,
|
|
},
|
|
onOpenSettings: input.openSettings,
|
|
onOpenUsage: input.onOpenUsage,
|
|
onOpenActivityLog: input.onOpenActivityLog,
|
|
onOpenGitHubImport: input.onOpenGitHubImport,
|
|
onOpenGitManager: input.onOpenGitManager,
|
|
onOpenSchedules: input.onOpenSchedules,
|
|
onOpenTaskDetail: (taskId: string) => {
|
|
void fetchTaskDetail(taskId, input.projectId)
|
|
.then((task) => input.openDetailTask(task as TaskDetail))
|
|
.catch((error) => input.addToast(error instanceof Error ? error.message : "Failed to open task detail", "error"));
|
|
},
|
|
onOpenDetail: input.openDetailTask,
|
|
onSendSelectionToTask: input.onSendSelectionToTask,
|
|
onCreateTaskFromInsight: input.onCreateTaskFromInsight,
|
|
onNavigateToMission: input.onNavigateToMission,
|
|
onPlanningMode: input.onSendSelectionToTask,
|
|
onTaskCreated: input.onTaskCreated,
|
|
renderTaskCard,
|
|
subscribePluginEvents: input.subscribePluginEvents,
|
|
openFile: input.openFileInBrowser,
|
|
}), [input, renderTaskCard]);
|
|
|
|
return {
|
|
open,
|
|
toggle,
|
|
dock: input.active ? <RightDock open={open} onOpenChange={setPersistedOpen} renderProps={renderProps} visibilityOptions={input.visibilityOptions} footerVisible={input.footerVisible} onExpand={setExpandedView} /> : null,
|
|
modal: input.active ? <RightDockExpandModal viewKey={expandedView} renderProps={renderProps} visibilityOptions={input.visibilityOptions} onClose={() => setExpandedView(null)} /> : null,
|
|
};
|
|
}
|