From 4baa4c43c55cabd8facd62886aa4302df156f28c Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 4 Jul 2026 13:09:29 -0700 Subject: [PATCH] FN-7505: show defaults in settings descriptions Expose settings defaults directly in dashboard help text so operators can see baseline values while editing settings. - Add default-value wording across global and project settings descriptions and locale strings. - Document the dashboard/default-reference alignment and add a changeset for the published CLI package. - Add coverage that every visible settings field includes its default in helper text and update existing assertions. Files changed: .changeset/fn-7505-settings-default-descriptions.md | 7 + docs/dashboard-guide.md | 3 + docs/settings-reference.md | 3 + .../__tests__/SettingsModal.general.test.tsx | 2 +- .../settings/sections/AgentPermissionsSection.tsx | 4 +- .../settings/sections/AppearanceSection.tsx | 4 +- .../settings/sections/BackupsSection.tsx | 13 +- .../settings/sections/CommandsSection.tsx | 4 +- .../settings/sections/ExperimentalSection.tsx | 2 +- .../settings/sections/GeneralSection.tsx | 26 +- .../settings/sections/GlobalGeneralSection.tsx | 25 +- .../settings/sections/GlobalModelsSection.tsx | 24 +- .../settings/sections/McpServersCard.tsx | 1 + .../components/settings/sections/MemorySection.tsx | 12 +- .../components/settings/sections/MergeSection.tsx | 37 +- .../settings/sections/ModelPricingSection.tsx | 2 +- .../settings/sections/NodeRoutingSection.tsx | 3 +- .../settings/sections/NodeSyncSection.tsx | 6 +- .../settings/sections/NotificationsSection.tsx | 14 +- .../settings/sections/ProjectModelsSection.tsx | 11 +- .../settings/sections/PromptsSection.tsx | 2 +- .../components/settings/sections/RemoteSection.tsx | 7 +- .../settings/sections/ResearchGlobalSection.tsx | 15 +- .../settings/sections/ResearchProjectSection.tsx | 16 +- .../settings/sections/ScheduledEvalsSection.tsx | 7 +- .../settings/sections/SchedulingSection.tsx | 20 +- .../settings/sections/WorktreesSection.tsx | 14 +- .../sections/__tests__/AppearanceSection.test.tsx | 4 +- .../MergeSection.legacy-automerge-cleanup.test.tsx | 2 +- .../settings-default-descriptions.test.tsx | 573 +++++++++++++++++++++ packages/i18n/locales/en/app.json | 261 ++++++---- 31 files changed, 914 insertions(+), 210 deletions(-) Fusion-Task-Id: FN-7505 Fusion-Task-Lineage: 7688caf2-2a95-4401-8b27-9da4b46ecfcd Co-authored-by: Fusion (runfusion.ai) --- .../fn-7505-settings-default-descriptions.md | 7 + docs/dashboard-guide.md | 3 + docs/settings-reference.md | 3 + .../__tests__/SettingsModal.general.test.tsx | 2 +- .../sections/AgentPermissionsSection.tsx | 4 +- .../settings/sections/AppearanceSection.tsx | 4 +- .../settings/sections/BackupsSection.tsx | 13 +- .../settings/sections/CommandsSection.tsx | 4 +- .../settings/sections/ExperimentalSection.tsx | 2 +- .../settings/sections/GeneralSection.tsx | 26 +- .../sections/GlobalGeneralSection.tsx | 25 +- .../settings/sections/GlobalModelsSection.tsx | 24 +- .../settings/sections/McpServersCard.tsx | 1 + .../settings/sections/MemorySection.tsx | 12 +- .../settings/sections/MergeSection.tsx | 37 +- .../settings/sections/ModelPricingSection.tsx | 2 +- .../settings/sections/NodeRoutingSection.tsx | 3 +- .../settings/sections/NodeSyncSection.tsx | 6 +- .../sections/NotificationsSection.tsx | 14 +- .../sections/ProjectModelsSection.tsx | 11 +- .../settings/sections/PromptsSection.tsx | 2 +- .../settings/sections/RemoteSection.tsx | 7 +- .../sections/ResearchGlobalSection.tsx | 15 +- .../sections/ResearchProjectSection.tsx | 16 +- .../sections/ScheduledEvalsSection.tsx | 7 +- .../settings/sections/SchedulingSection.tsx | 20 +- .../settings/sections/WorktreesSection.tsx | 14 +- .../__tests__/AppearanceSection.test.tsx | 4 +- ...eSection.legacy-automerge-cleanup.test.tsx | 2 +- .../settings-default-descriptions.test.tsx | 573 ++++++++++++++++++ packages/i18n/locales/en/app.json | 261 +++++--- 31 files changed, 914 insertions(+), 210 deletions(-) create mode 100644 .changeset/fn-7505-settings-default-descriptions.md create mode 100644 packages/dashboard/app/components/settings/sections/__tests__/settings-default-descriptions.test.tsx diff --git a/.changeset/fn-7505-settings-default-descriptions.md b/.changeset/fn-7505-settings-default-descriptions.md new file mode 100644 index 0000000000..23bf9f718c --- /dev/null +++ b/.changeset/fn-7505-settings-default-descriptions.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Settings descriptions now show each setting's default value. +category: feature +dev: Appended default-value copy to settings.* i18n descriptions across Global, Runtimes, and Project Settings sections, sourced from DEFAULT_GLOBAL_SETTINGS/DEFAULT_PROJECT_SETTINGS in settings-schema.ts; added settings-default-descriptions.test.tsx guarding that every surfaced setting states a default (or explicit "inherits"/"no default \u2014 unset") and that every DEFAULT_SETTINGS key is documented or allowlisted as not surfaced. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 0b7f2745c6..ab3d1f9908 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -13,6 +13,9 @@ When Fusion detects a newer `@runfusion/fusion` release, the Settings modal foot Use **Search settings** at the top of Settings to find the section that contains a setting by name or keyword. The same search works in the Settings modal and embedded Settings page, filters both the desktop section list and mobile section picker, and only searches sections currently visible for enabled feature flags. + +Every user-editable setting's help text (the `.settings-description`/`` hint under a field) states its own default value — for example “Default: 3.”, “Default: enabled.”, or “No default — unset (inherits the global setting).” for values that fall back to another scope. Canonical default values come from `DEFAULT_GLOBAL_SETTINGS` / `DEFAULT_PROJECT_SETTINGS` in `packages/core/src/settings-schema.ts`; the dashboard copy never invents a number. A guard test (`settings-default-descriptions.test.tsx`) enforces that every surfaced setting states its default and that every `DEFAULT_SETTINGS` key is either documented or explicitly allowlisted as not surfaced in the Settings UI. + ## Keyboard shortcuts diff --git a/docs/settings-reference.md b/docs/settings-reference.md index ebf2f4382a..2cdf5b1d78 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -13,6 +13,9 @@ Fusion uses a two-tier settings system: At runtime, settings are merged. **Project settings override global settings** when keys overlap. + +The `Default` column below is the same source of truth (`DEFAULT_GLOBAL_SETTINGS` / `DEFAULT_PROJECT_SETTINGS` in `packages/core/src/settings-schema.ts`) that the dashboard Settings UI now surfaces inline in each field's own description/help text (see `docs/dashboard-guide.md` → Settings discovery). + ## Settings API Endpoints | Endpoint | Purpose | diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx index 05b389086a..20e025ef7e 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx @@ -588,7 +588,7 @@ describe("SettingsModal", () => { // Global modal outside-dismiss and persistAgentToolOutput default to unchecked; Star-on-GitHub control absent. expect(screen.getByRole("checkbox", { name: "Dismiss modals by clicking outside" })).not.toBeChecked(); - expect(screen.getByText(/Off by default to prevent accidental dismissal/i).closest("small")).toBeTruthy(); + expect(screen.getByText(/Default: disabled, to prevent accidental dismissal/i).closest("small")).toBeTruthy(); expect(screen.getByRole("checkbox", { name: "Save tool output in agent logs" })).not.toBeChecked(); expect(screen.queryByRole("checkbox", { name: /Show "Star on GitHub" button in Settings header/i })).toBeNull(); diff --git a/packages/dashboard/app/components/settings/sections/AgentPermissionsSection.tsx b/packages/dashboard/app/components/settings/sections/AgentPermissionsSection.tsx index 7caac382a8..daa68e7113 100644 --- a/packages/dashboard/app/components/settings/sections/AgentPermissionsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/AgentPermissionsSection.tsx @@ -20,7 +20,7 @@ export function AgentPermissionsSection({ scopeBanner, form, setForm }: AgentPer {scopeBanner}

