From d08e8db2c3ef69bca28937a9ef2d635e51684c70 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 25 Jun 2026 01:05:06 -0700 Subject: [PATCH] FN-6932: repair dashboard copy assertions Repair dashboard copy and keep changed-only assertions aligned with current navigation behavior. - Restore TaskChat tool-call pluralization and inline result/error casing. - Replace JSX entity defaults in Settings helper copy with literal apostrophes. - Refresh dashboard tests for embedded navigation destinations and theme option parity. - Add a patch changeset for the dashboard copy repairs. Files changed: .changeset/fn-6932-dashboard-copy-repairs.md | 7 ++ packages/dashboard/app/components/TaskChatTab.tsx | 27 ++++++- .../app/components/__tests__/App.test.tsx | 94 +++++++++++++++++----- .../components/__tests__/ThemeSelector.test.tsx | 7 +- .../settings/sections/GlobalGeneralSection.tsx | 2 +- .../components/settings/sections/MergeSection.tsx | 12 +-- .../settings/sections/SchedulingSection.tsx | 2 +- .../settings/sections/WorktreesSection.tsx | 4 +- 8 files changed, 119 insertions(+), 36 deletions(-) Fusion-Task-Id: FN-6932 Fusion-Task-Lineage: 262f40e3-5a04-49c7-b2d4-8122d88ac488 --- .changeset/fn-6932-dashboard-copy-repairs.md | 7 ++ .../dashboard/app/components/TaskChatTab.tsx | 27 +++++- .../app/components/__tests__/App.test.tsx | 94 ++++++++++++++----- .../__tests__/ThemeSelector.test.tsx | 7 +- .../sections/GlobalGeneralSection.tsx | 2 +- .../settings/sections/MergeSection.tsx | 12 +-- .../settings/sections/SchedulingSection.tsx | 2 +- .../settings/sections/WorktreesSection.tsx | 4 +- 8 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 .changeset/fn-6932-dashboard-copy-repairs.md diff --git a/.changeset/fn-6932-dashboard-copy-repairs.md b/.changeset/fn-6932-dashboard-copy-repairs.md new file mode 100644 index 0000000000..1a88c01f2b --- /dev/null +++ b/.changeset/fn-6932-dashboard-copy-repairs.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Restore dashboard Settings helper copy and TaskChat tool-call labels. +category: fix +dev: Repairs changed-only dashboard assertions for navigation, pause routes, TaskChat, and theme selector parity. diff --git a/packages/dashboard/app/components/TaskChatTab.tsx b/packages/dashboard/app/components/TaskChatTab.tsx index ebd6904558..731a6b5cb6 100644 --- a/packages/dashboard/app/components/TaskChatTab.tsx +++ b/packages/dashboard/app/components/TaskChatTab.tsx @@ -244,14 +244,33 @@ function formatEntryLabel(entry: AgentLogEntry, t: TFunction<"app">): string { } } +/* +FNXC:TaskChat 2026-06-22-03:05: +Tool-call group copy intentionally preserves the pre-i18n grammar contract because the test i18n instance interpolates defaults without plural suffix resolution. +Keep plural branches in source for deterministic "1 tool call" / "N tool calls" and use lowercase completion labels only for the inline "Tool call → result/error" kicker; detail headers remain capitalized below. +*/ function formatCompletionLabel(entry: AgentLogEntry, t: TFunction<"app">): string { - return entry.type === "tool_error" ? t("taskChat.error", "Error") : t("taskChat.result", "Result"); + return entry.type === "tool_error" ? t("taskChat.errorInline", "error") : t("taskChat.resultInline", "result"); } const TOOL_NAME_SUMMARY_LIMIT = 5; function formatToolCallCount(count: number, t: TFunction<"app">): string { - return t("taskChat.toolCallCount", "{{count}} tool call", { count }); + return count === 1 + ? t("taskChat.toolCallCount", "{{count}} tool call", { count }) + : t("taskChat.toolCallCountPlural", "{{count}} tool calls", { count }); +} + +function formatErrorCount(count: number, t: TFunction<"app">): string { + return count === 1 + ? t("taskChat.errorCount", "{{count}} error", { count }) + : t("taskChat.errorCountPlural", "{{count}} errors", { count }); +} + +function formatEntryCount(count: number, t: TFunction<"app">): string { + return count === 1 + ? t("taskChat.entryCount", "{{count}} entry", { count }) + : t("taskChat.entryCountPlural", "{{count}} entries", { count }); } function getToolInvocationEntries(entries: AgentLogEntry[]): AgentLogEntry[] { @@ -414,7 +433,7 @@ function TaskChatToolGroup({ entries }: { entries: AgentLogEntry[] }) { ) : null} {errorCount > 0 ? ( - {t("taskChat.errorCount", "{{count}} error", { count: errorCount })} + {formatErrorCount(errorCount, t)} ) : null} @@ -825,7 +844,7 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated,
{item.label}
- {t("taskChat.entryCount", "{{count}} entry", { count: item.entries.length })} + {formatEntryCount(item.entries.length, t)} {relativeTime ? ( {relativeTime} diff --git a/packages/dashboard/app/components/__tests__/App.test.tsx b/packages/dashboard/app/components/__tests__/App.test.tsx index 5f5e70e398..390d12bb95 100644 --- a/packages/dashboard/app/components/__tests__/App.test.tsx +++ b/packages/dashboard/app/components/__tests__/App.test.tsx @@ -25,7 +25,12 @@ const defaultSettings: Settings = { buildCommand: "", capacityRiskBannerEnabled: false, capacityRiskTodoThreshold: 20, - experimentalFeatures: { insights: true, skillsView: true, agentsView: true, memoryView: true, evalsView: true }, + /* + * FNXC:DashboardTests 2026-06-22-03:38: + * App.test.tsx keeps legacy Header view-toggle coverage unless a test explicitly opts into the left-sidebar default. + * The product now enables leftSidebarNav by default, so the test fixture must set the flag false instead of accidentally hiding Header controls. + */ + experimentalFeatures: { insights: true, skillsView: true, agentsView: true, memoryView: true, evalsView: true, leftSidebarNav: false }, }; const mockAgentStats = { @@ -623,6 +628,22 @@ async function waitForAppShell(): Promise { beforeEach(() => { vi.clearAllMocks(); + /* + * FNXC:DashboardTests 2026-06-22-03:47: + * App.test.tsx runs beside other dashboard specs in the same Vitest process, so reset API mock implementations as well as call counts to prevent cross-file implementation leakage. + */ + vi.mocked(fetchSettings).mockResolvedValue({ ...defaultSettings }); + vi.mocked(updateSettings).mockResolvedValue({ ...defaultSettings }); + vi.mocked(fetchGlobalSettings).mockResolvedValue({ modelOnboardingComplete: true }); + vi.mocked(fetchAuthStatus).mockResolvedValue({ + providers: [ + { id: "anthropic", name: "Anthropic", authenticated: true }, + { id: "github", name: "GitHub", authenticated: true }, + ], + }); + vi.mocked(fetchModels).mockResolvedValue({ models: [], favoriteProviders: [], favoriteModels: [] }); + vi.mocked(fetchScripts).mockResolvedValue({ build: "npm run build", test: "pnpm test" }); + vi.mocked(runScript).mockResolvedValue({ sessionId: "sess-script-1", command: "echo hello" }); __resetShellHostContextForTests(); localStorage.clear(); mockSubscribeSse.mockReset(); @@ -1758,8 +1779,17 @@ describe("App mission wiring", () => { }); describe("App auto-open Settings on unauthenticated", () => { + beforeEach(() => { + vi.mocked(fetchAuthStatus).mockResolvedValue({ + providers: [ + { id: "anthropic", name: "Anthropic", authenticated: false }, + { id: "github", name: "GitHub", authenticated: false }, + ], + }); + }); + it("auto-opens onboarding modal when all providers are unauthenticated and onboarding not complete", async () => { - // fetchGlobalSettings returns {} by default (modelOnboardingComplete is undefined) + vi.mocked(fetchGlobalSettings).mockResolvedValue({}); render(); // Wait for the auth status check and global settings check @@ -1791,9 +1821,7 @@ describe("App auto-open Settings on unauthenticated", () => { await waitFor(() => expect(fetchSettings).toHaveBeenCalled()); // Authentication section should be active — auth status is fetched when section is active - await waitFor(() => { - expect(screen.getByText("Anthropic")).toBeTruthy(); - }); + expect(await screen.findByText("Anthropic")).toBeTruthy(); expect(screen.getByText("GitHub")).toBeTruthy(); // Onboarding modal should NOT be open @@ -1882,7 +1910,7 @@ describe("App auto-open Settings on unauthenticated", () => { }); it("re-opening Settings via gear icon defaults to Authentication tab after closing onboarding", async () => { - // fetchGlobalSettings returns {} by default → onboarding opens + vi.mocked(fetchGlobalSettings).mockResolvedValue({}); render(); // Wait for onboarding to auto-open @@ -1949,6 +1977,13 @@ describe("OnboardingResumeCard", () => { }); it("renders onboarding modal when in resumable state and modal is open", async () => { + vi.mocked(fetchGlobalSettings).mockResolvedValue({}); + vi.mocked(fetchAuthStatus).mockResolvedValue({ + providers: [ + { id: "anthropic", name: "Anthropic", authenticated: false }, + { id: "github", name: "GitHub", authenticated: false }, + ], + }); // Set up localStorage with resumable state localStorage.setItem( STORAGE_KEY, @@ -2459,7 +2494,7 @@ describe("App view switching", () => { // Override the default mock to exclude agentsView vi.mocked(fetchSettings).mockResolvedValue({ ...defaultSettings, - experimentalFeatures: { insights: true, skillsView: true }, // no agentsView + experimentalFeatures: { ...defaultSettings.experimentalFeatures, insights: true, skillsView: true }, // no agentsView }); render(); @@ -2620,7 +2655,7 @@ describe("App view switching", () => { it("keeps insights view button visible after graduation from experimental flags", async () => { (fetchSettings as ReturnType).mockResolvedValueOnce({ ...defaultSettings, - experimentalFeatures: { insights: false }, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, insights: false }, }); render(); @@ -2646,13 +2681,13 @@ describe("App view switching", () => { render(); - await waitFor(() => { - expect(screen.getByTestId("sidebar-nav-board")).toBeTruthy(); - }); + expect(screen.queryByTitle("Board view")).toBeNull(); + expect(document.querySelector(".insights-view")).toBeNull(); + expect(document.querySelector(".board")).toBeNull(); resolveSettings?.({ ...defaultSettings, - experimentalFeatures: {}, + experimentalFeatures: { leftSidebarNav: false }, }); await waitFor(() => { @@ -2666,7 +2701,7 @@ describe("App view switching", () => { it("keeps memory view button visible after graduation from experimental flags", async () => { (fetchSettings as ReturnType).mockResolvedValueOnce({ ...defaultSettings, - experimentalFeatures: { memoryView: false, insights: true }, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, memoryView: false, insights: true }, }); render(); @@ -2682,7 +2717,7 @@ describe("App view switching", () => { localStorage.setItem(taskViewStorageKey(), "memory"); (fetchSettings as ReturnType).mockResolvedValueOnce({ ...defaultSettings, - experimentalFeatures: { memoryView: false }, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, memoryView: false }, }); render(); @@ -2703,7 +2738,7 @@ describe("App view switching", () => { localStorage.setItem(taskViewStorageKey(), "goalsView"); (fetchSettings as ReturnType).mockResolvedValueOnce({ ...defaultSettings, - experimentalFeatures: { goalsView: true }, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, goalsView: true }, }); render(); @@ -2719,7 +2754,7 @@ describe("App view switching", () => { localStorage.setItem(taskViewStorageKey(), "goalsView"); (fetchSettings as ReturnType).mockResolvedValueOnce({ ...defaultSettings, - experimentalFeatures: { goalsView: false }, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, goalsView: false }, }); render(); @@ -2775,6 +2810,11 @@ describe("App GitHub import", () => { describe("App Planning Mode", () => { it("opens Planning Mode as an embedded view from the sidebar destination", async () => { + localStorage.setItem("kb-dashboard-view-mode", "project"); + vi.mocked(fetchSettings).mockResolvedValueOnce({ + ...defaultSettings, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, leftSidebarNav: true }, + }); render(); const planningNavItem = await screen.findByTestId("sidebar-nav-planning"); @@ -2788,6 +2828,11 @@ describe("App Planning Mode", () => { }); it("closes Planning Mode embedded view back to the board", async () => { + localStorage.setItem("kb-dashboard-view-mode", "project"); + vi.mocked(fetchSettings).mockResolvedValueOnce({ + ...defaultSettings, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, leftSidebarNav: true }, + }); render(); fireEvent.click(await screen.findByTestId("sidebar-nav-planning")); @@ -4070,6 +4115,9 @@ describe("App board branch filters", () => { }); it("composes with search and does not affect list view tasks", async () => { + localStorage.setItem("kb-dashboard-view-mode", "project"); + localStorage.setItem(taskViewStorageKey(), "board"); + vi.mocked(fetchSettings).mockResolvedValue({ ...defaultSettings }); mockUseTasks.mockImplementation(() => ({ tasks: [ makeTask("FN-4", "Alpha Search", "feature/a", "main"), @@ -4216,8 +4264,14 @@ describe("App shell connection status plumbing", () => { expect(screen.queryByTestId("shell-connection-status-button")).toBeNull(); }); - it("renders shell connection status for mobile shell host in mobile More sheet only", async () => { + it("keeps shell connection status out of compact mobile header chrome", async () => { mockUseViewportMode.mockReturnValue("mobile"); + localStorage.setItem("kb-dashboard-view-mode", "project"); + vi.mocked(fetchSettings).mockResolvedValueOnce({ + ...defaultSettings, + experimentalFeatures: { ...defaultSettings.experimentalFeatures, leftSidebarNav: true }, + }); + vi.mocked(fetchGlobalSettings).mockResolvedValueOnce({ modelOnboardingComplete: true }); mockShellHostContextValue.host = { kind: "mobile-shell", mode: "remote", connectionId: "p1", serverUrl: "https://fusion.example.com" }; mockGetShellConnectionNativeResult.mockResolvedValueOnce({ hostKind: "mobile-shell", @@ -4232,13 +4286,11 @@ describe("App shell connection status plumbing", () => { await waitFor(() => { expect(mockGetShellConnectionNativeResult).toHaveBeenCalledWith(mockShellHostContextValue.host); - expect(screen.getByTestId("mobile-nav-tab-more")).toBeInTheDocument(); + expect(screen.getByTestId("mobile-view-toggle")).toBeInTheDocument(); }); expect(screen.queryByTestId("shell-connection-status-button")).toBeNull(); - fireEvent.click(screen.getByTestId("mobile-nav-tab-more")); - expect(screen.getAllByTestId("shell-connection-status-button")).toHaveLength(1); - expect(screen.getByTestId("mobile-more-shell-connection")).toBeInTheDocument(); + expect(screen.queryByTestId("mobile-more-shell-connection")).toBeNull(); }); it("keeps desktop shell connection status in header and out of mobile sheet", async () => { diff --git a/packages/dashboard/app/components/__tests__/ThemeSelector.test.tsx b/packages/dashboard/app/components/__tests__/ThemeSelector.test.tsx index 9547525716..2e1a27de48 100644 --- a/packages/dashboard/app/components/__tests__/ThemeSelector.test.tsx +++ b/packages/dashboard/app/components/__tests__/ThemeSelector.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, fireEvent, within } from "@testing-library/react"; import { COLOR_THEMES } from "@fusion/core"; import { ThemeSelector } from "../ThemeSelector"; import { COLOR_THEMES as THEME_OPTIONS } from "../themeOptions"; @@ -81,10 +81,15 @@ describe("ThemeSelector", () => { // FNXC:Theme 2026-06-22-09:30: Assert the accessibility invariant — every theme in the // shared COLOR_THEMES list renders an accessibly-labeled option — instead of a frozen // hardcoded label list that drifts whenever themes are renamed/added (e.g. FN-6813 mono variants). + const colorThemeGroup = screen.getByRole("radiogroup", { name: "Color theme" }); + const themeButtons = within(colorThemeGroup).getAllByRole("button"); + + expect(themeButtons).toHaveLength(THEME_OPTIONS.length); for (const theme of THEME_OPTIONS) { expect(screen.getByLabelText(`${theme.label} theme`)).toBeDefined(); } expect(THEME_OPTIONS.map((theme) => theme.value)).toEqual([...COLOR_THEMES]); + expect(screen.getByLabelText("Default theme").getAttribute("aria-pressed")).toBe("true"); }); it("renders every shared swatch class from themeOptions", () => { diff --git a/packages/dashboard/app/components/settings/sections/GlobalGeneralSection.tsx b/packages/dashboard/app/components/settings/sections/GlobalGeneralSection.tsx index cc1ede8c6b..ccd9ee6556 100644 --- a/packages/dashboard/app/components/settings/sections/GlobalGeneralSection.tsx +++ b/packages/dashboard/app/components/settings/sections/GlobalGeneralSection.tsx @@ -39,7 +39,7 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalTrackin setForm((f) => ({ ...f, fnBinaryCheckEnabled: e.target.checked }))}/>{t("settings.globalGeneral.checkForThe", " Check for the ")}fn{t("settings.globalGeneral.cLIBinaryOnPATH", " CLI binary on PATH ")} {t("settings.globalGeneral.whenEnabledTheDashboardProbesForAGlobally", " When enabled, the dashboard probes for a globally-installed")}{" "} fn / fusion{t("settings.globalGeneral.cLIBySpawning", " CLI by spawning")}{" "} - <bin> --version{t("settings.globalGeneral.disableThisIfYourLocalDevProcessIs", ". Disable this if your local dev process is the source of truth and you don't want any outdated globally-installed binary executed during the probe. ")} + <bin> --version{t("settings.globalGeneral.disableThisIfYourLocalDevProcessIs", ". Disable this if your local dev process is the source of truth and you don't want any outdated globally-installed binary executed during the probe. ")}

{t("settings.globalGeneral.updates", "Updates")}

diff --git a/packages/dashboard/app/components/settings/sections/MergeSection.tsx b/packages/dashboard/app/components/settings/sections/MergeSection.tsx index 44e30a3040..e558b846d4 100644 --- a/packages/dashboard/app/components/settings/sections/MergeSection.tsx +++ b/packages/dashboard/app/components/settings/sections/MergeSection.tsx @@ -129,7 +129,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.aIModeMergesTheTaskBranchIntoAn", " AI mode merges the task branch into an isolated clean-room checkout at the target branch's tip, has an AI reviewer audit the squash (with corrective retries \u2014 advisory concerns land with a logged warning, an unfixable correctness concern hard-fails), then fast-forwards the target branch and syncs your local checkout (AI reconciles a conflicting restore). Each task merges to its own target branch, or the default integration branch. ")}{t("settings.merge.theLegacyMergeSettingsBelowDoNotApply", "The legacy merge settings below do not apply while AI merge is on.")} + {t("settings.merge.aIModeMergesTheTaskBranchIntoAn", " AI mode merges the task branch into an isolated clean-room checkout at the target branch's tip, has an AI reviewer audit the squash (with corrective retries \u2014 advisory concerns land with a logged warning, an unfixable correctness concern hard-fails), then fast-forwards the target branch and syncs your local checkout (AI reconciles a conflicting restore). Each task merges to its own target branch, or the default integration branch. ")}{t("settings.merge.theLegacyMergeSettingsBelowDoNotApply", "The legacy merge settings below do not apply while AI merge is on.")}
@@ -137,7 +137,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
setForm((f) => ({ ...f, merger: { ...(f.merger ?? {}), maxReviewPasses: e.target.value === "" ? undefined : Number(e.target.value) } }))}/> - {t("settings.merge.aICorrectiveRoundsBeforeLandingTheBestResult", "AI corrective rounds before landing the best result (advisory concern) or hard-failing (unfixable correctness concern). Default 3. The reviewer uses your project's reviewer/validator model.")} + {t("settings.merge.aICorrectiveRoundsBeforeLandingTheBestResult", "AI corrective rounds before landing the best result (advisory concern) or hard-failing (unfixable correctness concern). Default 3. The reviewer uses your project's reviewer/validator model.")}
{form.mergeStrategy !== "pull-request" && (form.merger?.mode ?? "ai") !== "ai" && (<> @@ -230,7 +230,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.autoKeepsTodayAposSSquashBehaviorFor", " Auto keeps today's squash behavior for branches with zero or one substantive commit, but switches multi-substantive branches to a history-preserving rebase-and-merge path. Individual tasks can override this in PROMPT.md with ")}**Direct Merge Commit Strategy:** auto|always-squash|always-rebase. + {t("settings.merge.autoKeepsTodayAposSSquashBehaviorFor", " Auto keeps today's squash behavior for branches with zero or one substantive commit, but switches multi-substantive branches to a history-preserving rebase-and-merge path. Individual tasks can override this in PROMPT.md with ")}**Direct Merge Commit Strategy:** auto|always-squash|always-rebase.
@@ -245,7 +245,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti {t("settings.merge.autoMergeRunsInTheTaskWorktreeBy", " Auto-merge runs in the task worktree by default. Switch to the legacy project-root path only if you need the pre-FN-5279 fallback; worktrunk-managed projects still defer to worktrunk. ")} {(form.mergeIntegrationWorktree ?? "reuse-task-worktree") !== "reuse-task-worktree" && (
- {t("settings.merge.legacyIntegrationBranchMode", "Legacy integration-branch mode.")}{" "}{t("settings.merge.autoMergeWillRunRebaseConflictResolutionAnd", " Auto-merge will run rebase, conflict resolution, and squash commits inside the project root (the user's checked-out integration-branch worktree) instead of the task worktree. Fusion assumes that directory is already on the integration branch and clean; if it isn't, merges may fail or touch the user's working tree. Reuse-task-worktree is the recommended default (FN-5279). Switch back unless you have a specific reason to opt in (FN-5348). ")}
)} + {t("settings.merge.legacyIntegrationBranchMode", "Legacy integration-branch mode.")}{" "}{t("settings.merge.autoMergeWillRunRebaseConflictResolutionAnd", " Auto-merge will run rebase, conflict resolution, and squash commits inside the project root (the user's checked-out integration-branch worktree) instead of the task worktree. Fusion assumes that directory is already on the integration branch and clean; if it isn't, merges may fail or touch the user's working tree. Reuse-task-worktree is the recommended default (FN-5279). Switch back unless you have a specific reason to opt in (FN-5348). ")})}
@@ -370,7 +370,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti - {t("settings.merge.controlsThePostMergeAuditGate", " Controls the post-merge audit gate. ")}{t("settings.merge.warn", "Warn")}{t("settings.merge.defaultLogsFindingsButAutoCompletesTheMerge", " (default) logs findings but auto-completes the merge. ")}{t("settings.merge.block", "Block")}{t("settings.merge.isTheStricterOptInModeThatRefuses", " is the stricter opt-in mode that refuses to auto-complete merges with duplicate-subject or touched-file overlap risks. ")}{t("settings.merge.off", "Off")}{t("settings.merge.skipsTheAuditEntirelySwitchingToOffIs", " skips the audit entirely. Switching to Off is recommended only if you trust your branches don't silently drop edits. ")} + {t("settings.merge.controlsThePostMergeAuditGate", " Controls the post-merge audit gate. ")}{t("settings.merge.warn", "Warn")}{t("settings.merge.defaultLogsFindingsButAutoCompletesTheMerge", " (default) logs findings but auto-completes the merge. ")}{t("settings.merge.block", "Block")}{t("settings.merge.isTheStricterOptInModeThatRefuses", " is the stricter opt-in mode that refuses to auto-complete merges with duplicate-subject or touched-file overlap risks. ")}{t("settings.merge.off", "Off")}{t("settings.merge.skipsTheAuditEntirelySwitchingToOffIs", " skips the audit entirely. Switching to Off is recommended only if you trust your branches don't silently drop edits. ")}
)}
diff --git a/packages/dashboard/app/components/settings/sections/SchedulingSection.tsx b/packages/dashboard/app/components/settings/sections/SchedulingSection.tsx index 3b2e69e160..df2e859ad9 100644 --- a/packages/dashboard/app/components/settings/sections/SchedulingSection.tsx +++ b/packages/dashboard/app/components/settings/sections/SchedulingSection.tsx @@ -82,7 +82,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr const num = Number(val); setForm((f) => ({ ...f, taskStuckTimeoutMs: val && num > 0 ? num * 60000 : undefined })); }}/> - {t("settings.scheduling.timeoutInMinutesForDetectingStuckTasksWhen", "Timeout in minutes for detecting stuck tasks. When a task's agent session shows no activity for longer than this duration, the task is terminated and retried. Leave empty to disable. Suggested: 10.")} + {t("settings.scheduling.timeoutInMinutesForDetectingStuckTasksWhen", "Timeout in minutes for detecting stuck tasks. When a task's agent session shows no activity for longer than this duration, the task is terminated and retried. Leave empty to disable. Suggested: 10.")}
diff --git a/packages/dashboard/app/components/settings/sections/WorktreesSection.tsx b/packages/dashboard/app/components/settings/sections/WorktreesSection.tsx index d1778f8e67..e8509a2cee 100644 --- a/packages/dashboard/app/components/settings/sections/WorktreesSection.tsx +++ b/packages/dashboard/app/components/settings/sections/WorktreesSection.tsx @@ -97,7 +97,7 @@ export function WorktreesSection({ scopeBanner, form, setForm, gitRemotes, workt onFailure: f.worktrunk?.onFailure ?? "fail", }, }))}/>{t("settings.worktrees.enableWorktrunkIntegration", " Enable worktrunk integration ")} - {t("settings.worktrees.disabledByDefaultOptInWhenEnabledFusion", " Disabled by default (opt-in). When enabled, Fusion shells out to ")}worktrunk{t("settings.worktrees.forWorktreeCreateSyncPruneAndRemoveOperations", " for worktree create, sync, prune, and remove operations and follows worktrunk's directory layout. ")} + {t("settings.worktrees.disabledByDefaultOptInWhenEnabledFusion", " Disabled by default (opt-in). When enabled, Fusion shells out to ")}worktrunk{t("settings.worktrees.forWorktreeCreateSyncPruneAndRemoveOperations", " for worktree create, sync, prune, and remove operations and follows worktrunk's directory layout. ")} {!worktrunkInstallVerified && form.worktrunk?.enabled !== true && ({t("settings.worktrees.installTheWorktrunkBinaryBelowToEnableThis", "Install the worktrunk binary below to enable this integration.")})}
@@ -148,7 +148,7 @@ export function WorktreesSection({ scopeBanner, form, setForm, gitRemotes, workt - fail{t("settings.worktrees.stopsOnWorktrunkErrorsForExplicitOperatorRecovery", " stops on worktrunk errors for explicit operator recovery; ")}fallback-native{t("settings.worktrees.keepsProgressMovingBySwitchingToFusionApos", " keeps progress moving by switching to Fusion's built-in worktree backend. ")} + fail{t("settings.worktrees.stopsOnWorktrunkErrorsForExplicitOperatorRecovery", " stops on worktrunk errors for explicit operator recovery; ")}fallback-native{t("settings.worktrees.keepsProgressMovingBySwitchingToFusionApos", " keeps progress moving by switching to Fusion's built-in worktree backend. ")}
); }