From 1215c76d9a94c213cc0935fbf8dfbfc1d80f3b8d Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 18 Jul 2026 14:19:21 -0700 Subject: [PATCH] FN-8326: add roadmap report deduplication Add an optional public-roadmap source for report deduplication. - Add project settings and localized controls for roadmap matching. - Resolve roadmap entries through the plugin API and stop strong duplicate reports before filing. - Cover report pipeline, route, UI, and roadmap store behavior with tests. Files changed: .changeset/fn-8326-roadmap-report-dedup.md | 7 +++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 1 + .../core/src/__tests__/settings-parity.test.ts | 3 + packages/core/src/settings-schema.ts | 1 + packages/core/src/types.ts | 6 ++ packages/dashboard/app/components/ReportModal.tsx | 11 +++- .../app/components/__tests__/ReportModal.test.tsx | 14 +++++ .../__tests__/SettingsModal.general.test.tsx | 2 + .../settings/sections/GeneralSection.tsx | 10 ++++ .../settings-default-descriptions.test.tsx | 1 + .../src/__tests__/report-pipeline.test.ts | 31 +++++++++++ .../src/__tests__/report-roadmap-source.test.ts | 32 +++++++++++ .../dashboard/src/__tests__/report-routes.test.ts | 27 +++++++-- packages/dashboard/src/index.ts | 1 + packages/dashboard/src/report-pipeline.ts | 36 +++++++++++- packages/dashboard/src/report-roadmap-source.ts | 64 ++++++++++++++++++++++ .../dashboard/src/routes/register-report-routes.ts | 3 + packages/i18n/locales/en/app.json | 10 +++- packages/i18n/locales/es/app.json | 10 +++- packages/i18n/locales/fr/app.json | 10 +++- packages/i18n/locales/ko/app.json | 10 +++- packages/i18n/locales/zh-CN/app.json | 10 +++- packages/i18n/locales/zh-TW/app.json | 10 +++- .../src/__tests__/roadmap-store-factory.test.ts | 25 +++++++++ .../src/routes/roadmap-routes.ts | 24 +------- plugins/fusion-plugin-roadmap/src/server/index.ts | 34 ++++++++++++ 27 files changed, 360 insertions(+), 35 deletions(-) Fusion-Task-Id: FN-8326 Fusion-Task-Lineage: e9731c3f-dd92-44ac-910b-0e0efbaaf288 Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-8326-roadmap-report-dedup.md | 7 ++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 1 + .../src/__tests__/settings-parity.test.ts | 3 + packages/core/src/settings-schema.ts | 1 + packages/core/src/types.ts | 6 ++ .../dashboard/app/components/ReportModal.tsx | 11 +++- .../components/__tests__/ReportModal.test.tsx | 14 ++++ .../__tests__/SettingsModal.general.test.tsx | 2 + .../settings/sections/GeneralSection.tsx | 10 +++ .../settings-default-descriptions.test.tsx | 1 + .../src/__tests__/report-pipeline.test.ts | 31 +++++++++ .../__tests__/report-roadmap-source.test.ts | 32 ++++++++++ .../src/__tests__/report-routes.test.ts | 27 ++++++-- packages/dashboard/src/index.ts | 1 + packages/dashboard/src/report-pipeline.ts | 36 ++++++++++- .../dashboard/src/report-roadmap-source.ts | 64 +++++++++++++++++++ .../src/routes/register-report-routes.ts | 3 + packages/i18n/locales/en/app.json | 10 ++- packages/i18n/locales/es/app.json | 10 ++- packages/i18n/locales/fr/app.json | 10 ++- packages/i18n/locales/ko/app.json | 10 ++- packages/i18n/locales/zh-CN/app.json | 10 ++- packages/i18n/locales/zh-TW/app.json | 10 ++- .../__tests__/roadmap-store-factory.test.ts | 25 ++++++++ .../src/routes/roadmap-routes.ts | 24 +------ .../fusion-plugin-roadmap/src/server/index.ts | 34 ++++++++++ 27 files changed, 360 insertions(+), 35 deletions(-) create mode 100644 .changeset/fn-8326-roadmap-report-dedup.md create mode 100644 packages/dashboard/src/__tests__/report-roadmap-source.test.ts create mode 100644 packages/dashboard/src/report-roadmap-source.ts create mode 100644 plugins/fusion-plugin-roadmap/src/__tests__/roadmap-store-factory.test.ts diff --git a/.changeset/fn-8326-roadmap-report-dedup.md b/.changeset/fn-8326-roadmap-report-dedup.md new file mode 100644 index 0000000000..44667a622f --- /dev/null +++ b/.changeset/fn-8326-roadmap-report-dedup.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Let operators prevent duplicate in-app reports with optional roadmap matching. +category: feature +dev: Adds the project-scoped `reportRoadmapDedup` setting and local `roadmap-match` result. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 7d2c678f20..0e0af098a9 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -2090,6 +2090,8 @@ The Header **Report** menu is available on desktop and mobile and offers **Bug** 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. +Projects can opt into **Check roadmap before filing reports** in **Settings → General**. It is off by default. When enabled, the project roadmap is a third deduplication source: a strong feature match is shown inline as **Already on the roadmap**, including its title and description, and no Issue, Discussion, comment, or reaction is filed. The result intentionally has no roadmap link because Fusion does not expose a roadmap dashboard destination. + In **Settings → General**, choose **Review draft before filing** (the default) or **File automatically**. Both paths show the resulting issue, Discussion, or endorsement link. Help checks Fusion's local knowledge index on every server report path first and only escalates when it cannot find an answer. Reports include a default-on activity trace of up to 20 recent views, report actions, toast messages, and uncaught client errors (at most 4,000 characters). It is ordinary report text and receives the same server-side scrub as every other report field. You can optionally attach a screenshot of the current Fusion view. Fusion captures only the dashboard DOM, shows the exact preview, and requires confirmation before it uploads the PNG/JPEG (up to 2MB) as a local image artifact. In automatic mode this confirmation and local artifact upload happen before filing begins. The server validates the resulting artifact reference (format, image type, MIME type, and report-upload provenance) before it can appear as a text-only local-retention note in a report. Screenshot pixels are never sent to GitHub. diff --git a/docs/settings-reference.md b/docs/settings-reference.md index e2407cbabb..37b36e344e 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -655,6 +655,7 @@ Default notes: | `githubLinkImportedIssuesToTracking` | `boolean` | `false` | Project-scoped, import-only option. When enabled, GitHub issue imports from the dashboard, CLI, and extension tools persist `githubTracking: { enabled: true }` so Fusion adopts the imported source issue as the tracking issue without turning tracking on for ordinary new tasks. Duplicate/skipped imports do not create tasks or tracking metadata. | | `reportMode` | `"draft-review" \| "auto-file"` | `"draft-review"` | Default in-app Bug, Feedback, Idea, and Help report behavior. `"draft-review"` presents the scrubbed, structured draft for approval; `"auto-file"` files a new report or endorses a strong open duplicate automatically. Dashboard location: **Settings → Project → General → In-app report mode**. | | `reportModeByAction` | `Partial>` | `undefined` | Optional per-action override map. An action-specific value takes precedence over `reportMode`; unset actions use the project default. Every mode always scrubs report content before GitHub egress. | +| `reportRoadmapDedup` | `boolean` | `false` | Opt-in project roadmap deduplication for in-app reports. A strong roadmap feature match returns an inline `roadmap-match` result and performs no GitHub egress; off preserves Issues-and-Discussions-only behavior. Dashboard location: **Settings → Project → General → Check roadmap before filing reports**. | | `githubImportAutoTranslate` | `boolean` | `false` | Project-scoped, import-only option. When enabled, the Import Tasks panel automatically translates foreign-language **GitHub open issue** titles and bodies into `importTranslateTargetLocale` and shows the translation by default (the original text stays one toggle away). Translation follows each reachable page of the fetched list (up to the 300-issue fetch cap in one hour); cached repeat views do not spend model calls. Pull requests and GitLab retain per-selection translation. Translations persist across app restarts until the upstream issue changes or closes. Off by default so all-English projects never pay for a per-issue AI call. Issue-form template scaffolding (headings, field labels, placeholders, and checkboxes) is ignored when detecting an issue's language, so foreign-language answers remain eligible. Dashboard location: **Settings → Project → General → GitHub Tracking**. | | `importTranslateTargetLocale` | `Locale` | `undefined` | Target language for `githubImportAutoTranslate`. One of `SUPPORTED_LOCALES`. When unset, import translation follows the dashboard's own `language` setting. Dashboard location: **Settings → Project → General → GitHub Tracking**. | | `githubTrackingDefaultRepo` | `string` | `undefined` | Project default issue-tracking repo (`owner/repo`) used before global fallback for tracked task creation (precedence: task override → project default → global default). In Settings UI this is a detected-remote dropdown with a Custom fallback for manual entry. This key is dual-scope: project saves go through `PUT /api/settings` (Settings → General → GitHub Tracking) while global saves go through `PUT /api/settings/global` (Settings → Global General). | diff --git a/packages/core/src/__tests__/settings-parity.test.ts b/packages/core/src/__tests__/settings-parity.test.ts index 2bfdaaeb5c..63e3b2e44c 100644 --- a/packages/core/src/__tests__/settings-parity.test.ts +++ b/packages/core/src/__tests__/settings-parity.test.ts @@ -169,10 +169,13 @@ describe("settings key parity", () => { it("defaults in-app reports to draft review and keeps mode settings project-scoped", () => { expect(DEFAULT_PROJECT_SETTINGS.reportMode).toBe("draft-review"); expect(DEFAULT_PROJECT_SETTINGS.reportModeByAction).toBeUndefined(); + expect(DEFAULT_PROJECT_SETTINGS.reportRoadmapDedup).toBe(false); expect(isProjectSettingsKey("reportMode")).toBe(true); expect(isProjectSettingsKey("reportModeByAction")).toBe(true); + expect(isProjectSettingsKey("reportRoadmapDedup")).toBe(true); expect(isGlobalSettingsKey("reportMode")).toBe(false); expect(isGlobalSettingsKey("reportModeByAction")).toBe(false); + expect(isGlobalSettingsKey("reportRoadmapDedup")).toBe(false); }); it("defaults autoClaimCandidatesInPrompt to 5 and keeps it project-scoped", () => { diff --git a/packages/core/src/settings-schema.ts b/packages/core/src/settings-schema.ts index 0c0a52b2dd..28f5435544 100644 --- a/packages/core/src/settings-schema.ts +++ b/packages/core/src/settings-schema.ts @@ -640,6 +640,7 @@ export const DEFAULT_PROJECT_SETTINGS = { githubTrackingDefaultRepo: undefined, reportMode: "draft-review" as const, reportModeByAction: undefined, + reportRoadmapDedup: false, gitlabEnabled: undefined, gitlabInstanceUrl: undefined, gitlabApiBaseUrl: undefined, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 1da189eeff..c810657fc1 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -4088,6 +4088,12 @@ export interface ProjectSettings { */ reportMode?: ReportMode; reportModeByAction?: Partial>; + /** + * FNXC:ReportPipeline 2026-07-18-12:00: + * Roadmap deduplication is opt-in because projects without a roadmap must + * retain the existing Issues-and-Discussions-only report behavior. + */ + reportRoadmapDedup?: boolean; /** * FNXC:GitLabConfiguration 2026-07-02-00:00: * FN-7422 adds durable GitLab instance/API URL settings for GitLab.com and self-managed hosts. FN-7423 layers token settings onto the same project-over-global configuration contract without adding runtime GitLab imports or tracking. diff --git a/packages/dashboard/app/components/ReportModal.tsx b/packages/dashboard/app/components/ReportModal.tsx index a0612ebaae..f4fc315f2f 100644 --- a/packages/dashboard/app/components/ReportModal.tsx +++ b/packages/dashboard/app/components/ReportModal.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import type { ReportActionType } from "@fusion/core"; import { reportDraft, reportFile, reportHelp, uploadReportScreenshot } from "../api"; import { captureAppScreenshot, type CapturedScreenshot } from "../utils/capture-screenshot"; @@ -6,7 +7,7 @@ import { recordActivity, snapshotActivityTrace } from "../utils/activity-trace"; import "./ReportModal.css"; const prompts: Record = { bug: "What went wrong?", feedback: "What would you like to share?", idea: "What would you like Fusion to do?", help: "What would you like help with?" }; -type ModalResult = { kind: string; report?: { userPrompt: string; sourcePrompt?: string; summary?: string; body?: string; context?: Record; sessionToken?: string }; issue?: { number: number; url: string; title: string; discussionId?: string }; url?: string; answer?: { summary?: string; content?: string }; message?: string }; +type ModalResult = { kind: string; report?: { userPrompt: string; sourcePrompt?: string; summary?: string; body?: string; context?: Record; sessionToken?: string }; issue?: { number: number; url: string; title: string; discussionId?: string }; roadmap?: { featureId: string; title: string; description: string }; url?: string; answer?: { summary?: string; content?: string }; message?: string }; /** * FNXC:ReportPipeline 2026-07-16-12:00: @@ -14,6 +15,7 @@ type ModalResult = { kind: string; report?: { userPrompt: string; sourcePrompt?: * final filed or endorsed link remains visible after the selected mode acts. */ export function ReportModal({ actionType, onClose, contextRefs }: { actionType: ReportActionType; onClose: () => void; contextRefs?: { taskId?: string; agentId?: string } }) { + const { t } = useTranslation("app"); const [prompt, setPrompt] = useState(""); const [result, setResult] = useState(); const [busy, setBusy] = useState(false); @@ -107,6 +109,13 @@ export function ReportModal({ actionType, onClose, contextRefs }: { actionType: