diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 142464cc2d..994e4966cf 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -2117,7 +2117,7 @@ In **Settings โ†’ General**, choose up to six Mobile footer quick actions from t ## In-app reports -The Header **Report** menu is available on desktop and mobile and offers **Bug**, **Feedback**, **Idea**, and **Help**. Each action begins with a short, guided prompt rather than a raw GitHub issue form. +The **Report** menu is available in **Settings โ†’ General ยท Project** and the Command Center **Overview** on desktop and mobile. It offers **Bug**, **Feedback**, **Idea**, and **Help**; each action begins with a short, guided prompt rather than a raw GitHub issue form. Fusion gathers available task/agent context, structures the prompt into a report, scrubs secrets, local paths, project names, home-directory identities, email addresses, and likely personal names, then checks **open** GitHub issues or Discussions for duplicates. Scrubbing is mandatory for every route and is repeated on the server when a reviewed draft is edited before filing. A strong duplicate receives a visible ๐Ÿ‘ reaction and one scrubbed data-point comment instead of a new issue or Discussion. Bug and Idea reports use issues; Feedback and unresolved Help reports use repository Discussions. If preparation or filing cannot reach GitHub, Fusion preserves the draft and shows a retryable error. diff --git a/packages/dashboard/app/components/Header.tsx b/packages/dashboard/app/components/Header.tsx index 4a4e84031c..31a92591ec 100644 --- a/packages/dashboard/app/components/Header.tsx +++ b/packages/dashboard/app/components/Header.tsx @@ -17,28 +17,12 @@ import type { PluginDashboardViewEntry } from "../api"; import { buildPluginTaskViewId, isPluginViewId } from "../plugins/pluginViewRegistry"; import { getPluginNavIcon } from "./pluginNavIcon"; import type { ShellHostContext } from "../shell-host"; -import type { ReportActionType } from "@fusion/core"; -import { ReportActionMenu } from "./ReportActionMenu"; -import { ReportModal } from "./ReportModal"; +export { resolveReportContextRefs } from "../utils/reportContextRefs"; export { useViewportMode }; const NO_BRANCH_FILTER_VALUE = "__fusion:no-branch__"; -/** - * FNXC:ReportPipeline 2026-07-16-19:30: - * Reports opened from task detail routes must gather the active task's logs, - * agent, and errors. Task navigation uses hash routes, while legacy links use - * query parameters, so resolve both at modal-open render time. - */ -export function resolveReportContextRefs(location: Pick): { taskId?: string; agentId?: string } | undefined { - const params = new URLSearchParams(location.search); - const hashTaskMatch = location.hash.match(/^#\/tasks\/([^/?#]+)/); - const taskId = hashTaskMatch?.[1] ? decodeURIComponent(hashTaskMatch[1]) : params.get("taskId") ?? undefined; - const agentId = params.get("agentId") ?? undefined; - return taskId || agentId ? { taskId, agentId } : undefined; -} - // Status icon config for project selector dropdown const PROJECT_STATUS_CONFIG: Record = { active: { color: "var(--success)" }, @@ -189,10 +173,6 @@ export function Header({ const isTablet = mode === "tablet"; const isCompact = isMobile || isTablet; const hideFullNav = isMobile && mobileNavEnabled; - const [reportAction, setReportAction] = useState(null); - // Resolve on every render so opening a report after in-app hash navigation - // uses the current task rather than the Header's initial route. - const reportContextRefs = typeof window === "undefined" ? undefined : resolveReportContextRefs(window.location); /* FNXC:Navigation 2026-06-19-00:00: When experimental left sidebar navigation is active on tablet/desktop, Header must suppress its view-toggle and More-views trigger so there is one canonical non-mobile navigation surface and no orphaned chevron remains. @@ -1000,12 +980,12 @@ export function Header({ When the left sidebar is active it owns Workflows as a main-content destination, so the Header drops its duplicate desktop Workflow button. The flag-off desktop layout keeps the Header button; mobile/tablet keep the overflow entry. */} {/* - FNXC:ReportPipeline 2026-07-16-21:30: - Bug, Feedback, Idea, and Help must remain reachable at every responsive - breakpoint. Compact navigation can hide the overflow trigger entirely, - so the report menu lives directly in the header action row instead. + FNXC:ReportPipeline 2026-07-18-19:25: + FN-8348 relocates guided Bug, Feedback, Idea, and Help reporting from + Header to Settings General and Command Center. Those always-reachable + destinations replace this responsive action-row slot without leaving a + compact-navigation dependency or an empty header control. */} - {!isCompact && !leftSidebarNavActive && onOpenWorkflowEditor && ( + {reportAction && setReportAction(null)} />} ); } export default GeneralSection; diff --git a/packages/dashboard/app/components/settings/sections/__tests__/GeneralSection.aiUndoWorkflow.test.tsx b/packages/dashboard/app/components/settings/sections/__tests__/GeneralSection.aiUndoWorkflow.test.tsx index cafb52932c..eca6981584 100644 --- a/packages/dashboard/app/components/settings/sections/__tests__/GeneralSection.aiUndoWorkflow.test.tsx +++ b/packages/dashboard/app/components/settings/sections/__tests__/GeneralSection.aiUndoWorkflow.test.tsx @@ -12,10 +12,12 @@ import { render, screen, fireEvent, cleanup, waitFor } from "@testing-library/re import * as jestDomMatchers from "@testing-library/jest-dom/matchers"; import { GeneralSection } from "../GeneralSection"; +import { SETTINGS_SECTIONS } from "../../../SettingsModal"; import type { SettingsFormState } from "../context"; import { fetchWorkflows } from "../../../../api"; -vi.mock("react-i18next", () => ({ +vi.mock("react-i18next", async (importOriginal) => ({ + ...await importOriginal(), useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key, }), @@ -69,7 +71,21 @@ function GeneralHost({ initialForm, onSetForm }: { ); } -describe("GeneralSection - AI-undo task workflow picker", () => { +describe("GeneralSection - project controls", () => { + it("renders the report menu and opens its guided modal", async () => { + render(); + + fireEvent.click(screen.getByRole("button", { name: "Report" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Report bug" })); + + expect(await screen.findByRole("dialog", { name: "bug report" })).toBeInTheDocument(); + }); + + it("advertises report actions through General settings search metadata", () => { + const general = SETTINGS_SECTIONS.find((section) => section.id === "general"); + expect(general?.searchableText).toEqual(expect.arrayContaining(["report", "report bug", "send feedback", "share idea", "get help"])); + }); + it("shows builtin:review-heavy as the effective default when unset", async () => { render(); diff --git a/packages/dashboard/app/utils/reportContextRefs.ts b/packages/dashboard/app/utils/reportContextRefs.ts new file mode 100644 index 0000000000..3c622c1280 --- /dev/null +++ b/packages/dashboard/app/utils/reportContextRefs.ts @@ -0,0 +1,13 @@ +/** + * FNXC:ReportPipeline 2026-08-02-00:00: + * FN-8348 moves report entry points from Header into Settings General and Command + * Center. Resolve task and agent context in this shared helper so each home keeps + * the same deep-link behavior without duplicating route parsing. + */ +export function resolveReportContextRefs(location: Pick): { taskId?: string; agentId?: string } | undefined { + const params = new URLSearchParams(location.search); + const hashTaskMatch = location.hash.match(/^#\/tasks\/([^/?#]+)/); + const taskId = hashTaskMatch?.[1] ? decodeURIComponent(hashTaskMatch[1]) : params.get("taskId") ?? undefined; + const agentId = params.get("agentId") ?? undefined; + return taskId || agentId ? { taskId, agentId } : undefined; +}