{t("settings.agentPermissions.agentPermissions", "Agent Permissions")}

- {t("settings.agentPermissions.perAgentSettingsOverrideProjectDefaultsEachCategory", "Project defaults apply to permanent agents, ephemeral task workers, and fallback executor workers unless a per-agent override is set. Exact tool rules compose with the legacy ephemeral create-task toggle.")} + {t("settings.agentPermissions.perAgentSettingsOverrideProjectDefaultsEachCategory", "Project defaults apply to permanent agents, ephemeral task workers, and fallback executor workers unless a per-agent override is set. Exact tool rules compose with the legacy ephemeral create-task toggle. Default: unset \u2014 every action category defaults to allow until a category is explicitly restricted.")}
setForm((f) => ({ ...f, @@ -29,7 +29,7 @@ export function AgentPermissionsSection({ scopeBanner, form, setForm }: AgentPer

{t("settings.agentPermissions.agentProvisioningApprovals", "Agent Provisioning Approvals")}

- {t("settings.agentPermissions.configureProjectLevelApprovalBehaviorForDurableProvisioning", " Configure project-level approval behavior for durable provisioning tools (fn_agent_create/fn_agent_delete). ")} + {t("settings.agentPermissions.configureProjectLevelApprovalBehaviorForDurableProvisioning", " Configure project-level approval behavior for durable provisioning tools (fn_agent_create/fn_agent_delete). Default: no approval policy configured (empty). ")}
setForm((f) => ({ ...f, agentProvisioning: next }))}/> ); diff --git a/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx b/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx index b6c56b668a..f32e4c3944 100644 --- a/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx +++ b/packages/dashboard/app/components/settings/sections/AppearanceSection.tsx @@ -42,7 +42,7 @@ export function AppearanceSection({ scopeBanner, form, setForm, themeMode, color setForm((f) => ({ ...f, openTasksInRightSidebar: e.target.checked }))}/> {t("settings.appearance.openTasksInRightSidebar", "Open tasks in the right sidebar")} - {t("settings.appearance.openTasksInRightSidebarHelp", "When enabled, board task cards open detail in the right sidebar when it is available; mobile and hidden-sidebar states keep the full task panel.")} + {t("settings.appearance.openTasksInRightSidebarHelp", "When enabled, board task cards open detail in the right sidebar when it is available; mobile and hidden-sidebar states keep the full task panel. Default: disabled.")}
{/* FNXC:MobileTaskPopups 2026-07-01-12:00: Keep the stored openMobileTasksInPopup key for compatibility, but present the setting as all-viewport ordinary task popup routing because desktop operators also need the board or right-dock Tasks list visible behind task detail. */} @@ -50,7 +50,7 @@ export function AppearanceSection({ scopeBanner, form, setForm, themeMode, color setForm((f) => ({ ...f, openMobileTasksInPopup: e.target.checked }))}/> {t("settings.appearance.openMobileTasksInPopup", "Open tasks as popups")} - {t("settings.appearance.openMobileTasksInPopupHelp", "When enabled, ordinary board task-card and right-dock Tasks-list clicks open the existing task popup so the board or list remains visible. Deep-tab and other task opens keep their current behavior.")} + {t("settings.appearance.openMobileTasksInPopupHelp", "When enabled, ordinary board task-card and right-dock Tasks-list clicks open the existing task popup so the board or list remains visible. Deep-tab and other task opens keep their current behavior. Default: disabled.")}
{/* FNXC:TaskDetailActivityFirst 2026-06-30-23:59: The project setting is opt-in because task details now default to Activity-first; explicit Activity/Chat/Logs links keep their destination regardless of this checkbox. */} diff --git a/packages/dashboard/app/components/settings/sections/BackupsSection.tsx b/packages/dashboard/app/components/settings/sections/BackupsSection.tsx index 2f10c64874..d0eaee7b4f 100644 --- a/packages/dashboard/app/components/settings/sections/BackupsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/BackupsSection.tsx @@ -17,7 +17,7 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL
- {t("settings.backups.whenEnabledTheDatabaseIsBackedUpAutomatically", "When enabled, the database is backed up automatically on a schedule")} + {t("settings.backups.whenEnabledTheDatabaseIsBackedUpAutomatically", "When enabled, the database is backed up automatically on a schedule. Default: disabled.")}
@@ -31,13 +31,13 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL const val = e.target.value; setForm((f) => ({ ...f, autoBackupRetention: val === "" ? undefined : Number(val) })); }} disabled={!form.autoBackupEnabled}/> - {t("settings.backups.numberOfBackupFilesToKeepOldestAre", "Number of backup files to keep (oldest are deleted first). Range: 1-100.")} + {t("settings.backups.numberOfBackupFilesToKeepOldestAre", "Number of backup files to keep (oldest are deleted first). Range: 1-100. Default: 7.")} {form.autoBackupRetention !== undefined && (form.autoBackupRetention < 1 || form.autoBackupRetention > 100) && ({t("settings.backups.mustBeBetween1And100", "Must be between 1 and 100")})}
setForm((f) => ({ ...f, autoBackupDir: e.target.value }))} disabled={!form.autoBackupEnabled}/> - {t("settings.backups.directoryForBackupFilesRelativeToProjectRoot", "Directory for backup files, relative to project root")} + {t("settings.backups.directoryForBackupFilesRelativeToProjectRoot", "Directory for backup files, relative to project root. Default: .fusion/backups.")} {form.autoBackupDir && form.autoBackupDir.includes("..") && ({t("settings.backups.pathCannotContainParentDirectoryTraversal", "Path cannot contain parent directory traversal (..)")})}
@@ -45,7 +45,7 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL
- {t("settings.backups.whenEnabledProjectAndAgentMemoryFilesAre", "When enabled, project and agent memory files are backed up automatically on a schedule.")} + {t("settings.backups.whenEnabledProjectAndAgentMemoryFilesAre", "When enabled, project and agent memory files are backed up automatically on a schedule. Default: disabled.")}
@@ -59,13 +59,13 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL const val = e.target.value; setForm((f) => ({ ...f, memoryBackupRetention: val === "" ? undefined : Number(val) })); }} disabled={!form.memoryBackupEnabled}/> - {t("settings.backups.numberOfMemoryBackupsToKeepOldestAre", "Number of memory backups to keep (oldest are deleted first). Range: 1-100.")} + {t("settings.backups.numberOfMemoryBackupsToKeepOldestAre", "Number of memory backups to keep (oldest are deleted first). Range: 1-100. Default: 14.")} {form.memoryBackupRetention !== undefined && (form.memoryBackupRetention < 1 || form.memoryBackupRetention > 100) && ({t("settings.backups.mustBeBetween1And100", "Must be between 1 and 100")})}
setForm((f) => ({ ...f, memoryBackupDir: e.target.value }))} disabled={!form.memoryBackupEnabled}/> - {t("settings.backups.directoryForMemoryBackupsRelativeToProjectRoot", "Directory for memory backups, relative to project root.")} + {t("settings.backups.directoryForMemoryBackupsRelativeToProjectRoot", "Directory for memory backups, relative to project root. Default: .fusion/backups/memory.")} {form.memoryBackupDir && form.memoryBackupDir.includes("..") && ({t("settings.backups.pathCannotContainParentDirectoryTraversal", "Path cannot contain parent directory traversal (..)")})}
@@ -75,6 +75,7 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL + {t("settings.backups.memoryBackupScopeHint", "Default: all (project + agents).")}
{backupLoading ? (
) : backupInfo ? (
diff --git a/packages/dashboard/app/components/settings/sections/CommandsSection.tsx b/packages/dashboard/app/components/settings/sections/CommandsSection.tsx index 64d0e29e68..5ad137657d 100644 --- a/packages/dashboard/app/components/settings/sections/CommandsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/CommandsSection.tsx @@ -12,12 +12,12 @@ export function CommandsSection({ scopeBanner, form, setForm }: CommandsSectionP
setForm((f) => ({ ...f, testCommand: e.target.value || undefined }))}/> - {t("settings.commands.commandUsedToRunTestsInjectedIntoGenerated", "Command used to run tests \u2014 injected into generated task specs")} + {t("settings.commands.commandUsedToRunTestsInjectedIntoGenerated", "Command used to run tests \u2014 injected into generated task specs. No default \u2014 unset.")}
setForm((f) => ({ ...f, buildCommand: e.target.value || undefined }))}/> - {t("settings.commands.commandUsedToBuildTheProjectInjectedInto", "Command used to build the project \u2014 injected into generated task specs")} + {t("settings.commands.commandUsedToBuildTheProjectInjectedInto", "Command used to build the project \u2014 injected into generated task specs. No default \u2014 unset.")}
); } diff --git a/packages/dashboard/app/components/settings/sections/ExperimentalSection.tsx b/packages/dashboard/app/components/settings/sections/ExperimentalSection.tsx index 2c2eb2651d..a3fd6157d7 100644 --- a/packages/dashboard/app/components/settings/sections/ExperimentalSection.tsx +++ b/packages/dashboard/app/components/settings/sections/ExperimentalSection.tsx @@ -26,7 +26,7 @@ export function ExperimentalSection({ scopeBanner, form, setForm, knownFeatures, {scopeBanner}

{t("settings.experimental.experimentalFeatures", "Experimental Features")}

- {t("settings.experimental.experimentalFeaturesAreEarlyCapabilitiesThatAreNot", " Experimental features are early capabilities that are not yet fully stable. Enable them to test new functionality, but be aware they may change or be removed. ")} + {t("settings.experimental.experimentalFeaturesAreEarlyCapabilitiesThatAreNot", " Experimental features are early capabilities that are not yet fully stable. Enable them to test new functionality, but be aware they may change or be removed. Default: disabled for every feature flag below. ")}
diff --git a/packages/dashboard/app/components/settings/sections/GeneralSection.tsx b/packages/dashboard/app/components/settings/sections/GeneralSection.tsx index 6f58a95248..8cae07c825 100644 --- a/packages/dashboard/app/components/settings/sections/GeneralSection.tsx +++ b/packages/dashboard/app/components/settings/sections/GeneralSection.tsx @@ -91,11 +91,11 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast } }}/> {prefixError && {prefixError}} - {!prefixError && {t("settings.general.prefixForNewTaskIDsEGKB", "Prefix for new task IDs (e.g. KB, PROJ)")}} + {!prefixError && {t("settings.general.prefixForNewTaskIDsEGKB", "Prefix for new task IDs (e.g. KB, PROJ). No default \u2014 unset.")}}
- {t("settings.general.newTasksInheritThisCustomWorkflowsStepsOverridable", "New tasks inherit this custom workflow's steps (overridable per task)")} + {t("settings.general.newTasksInheritThisCustomWorkflowsStepsOverridable", "New tasks inherit this custom workflow's steps (overridable per task). No default \u2014 unset (built-in default workflow).")}
{builtinWorkflows.length > 0 && (
@@ -106,7 +106,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast {workflow.name} ))}
- {t("settings.general.disabledFusionWorkflowsAreHiddenFromWorkflow", "Disabled Fusion workflows are hidden from workflow pickers. Existing tasks that already use one continue to resolve.")} + {t("settings.general.disabledFusionWorkflowsAreHiddenFromWorkflow", "Disabled Fusion workflows are hidden from workflow pickers. Existing tasks that already use one continue to resolve. Default: all built-in workflows enabled (unset).")}
)}
@@ -243,7 +243,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
- {t("settings.general.warnOnTheBoardWhenTodoWorkExceeds", "Warn on the board when todo work exceeds the threshold and no idle agents are available.")} + {t("settings.general.warnOnTheBoardWhenTodoWorkExceeds", "Warn on the board when todo work exceeds the threshold and no idle agents are available. Default: disabled.")}
@@ -286,7 +286,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast */} - {t("settings.general.whenEnabledImportedGitHubIssuesUseTheirSource", "When enabled, GitHub issue imports become tracked tasks that adopt the source issue. This does not turn GitHub tracking on for ordinary new tasks.")} + {t("settings.general.whenEnabledImportedGitHubIssuesUseTheirSource", "When enabled, GitHub issue imports become tracked tasks that adopt the source issue. This does not turn GitHub tracking on for ordinary new tasks. Default: disabled.")}
@@ -296,7 +296,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
- {t("settings.general.whenEnabledFusionChecksOpenAndClosedIssues", " When enabled, Fusion checks open and closed issues in the target repo for likely duplicates (using File Scope paths and key symptoms) before creating a new tracking issue. Uncheck to always create a new issue. ")} + {t("settings.general.whenEnabledFusionChecksOpenAndClosedIssues", " When enabled, Fusion checks open and closed issues in the target repo for likely duplicates (using File Scope paths and key symptoms) before creating a new tracking issue. Uncheck to always create a new issue. Default: enabled. ")}

{t("settings.general.gitLabConfiguration", "GitLab Configuration")}

{/* @@ -311,7 +311,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast {t("settings.general.enableGitLabIntegration", "Enable GitLab integration")} - {form.gitlabEnabled === false ? t("settings.general.gitLabDisabledHint", "GitLab API imports, comments, close/reopen, and refresh operations are disabled. Saved URLs and tokens remain stored for re-enable.") : t("settings.general.gitLabEnabledHint", "Configure GitLab.com or self-managed GitLab URLs. Blank values inherit global fallbacks and then GitLab.com.")} + {form.gitlabEnabled === false ? t("settings.general.gitLabDisabledHint", "GitLab API imports, comments, close/reopen, and refresh operations are disabled. Saved URLs and tokens remain stored for re-enable.") : t("settings.general.gitLabEnabledHint", "Configure GitLab.com or self-managed GitLab URLs. Blank values inherit global fallbacks and then GitLab.com. No default — unset (unset behaves as enabled until explicitly disabled).")}
diff --git a/packages/dashboard/app/components/settings/sections/GlobalGeneralSection.tsx b/packages/dashboard/app/components/settings/sections/GlobalGeneralSection.tsx index 3b9be8372c..054334bb5a 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, globalSetting
setForm((f) => ({ ...f, githubTrackingDefaultRepo: nextValue || undefined }))}/> - {t("settings.globalGeneral.projectsInheritThisValueWhenTheyDoNot", "Projects inherit this value when they do not set a project default tracking repo.")} + {t("settings.globalGeneral.projectsInheritThisValueWhenTheyDoNot", "Projects inherit this value when they do not set a project default tracking repo. No default — unset.")}
{/* FNXC:GitLabEnablement 2026-07-02-00:00: @@ -53,17 +53,17 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting {t("settings.globalGeneral.enableGitLabIntegration", "Enable GitLab integration")} - {globalGitlab.gitlabEnabled === false ? t("settings.globalGeneral.gitLabDisabledHint", "GitLab API operations are disabled by global default. Saved URL and token fallbacks remain stored for re-enable.") : t("settings.globalGeneral.gitLabEnabledHint", "Global GitLab URL and token fallbacks apply to projects that do not set their own values.")} + {globalGitlab.gitlabEnabled === false ? t("settings.globalGeneral.gitLabDisabledHint", "GitLab API operations are disabled by global default. Saved URL and token fallbacks remain stored for re-enable.") : t("settings.globalGeneral.gitLabEnabledHint", "Global GitLab URL and token fallbacks apply to projects that do not set their own values. No default — unset (unset behaves as enabled until explicitly disabled).")}
onGlobalGitlabSettingsChange({ gitlabInstanceUrl: e.target.value || undefined })}/> - {t("settings.globalGeneral.gitLabInstanceUrlHint", "Blank defaults to GitLab.com. Projects inherit this self-managed GitLab URL unless they set their own project value.")} + {t("settings.globalGeneral.gitLabInstanceUrlHint", "Blank defaults to GitLab.com. Projects inherit this self-managed GitLab URL unless they set their own project value. No default — unset.")}
onGlobalGitlabSettingsChange({ gitlabApiBaseUrl: e.target.value || undefined })}/> - {t("settings.globalGeneral.gitLabApiBaseUrlHint", "Blank derives /api/v4. Override only for self-managed GitLab API gateways that use a different absolute http:// or https:// URL.")} + {t("settings.globalGeneral.gitLabApiBaseUrlHint", "Blank derives /api/v4. Override only for self-managed GitLab API gateways that use a different absolute http:// or https:// URL. No default — unset.")}
@@ -72,11 +72,12 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting + {t("settings.globalGeneral.gitLabTokenTypeHint", "No default — unset (the selector falls back to personal access token until you choose otherwise).")}
onGlobalGitlabSettingsChange({ gitlabAuthToken: e.target.value || undefined })}/> - {t("settings.globalGeneral.gitLabAuthTokenHint", "Projects inherit this fallback only when they do not set a project GitLab token. Read-only operations need read_api or api; write actions need api; project/group tokens remain limited by resource membership.")} + {t("settings.globalGeneral.gitLabAuthTokenHint", "Projects inherit this fallback only when they do not set a project GitLab token. Read-only operations need read_api or api; write actions need api; project/group tokens remain limited by resource membership. No default — unset.")}
@@ -105,12 +106,12 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting
- {t("settings.globalGeneral.dismissModalsByClickingOutsideHint", " When enabled, clicking or tapping a modal backdrop closes the modal. Off by default to prevent accidental dismissal. ")} + {t("settings.globalGeneral.dismissModalsByClickingOutsideHint", " When enabled, clicking or tapping a modal backdrop closes the modal. Default: disabled, to prevent accidental dismissal. ")}
- {t("settings.globalGeneral.whenDisabledToolRowsAreStillLoggedBut", " When disabled, tool rows are still logged but detailed tool payloads are omitted. Very large tool payloads may still be clipped even when this stays enabled. ")} + {t("settings.globalGeneral.whenDisabledToolRowsAreStillLoggedBut", " When disabled, tool rows are still logged but detailed tool payloads are omitted. Very large tool payloads may still be clipped even when this stays enabled. Default: disabled. ")}
{t("settings.globalGeneral.saveAIThinkingLogs", "Save AI thinking logs")}
@@ -118,21 +119,21 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting setForm((f) => ({ ...f, persistAgentThinkingLogPermanent: e.target.checked }))}/>{t("settings.globalGeneral.saveAIThinkingForPermanentAgents", " Save AI thinking for permanent agents ")} - {t("settings.globalGeneral.leaveBothThinkingTogglesOffToKeepThe", " Leave both thinking toggles off to keep the original default behavior. This only controls persisted ")}thinking{t("settings.globalGeneral.rowsAndDoesNotAffectAssistantTextOr", " rows and does not affect assistant text or tool rows. ")} + {t("settings.globalGeneral.leaveBothThinkingTogglesOffToKeepThe", " Leave both thinking toggles off to keep the original default behavior. This only controls persisted ")}thinking{t("settings.globalGeneral.rowsAndDoesNotAffectAssistantTextOr", " rows and does not affect assistant text or tool rows. Default: disabled for both permanent and ephemeral agents. ")}
{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. Default: enabled. ")}

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

{t("settings.globalGeneral.whenEnabledFusionChecksNpmForNewVersions", " When enabled, Fusion checks npm for new versions of")}{" "} - @runfusion/fusion{t("settings.globalGeneral.andShowsUpdateNoticesInTheCLIAnd", " and shows update notices in the CLI and dashboard. Cadence is governed by the frequency below. ")} + @runfusion/fusion{t("settings.globalGeneral.andShowsUpdateNoticesInTheCLIAnd", " and shows update notices in the CLI and dashboard. Cadence is governed by the frequency below. Default: enabled. ")}
@@ -145,12 +146,12 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting - {t("settings.globalGeneral.controlsHowOftenTheDashboardReFetchesThe", " Controls how often the dashboard re-fetches the npm registry. Use the version + refresh control in the header to trigger an immediate check at any time. ")} + {t("settings.globalGeneral.controlsHowOftenTheDashboardReFetchesThe", " Controls how often the dashboard re-fetches the npm registry. Use the version + refresh control in the header to trigger an immediate check at any time. Default: daily. ")}
- {t("settings.globalGeneral.whenEnabledDefaultTheDashboardAutomaticallyReloadsWhen", " When enabled (default), the dashboard automatically reloads when it detects a new build version \u2014 either from server rebuilds or service worker updates. Disable this to stay on the current version until you manually refresh. ")} + {t("settings.globalGeneral.whenEnabledDefaultTheDashboardAutomaticallyReloadsWhen", " When enabled (default), the dashboard automatically reloads when it detects a new build version \u2014 either from server rebuilds or service worker updates. Disable this to stay on the current version until you manually refresh. Default: enabled. ")}
); } diff --git a/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx b/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx index 352d02496e..c428ecbcec 100644 --- a/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/GlobalModelsSection.tsx @@ -55,7 +55,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel })); } }} placeholder={t("settings.globalModels.useDefault", "Use default")} favoriteProviders={favoriteProviders} onToggleFavorite={onToggleFavorite} favoriteModels={favoriteModels} onToggleModelFavorite={onToggleModelFavorite}/> - {t("settings.globalModels.defaultAIModelUsedForTaskExecutionWhen", "Default AI model used for task execution when no per-task override is set. "Use default" lets the engine choose automatically.")} + {t("settings.globalModels.defaultAIModelUsedForTaskExecutionWhen", "Default AI model used for task execution when no per-task override is set. "Use default" lets the engine choose automatically. No default \u2014 unset.")}
@@ -73,7 +73,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel })); } }} placeholder={t("settings.globalModels.noFallback", "No fallback")} favoriteProviders={favoriteProviders} onToggleFavorite={onToggleFavorite} favoriteModels={favoriteModels} onToggleModelFavorite={onToggleModelFavorite}/> - {t("settings.globalModels.usedAutomaticallyIfThePrimaryDefaultModelHits", "Used automatically if the primary default model hits a retryable provider error like rate limiting or overload.")} + {t("settings.globalModels.usedAutomaticallyIfThePrimaryDefaultModelHits", "Used automatically if the primary default model hits a retryable provider error like rate limiting or overload. No default \u2014 unset.")}
)} {(() => { @@ -92,7 +92,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel {level.charAt(0).toUpperCase() + level.slice(1)} ))} - {t("settings.globalModels.controlsHowMuchReasoningEffortTheAIModel", "Controls how much reasoning effort the AI model uses. Higher levels produce better results but cost more.")} + {t("settings.globalModels.controlsHowMuchReasoningEffortTheAIModel", "Controls how much reasoning effort the AI model uses. Higher levels produce better results but cost more. No default \u2014 unset (model's own default effort applies).")}
); })()} @@ -133,12 +133,12 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
- {t("settings.globalModels.whenEnabledStartupFetchesTheLatestAvailableModels", " When enabled, startup fetches the latest available models from the OpenRouter API so model pickers always include the newest catalog. ")} + {t("settings.globalModels.whenEnabledStartupFetchesTheLatestAvailableModels", " When enabled, startup fetches the latest available models from the OpenRouter API so model pickers always include the newest catalog. Default: enabled. ")}
- {t("settings.globalModels.whenEnabledStartupRefreshesModelsThroughTheLocal", " When enabled, startup refreshes models through the local ")}opencode models opencode --refresh{t("settings.globalModels.flowAndPublishesThemUnderTheOpencodeGo", " flow and publishes them under the opencode-go provider in model pickers. ")} + {t("settings.globalModels.whenEnabledStartupRefreshesModelsThroughTheLocal", " When enabled, startup refreshes models through the local ")}opencode models opencode --refresh{t("settings.globalModels.flowAndPublishesThemUnderTheOpencodeGo", " flow and publishes them under the opencode-go provider in model pickers. Default: enabled. ")}
{t("settings.globalModels.openRouterAdvanced", "OpenRouter advanced")} @@ -151,7 +151,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel referer: e.target.value, }, }))}/> - {t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultHttps", "Leave empty to omit this header. Default: https://runfusion.ai.")} + {t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultHttps", "Leave empty to omit this header. No default — unset (Fusion falls back to https://runfusion.ai when unset).")}
@@ -162,7 +162,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel title: e.target.value, }, }))}/> - {t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultFusion", "Leave empty to omit this header. Default: Fusion.")} + {t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultFusion", "Leave empty to omit this header. No default — unset (Fusion falls back to the title \"Fusion\" when unset).")}
@@ -176,7 +176,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel }, })); }}/> - {t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSync", "Comma-separated values sent to OpenRouter model sync.")} + {t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSync", "Comma-separated values sent to OpenRouter model sync. No default \u2014 unset (unfiltered).")}
@@ -190,7 +190,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel }, })); }}/> - {t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSync", "Comma-separated values sent to OpenRouter model sync.")} + {t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSyncOutputModalities", "Comma-separated values sent to OpenRouter model sync. No default \u2014 unset (unfiltered).")}
@@ -204,6 +204,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel }, })); }}/> + {t("settings.globalModels.openRouterRoutingOrderHint", "No default \u2014 unset (OpenRouter's own default routing order applies).")}
@@ -217,6 +218,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel }, })); }}/> + {t("settings.globalModels.openRouterRoutingIgnoreHint", "No default \u2014 unset (no providers ignored).")}
@@ -230,6 +232,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel }, })); }}/> + {t("settings.globalModels.openRouterRoutingOnlyHint", "No default \u2014 unset (no provider restriction).")}
@@ -247,6 +250,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel + {t("settings.globalModels.openRouterAllowFallbacksHint", "No default \u2014 unset (OpenRouter's own default fallback behavior applies).")}
@@ -265,6 +269,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel + {t("settings.globalModels.openRouterRoutingSortHint", "No default \u2014 unset (OpenRouter's own default sort applies).")}
+ {t("settings.globalModels.requireParametersHint", "Default: disabled.")}
); diff --git a/packages/dashboard/app/components/settings/sections/McpServersCard.tsx b/packages/dashboard/app/components/settings/sections/McpServersCard.tsx index 1cd5b27196..a7af32ecff 100644 --- a/packages/dashboard/app/components/settings/sections/McpServersCard.tsx +++ b/packages/dashboard/app/components/settings/sections/McpServersCard.tsx @@ -537,6 +537,7 @@ export function McpServersCard({ scope, form, setForm, globalSettings, projectId setEnabled(event.target.checked)} /> {t("settings.mcp.enabled", "Enable MCP servers for this scope")} + {t("settings.mcp.enabledHint", "Default: disabled, with no servers configured.")}
diff --git a/packages/dashboard/app/components/settings/sections/MemorySection.tsx b/packages/dashboard/app/components/settings/sections/MemorySection.tsx index bb95f708fd..a65bf92ff6 100644 --- a/packages/dashboard/app/components/settings/sections/MemorySection.tsx +++ b/packages/dashboard/app/components/settings/sections/MemorySection.tsx @@ -73,7 +73,7 @@ export function MemorySection({ scopeBanner, form, setForm, memory }: MemorySect
- {t("settings.memory.agentsGetMemorySearchMemoryGetAndMemory", "Agents get memory_search, memory_get, and memory_append tools. Search defaults to qmd with a local file fallback.")} + {t("settings.memory.agentsGetMemorySearchMemoryGetAndMemory", "Agents get memory_search, memory_get, and memory_append tools. Search defaults to qmd with a local file fallback. Default: enabled.")}
{backendLoading ? (
@@ -93,7 +93,7 @@ export function MemorySection({ scopeBanner, form, setForm, memory }: MemorySect
- {t("settings.memory.automaticallyCompactMemoryWhenItExceedsTheThreshold", "Automatically compact memory when it exceeds the threshold on a schedule")} + {t("settings.memory.automaticallyCompactMemoryWhenItExceedsTheThreshold", "Automatically compact memory when it exceeds the threshold on a schedule. Default: disabled.")}
{(form.memoryAutoSummarizeEnabled || false) && (<> @@ -103,12 +103,12 @@ export function MemorySection({ scopeBanner, form, setForm, memory }: MemorySect ...f, memoryAutoSummarizeThresholdChars: parseInt(e.target.value, 10) || 50000, }))} min={1000}/> - {t("settings.memory.memoryWillBeCompactedWhenItExceedsThis", "Memory will be compacted when it exceeds this character count")} + {t("settings.memory.memoryWillBeCompactedWhenItExceedsThis", "Memory will be compacted when it exceeds this character count. Default: 50000.")}
setForm((f) => ({ ...f, memoryAutoSummarizeSchedule: e.target.value }))} placeholder={t("settings.memory.03", "0 3 * * *")}/> - {t("settings.memory.cronExpressionForAutoSummarizeScheduleDefaultDaily", "Cron expression for auto-summarize schedule (default: daily at 3 AM)")} + {t("settings.memory.cronExpressionForAutoSummarizeScheduleDefaultDaily", "Cron expression for auto-summarize schedule. Default: 0 3 * * * (daily at 3 AM).")}
)} @@ -117,14 +117,14 @@ export function MemorySection({ scopeBanner, form, setForm, memory }: MemorySect
- {t("settings.memory.turnsDailyNotesIntoDREAMSMdAndPromotes", "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md.")} + {t("settings.memory.turnsDailyNotesIntoDREAMSMdAndPromotes", "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md. Default: disabled.")}
{isMemoryEnabled && form.memoryDreamsEnabled === true && (<>
setForm((f) => ({ ...f, memoryDreamsSchedule: e.target.value }))}/> - {t("settings.memory.cronExpressionForDreamProcessing", "Cron expression for dream processing.")} + {t("settings.memory.cronExpressionForDreamProcessing", "Cron expression for dream processing. Default: 0 4 * * * (daily at 4 AM).")}
@@ -102,7 +102,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti const nextMode = e.target.value as Settings["planApprovalMode"]; setForm((f) => ({ ...f, planApprovalMode: nextMode })); }} data-testid="plan-approval-mode-select"> - + @@ -166,7 +166,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti }))}/>{t("settings.merge.allowAIMergeToSyncADirtyChecked", " Allow AI merge to sync a dirty checked-out integration branch ")}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.dangerousCompatibilityEscapeHatchLeaveOffUnlessYou", " Dangerous compatibility escape hatch. Leave off unless you explicitly want the legacy stash \u2192 fast-forward \u2192 restore behavior when your checked-out integration branch has unrelated local edits. When off, AI merge blocks before advancing the branch so dirty project-root edits cannot contaminate a completed merge. ")} + {t("settings.merge.dangerousCompatibilityEscapeHatchLeaveOffUnlessYou", " Dangerous compatibility escape hatch \u2014 restores the legacy stash \u2192 fast-forward \u2192 restore behavior when your checked-out integration branch has unrelated local edits. When off, AI merge blocks before advancing the branch so dirty project-root edits cannot contaminate a completed merge. Default: enabled (new/unconfigured projects sync a dirty checkout). ")}
)} @@ -175,14 +175,14 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti setForm((f) => ({ ...f, testMode: e.target.checked }))}/>{t("settings.merge.enableTestMode", " Enable test mode ")}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.forcesAllAILanesToUseTheDeterministic", "Forces all AI lanes to use the deterministic mock provider. No network calls, zero token cost.")} + {t("settings.merge.forcesAllAILanesToUseTheDeterministic", "Forces all AI lanes to use the deterministic mock provider. No network calls, zero token cost. No default \u2014 unset (disabled).")}
@@ -231,7 +231,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti })()}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.theCanonicalBranchFusionMergesTasksIntoAnd", " The canonical branch Fusion merges tasks into and uses as the reference for all ahead/behind / overlap / pre-rebase computations. Leave on ")}{t("settings.merge.autoDetect", "auto-detect")}{t("settings.merge.toResolveViaTheStandardCascade", " to resolve via the standard cascade (")}integrationBranch{t("settings.merge.legacy", " \u2192 legacy ")}baseBranch → + {t("settings.merge.theCanonicalBranchFusionMergesTasksIntoAnd", " No default \u2014 unset (auto-detect). The canonical branch Fusion merges tasks into and uses as the reference for all ahead/behind / overlap / pre-rebase computations. Leave on ")}{t("settings.merge.autoDetect", "auto-detect")}{t("settings.merge.toResolveViaTheStandardCascade", " to resolve via the standard cascade (")}integrationBranch{t("settings.merge.legacy", " \u2192 legacy ")}baseBranch → origin/HEAD{t("settings.merge.symbolicRefFallback", " symbolic ref \u2192 fallback ")}main{t("settings.merge.pickALocalBranchFromTheDropdownCommon", "). Pick a local branch from the dropdown \u2014 common integration names like ")}main, master, trunk{t("settings.merge.and", ", and ")}develop{t("settings.merge.areListedFirstOrChoose", " are listed first \u2014 or choose ")}{t("settings.merge.custom", "Custom\u2026")}{t("settings.merge.toTypeABranchThatDoesnAposT", " to type a branch that doesn't exist locally yet. Applies to both direct merges and pull-request mode; individual tasks can still override via task metadata. ")}
@@ -244,7 +244,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti directMergeCommitStrategy: e.target.value as "auto" | "always-squash" | "always-rebase", }))}> - +
@@ -286,13 +286,14 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
{(form.githubAuthMode ?? "gh-cli") === "token" && (
setForm((f) => ({ ...f, githubAuthToken: e.target.value || undefined }))}/> + {t("settings.merge.githubAuthTokenHint", "No default \u2014 unset.")}
)}

{t("settings.merge.gitLabAuthentication", "GitLab Authentication")}

{/** @@ -307,12 +308,12 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti {t("settings.merge.enableGitLabIntegration", "Enable GitLab integration")} - {form.gitlabEnabled === false ? t("settings.merge.gitLabDisabledHint", "GitLab comments, close/reopen, import fetches, and refresh operations are disabled. Saved tokens remain stored for re-enable.") : t("settings.merge.gitLabAuthDetails", "Fusion uses GitLab REST API token authentication with the PRIVATE-TOKEN header. Leave the token blank to clear the project override and fall back to a configured global GitLab token or GITLAB_TOKEN where available.")} + {form.gitlabEnabled === false ? t("settings.merge.gitLabDisabledHint", "GitLab comments, close/reopen, import fetches, and refresh operations are disabled. Saved tokens remain stored for re-enable.") : t("settings.merge.gitLabAuthDetails", "Fusion uses GitLab REST API token authentication with the PRIVATE-TOKEN header. Leave the token blank to clear the project override and fall back to a configured global GitLab token or GITLAB_TOKEN where available. No default — unset (unset behaves as enabled until explicitly disabled).")}
@@ -320,7 +321,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
setForm((f) => ({ ...f, gitlabAuthToken: e.target.value || undefined }))}/> - {t("settings.merge.gitLabAuthTokenHint", "Read-only GitLab operations need read_api or api. Future write actions such as comments and auto-close need api. Project and group tokens are limited to their associated resource and role membership.")} + {t("settings.merge.gitLabAuthTokenHint", "Read-only GitLab operations need read_api or api. Future write actions such as comments and auto-close need api. Project and group tokens are limited to their associated resource and role membership. No default \u2014 unset.")}
@@ -329,7 +330,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti setForm((f) => ({ ...f, includeTaskIdInCommit: e.target.checked }))}/>{t("settings.merge.includeTaskIDInCommitScope", " Include task ID in commit scope ")}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.whenDisabledMergeCommitMessagesOmitTheTask", "When disabled, merge commit messages omit the task ID from the scope (e.g. ")}feat: ...{t("settings.merge.insteadOf", " instead of ")}feat(KB-001): ...) + {t("settings.merge.whenDisabledMergeCommitMessagesOmitTheTask", "When disabled, merge commit messages omit the task ID from the scope (e.g. ")}feat: ...{t("settings.merge.insteadOf", " instead of ")}feat(KB-001): ...{t("settings.merge.includeTaskIdInCommitDefault", "). Default: enabled.")}
@@ -337,7 +338,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti setForm((f) => ({ ...f, commitAuthorEnabled: e.target.checked }))}/>{t("settings.merge.addFusionAsCoAuthorOnCommits", " Add Fusion as co-author on commits ")}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.whenEnabledCommitsMadeByFusionKeepYour", " When enabled, commits made by Fusion keep your git identity as the primary author and append a ")}Co-authored-by{t("settings.merge.trailerCreditingFusionRecognizedByGitHubForShared", " trailer crediting Fusion (recognized by GitHub for shared attribution). ")} + {t("settings.merge.whenEnabledCommitsMadeByFusionKeepYour", " When enabled, commits made by Fusion keep your git identity as the primary author and append a ")}Co-authored-by{t("settings.merge.trailerCreditingFusionRecognizedByGitHubForShared", " trailer crediting Fusion (recognized by GitHub for shared attribution). Default: enabled. ")}
@@ -348,7 +349,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti ...f, commitAuthorName: e.target.value || undefined, }))}/> - {t("settings.merge.nameUsedInThe", "Name used in the ")}Co-authored-by{t("settings.merge.trailer", " trailer")} + {t("settings.merge.nameUsedInThe", "Name used in the ")}Co-authored-by{t("settings.merge.trailer", " trailer. Default: Fusion.")}
@@ -356,7 +357,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti ...f, commitAuthorEmail: e.target.value || undefined, }))}/> - {t("settings.merge.emailUsedInThe", "Email used in the ")}Co-authored-by{t("settings.merge.trailer", " trailer")} + {t("settings.merge.emailUsedInThe", "Email used in the ")}Co-authored-by{t("settings.merge.trailerEmail", " trailer. Default: noreply@runfusion.ai.")}
)} @@ -365,7 +366,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti setForm((f) => ({ ...f, autoResolveConflicts: e.target.checked }))}/>{t("settings.merge.autoResolveConflictsInLockFilesAndGenerated", " Auto-resolve conflicts in lock files and generated files ")}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.whenEnabledLockFilesPackageLockJsonPnpm", "When enabled, lock files (package-lock.json, pnpm-lock.yaml, etc.), generated files (dist/*, *.gen.ts), and trivial whitespace conflicts are resolved automatically without AI intervention. Complex code conflicts still require AI review.")} + {t("settings.merge.whenEnabledLockFilesPackageLockJsonPnpm", "When enabled, lock files (package-lock.json, pnpm-lock.yaml, etc.), generated files (dist/*, *.gen.ts), and trivial whitespace conflicts are resolved automatically without AI intervention. Complex code conflicts still require AI review. Default: enabled.")}
{(form.merger?.mode ?? "ai") !== "ai" && (<> @@ -374,7 +375,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti setForm((f) => ({ ...f, smartConflictResolution: e.target.checked }))}/>{t("settings.merge.smartConflictResolution", " Smart conflict resolution ")}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.whenEnabledLockFilesPackageLockJsonPnpm2", "When enabled, lock files (package-lock.json, pnpm-lock.yaml, etc.) are resolved using 'ours' strategy, generated files (dist/*, *.gen.ts) using 'theirs' strategy, and trivial whitespace conflicts are auto-resolved without spawning an AI agent. Complex code conflicts still require AI review.")} + {t("settings.merge.whenEnabledLockFilesPackageLockJsonPnpm2", "When enabled, lock files (package-lock.json, pnpm-lock.yaml, etc.) are resolved using 'ours' strategy, generated files (dist/*, *.gen.ts) using 'theirs' strategy, and trivial whitespace conflicts are auto-resolved without spawning an AI agent. Complex code conflicts still require AI review. Default: enabled.")}
@@ -427,7 +428,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti setForm((f) => ({ ...f, pushAfterMerge: e.target.checked }))}/>{t("settings.merge.pushToRemoteAfterMerge", " Push to remote after merge ")}
{t("settings.merge.moreDetails", "More details")} - {t("settings.merge.whenEnabledTheMergedResultIsAutomaticallyPushed", "When enabled, the merged result is automatically pushed to the configured git remote. This includes pulling the latest from the remote first (rebase) and resolving any conflicts with AI if needed.")} + {t("settings.merge.whenEnabledTheMergedResultIsAutomaticallyPushed", "When enabled, the merged result is automatically pushed to the configured git remote. This includes pulling the latest from the remote first (rebase) and resolving any conflicts with AI if needed. Default: disabled.")}
diff --git a/packages/dashboard/app/components/settings/sections/ModelPricingSection.tsx b/packages/dashboard/app/components/settings/sections/ModelPricingSection.tsx index 3dc308cbc6..9f7d806fe1 100644 --- a/packages/dashboard/app/components/settings/sections/ModelPricingSection.tsx +++ b/packages/dashboard/app/components/settings/sections/ModelPricingSection.tsx @@ -233,7 +233,7 @@ export function ModelPricingSection({ form, setForm, addToast, projectId }: Mode

{t("settings.modelPricing.title", "Model Pricing")}

- {t("settings.modelPricing.description", "Override per-1M token rates used by Command Center cost estimates. Overrides win over the built-in baseline; unlisted models still use the baseline.")} + {t("settings.modelPricing.description", "Override per-1M token rates used by Command Center cost estimates. Overrides win over the built-in baseline; unlisted models still use the baseline. No default \u2014 unset (no overrides).")}

{form.modelPricingFetchedAt diff --git a/packages/dashboard/app/components/settings/sections/NodeRoutingSection.tsx b/packages/dashboard/app/components/settings/sections/NodeRoutingSection.tsx index 95fbd89edd..a1b6a8b338 100644 --- a/packages/dashboard/app/components/settings/sections/NodeRoutingSection.tsx +++ b/packages/dashboard/app/components/settings/sections/NodeRoutingSection.tsx @@ -45,7 +45,7 @@ export function NodeRoutingSection({ scopeBanner, form, setForm, nodes }: NodeRo

); })()} - {t("settings.nodeRouting.usedWhenATaskHasNoNodeOverride", "Used when a task has no node override. Node status is shown for safer routing selection.")} + {t("settings.nodeRouting.usedWhenATaskHasNoNodeOverride", "Used when a task has no node override. Node status is shown for safer routing selection. No default \u2014 unset (local execution).")}
@@ -56,6 +56,7 @@ export function NodeRoutingSection({ scopeBanner, form, setForm, nodes }: NodeRo + {t("settings.nodeRouting.unavailableNodePolicyHint", "Default: block execution.")}
); } diff --git a/packages/dashboard/app/components/settings/sections/NodeSyncSection.tsx b/packages/dashboard/app/components/settings/sections/NodeSyncSection.tsx index 0d015edafd..172c493714 100644 --- a/packages/dashboard/app/components/settings/sections/NodeSyncSection.tsx +++ b/packages/dashboard/app/components/settings/sections/NodeSyncSection.tsx @@ -12,13 +12,13 @@ export function NodeSyncSection({ scopeBanner, form, setForm }: NodeSyncSectionP
- {t("settings.nodeSync.automaticallySynchronizeSettingsBetweenThisNodeAndConnected", "Automatically synchronize settings between this node and connected remote nodes")} + {t("settings.nodeSync.automaticallySynchronizeSettingsBetweenThisNodeAndConnected", "Automatically synchronize settings between this node and connected remote nodes. Default: disabled.")}
{form.settingsSyncEnabled && (<>
- {t("settings.nodeSync.includeAPIKeysAndOAuthTokensInSync", "Include API keys and OAuth tokens in sync operations")} + {t("settings.nodeSync.includeAPIKeysAndOAuthTokensInSync", "Include API keys and OAuth tokens in sync operations. Default: disabled.")}
@@ -28,6 +28,7 @@ export function NodeSyncSection({ scopeBanner, form, setForm }: NodeSyncSectionP + {t("settings.nodeSync.syncIntervalHint", "Default: every 15 minutes.")}
@@ -40,6 +41,7 @@ export function NodeSyncSection({ scopeBanner, form, setForm }: NodeSyncSectionP + {t("settings.nodeSync.conflictResolutionHint", "Default: last write wins.")}
)} {/* KTD-8: workflow settings are not yet part of the cross-node sync diff --git a/packages/dashboard/app/components/settings/sections/NotificationsSection.tsx b/packages/dashboard/app/components/settings/sections/NotificationsSection.tsx index 3fb39d7605..caf8526923 100644 --- a/packages/dashboard/app/components/settings/sections/NotificationsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/NotificationsSection.tsx @@ -79,7 +79,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat failureNotificationDelayMs: Number.isFinite(parsed) && parsed >= 0 ? parsed : 0, })); }}/> - {t("settings.notifications.howLongAFailureMustPersistBeforeA", " How long a failure must persist before a push notification is sent. 0 = notify immediately. ")} + {t("settings.notifications.howLongAFailureMustPersistBeforeA", " How long a failure must persist before a push notification is sent. 0 = notify immediately. Default: 30000 (30 seconds). ")} @@ -88,6 +88,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat {t("settings.notifications.ntfy", "ntfy")} + {t("settings.notifications.ntfyEnabledHint", "Default: disabled.")} {form.ntfyEnabled && (
@@ -96,7 +97,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat const val = e.target.value; setForm((f) => ({ ...f, ntfyTopic: val || undefined })); }}/> - {t("settings.notifications.yourNtfyShTopicName164Alphanumeric", " Your ntfy.sh topic name (1\u201364 alphanumeric/hyphen/underscore characters).")}{" "} + {t("settings.notifications.yourNtfyShTopicName164Alphanumeric", " Your ntfy.sh topic name (1\u201364 alphanumeric/hyphen/underscore characters). No default \u2014 unset.")}{" "} {t("settings.notifications.learnMoreAboutNtfySh", " Learn more about ntfy.sh ")} {form.ntfyTopic && !/^[a-zA-Z0-9_-]{1,64}$/.test(form.ntfyTopic) && ({t("settings.notifications.topicMustBe164AlphanumericHyphenOr", " Topic must be 1\u201364 alphanumeric, hyphen, or underscore characters ")})} @@ -108,13 +109,13 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat const value = e.target.value; setForm((f) => ({ ...f, ntfyBaseUrl: value || undefined })); }}/> - {t("settings.notifications.leaveBlankToKeepTheDefaultServerHttps", " Leave blank to keep the default server: https://ntfy.sh. Custom servers must use http:// or https://. ")} + {t("settings.notifications.leaveBlankToKeepTheDefaultServerHttps", " Leave blank to keep the default server: https://ntfy.sh. Custom servers must use http:// or https://. No default \u2014 unset. ")} { const value = e.target.value; setForm((f) => ({ ...f, ntfyAccessToken: value || undefined })); }}/> - {t("settings.notifications.leaveBlankToPublishWithoutAuthenticationWhenSet", " Leave blank to publish without authentication. When set, Fusion sends an Authorization Bearer header with ntfy requests. ")} + {t("settings.notifications.leaveBlankToPublishWithoutAuthenticationWhenSet", " Leave blank to publish without authentication. When set, Fusion sends an Authorization Bearer header with ntfy requests. No default \u2014 unset. ")}
@@ -145,7 +146,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat const val = e.target.value; setForm((f) => ({ ...f, ntfyDashboardHost: val || undefined })); }}/> - {t("settings.notifications.baseURLForDeepLinksInNotificationsWhen", " Base URL for deep links in notifications. When set, clicking a notification opens the dashboard directly to the task. ")} + {t("settings.notifications.baseURLForDeepLinksInNotificationsWhen", " Base URL for deep links in notifications. When set, clicking a notification opens the dashboard directly to the task. No default \u2014 unset. ")} {form.ntfyDashboardHost && !/^https?:\/\/.+/.test(form.ntfyDashboardHost) && ({t("settings.notifications.mustBeAValidURLStartingWithHttp", " Must be a valid URL starting with http:// or https:// ")})}
@@ -190,6 +191,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat {t("settings.notifications.webhook", "Webhook")} + {t("settings.notifications.webhookEnabledHint", "Default: disabled.")}
{form.webhookEnabled && (
@@ -198,6 +200,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat const val = e.target.value; setForm((f) => ({ ...f, webhookUrl: val || undefined })); }}/> + {t("settings.notifications.webhookUrlHint", "No default \u2014 unset.")}
@@ -209,6 +212,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat + {t("settings.notifications.webhookFormatHint", "Default: generic.")}
diff --git a/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx b/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx index f627fd8918..76f40c9293 100644 --- a/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx @@ -286,7 +286,7 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje }}/> {form.tokenCap != null && ()}
- {t("settings.projectModels.automaticallyCompactContextWhenApproachingThisTokenCount", "Automatically compact context when approaching this token count. Leave empty for no cap (compact only on overflow errors). Set a number to proactively compact when reaching this token count.")} + {t("settings.projectModels.automaticallyCompactContextWhenApproachingThisTokenCount", "Automatically compact context when approaching this token count. Leave empty for no cap (compact only on overflow errors). Set a number to proactively compact when reaching this token count. No default \u2014 unset (no cap).")}
{/* --- Project Model Lanes --- */} @@ -455,6 +455,7 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje
+ {t("settings.projectModels.autoSelectModelPresetHint", "Default: disabled.")}
{form.autoSelectModelPreset ? (
@@ -484,13 +485,13 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje
- {t("settings.projectModels.whenEnabledTasksCreatedWithoutATitleBut", " When enabled, tasks created without a title but with descriptions over 200 characters will automatically get an AI-generated title (max 60 characters). The same model is also used to generate fallback merge commit message bodies when the branch's commit log is empty (e.g. squash merges with no unique commits), and GitHub tracking issue titles when a tracked task has no title yet. ")} + {t("settings.projectModels.whenEnabledTasksCreatedWithoutATitleBut", " When enabled, tasks created without a title but with descriptions over 200 characters will automatically get an AI-generated title (max 60 characters). The same model is also used to generate fallback merge commit message bodies when the branch's commit log is empty (e.g. squash merges with no unique commits), and GitHub tracking issue titles when a tracked task has no title yet. Default: disabled. ")}
- {t("settings.projectModels.whenEnabledMergeCommitMessagesIncludeAnAI", " When enabled, merge commit messages include an AI-generated subject plus body summary (narrative + bullets + diff-stat) instead of just listing step commit subjects. Uses the title summarization model. ")} + {t("settings.projectModels.whenEnabledMergeCommitMessagesIncludeAnAI", " When enabled, merge commit messages include an AI-generated subject plus body summary (narrative + bullets + diff-stat) instead of just listing step commit subjects. Uses the title summarization model. Default: enabled. ")}
{(form.autoSummarizeTitles || form.useAiMergeCommitSummary || form.githubTrackingEnabledByDefault || false) && (

@@ -500,13 +501,13 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje