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) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-04 13:09:29 -07:00
parent 12a6d1bc6a
commit 4baa4c43c5
31 changed files with 914 additions and 210 deletions

View File

@@ -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.

View File

@@ -13,6 +13,9 @@ When Fusion detects a newer `@runfusion/fusion` release, the Settings modal foot
<!-- FNXC:SettingsSearchDocs 2026-07-04-00:00: Settings search is section-discovery, not a global command palette. Document that it filters visible Settings sections by section names and setting keywords while preserving feature-gated hidden sections. -->
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.
<!-- FNXC:SettingsDefaults 2026-07-04-00:00: FN-7505 requires every user-editable setting's help text to state its own default value, so operators reading a field's description know what it defaults to without checking the reference doc. -->
Every user-editable setting's help text (the `.settings-description`/`<small>` 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
<!-- FNXC:DashboardShortcuts 2026-07-04-00:00: Dashboard keyboard shortcuts are configurable global operator preferences. The docs must state the defaults, editable-field safety guard, duplicate/invalid save behavior, and one-popup Escape semantics so operators know why Space/Terminal/Escape act differently in text fields than on the board. -->

View File

@@ -13,6 +13,9 @@ Fusion uses a two-tier settings system:
At runtime, settings are merged. **Project settings override global settings** when keys overlap.
<!-- FNXC:SettingsDefaults 2026-07-04-00:00: FN-7505 mirrors this table's Default column into the dashboard Settings UI's own help text so operators do not need this doc open to see a field's default. -->
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 |

View File

@@ -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();

View File

@@ -20,7 +20,7 @@ export function AgentPermissionsSection({ scopeBanner, form, setForm }: AgentPer
{scopeBanner}
<h4 className="settings-section-heading">{t("settings.agentPermissions.agentPermissions", "Agent Permissions")}</h4>
<div className="form-group">
<small className="settings-muted">{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.")}</small>
<small className="settings-muted">{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.")}</small>
</div>
<AgentPermissionPolicyEditor mode="project-default" value={form.defaultAgentPermissionPolicy ? { presetId: "custom", rules: toCompleteAgentPermissionRules(form.defaultAgentPermissionPolicy.rules), ...(form.defaultAgentPermissionPolicy.toolRules ? { toolRules: form.defaultAgentPermissionPolicy.toolRules } : {}) } as AgentPermissionPolicy : { presetId: "custom", rules: toCompleteAgentPermissionRules() }} onChange={(next) => setForm((f) => ({
...f,
@@ -29,7 +29,7 @@ export function AgentPermissionsSection({ scopeBanner, form, setForm }: AgentPer
<h4 className="settings-section-heading">{t("settings.agentPermissions.agentProvisioningApprovals", "Agent Provisioning Approvals")}</h4>
<div className="form-group">
<small className="settings-muted">{t("settings.agentPermissions.configureProjectLevelApprovalBehaviorForDurableProvisioning", " Configure project-level approval behavior for durable provisioning tools (fn_agent_create/fn_agent_delete). ")}</small>
<small className="settings-muted">{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). ")}</small>
</div>
<AgentProvisioningPolicyEditor value={form.agentProvisioning} onChange={(next) => setForm((f) => ({ ...f, agentProvisioning: next }))}/>
</>);

View File

@@ -42,7 +42,7 @@ export function AppearanceSection({ scopeBanner, form, setForm, themeMode, color
<input type="checkbox" checked={form.openTasksInRightSidebar === true} onChange={(e) => setForm((f) => ({ ...f, openTasksInRightSidebar: e.target.checked }))}/>
<span>{t("settings.appearance.openTasksInRightSidebar", "Open tasks in the right sidebar")}</span>
</label>
<small className="form-text text-muted">{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.")}</small>
<small className="form-text text-muted">{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.")}</small>
</div>
<div className="form-group">
{/* 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
<input type="checkbox" checked={form.openMobileTasksInPopup === true} onChange={(e) => setForm((f) => ({ ...f, openMobileTasksInPopup: e.target.checked }))}/>
<span>{t("settings.appearance.openMobileTasksInPopup", "Open tasks as popups")}</span>
</label>
<small className="form-text text-muted">{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.")}</small>
<small className="form-text text-muted">{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.")}</small>
</div>
<div className="form-group">
{/* 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. */}

View File

@@ -17,7 +17,7 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL
<div className="form-group">
<label htmlFor="autoBackupEnabled" className="checkbox-label">
<input id="autoBackupEnabled" type="checkbox" checked={form.autoBackupEnabled || false} onChange={(e) => setForm((f) => ({ ...f, autoBackupEnabled: e.target.checked }))}/>{t("settings.backups.enableAutomaticDatabaseBackups", " Enable automatic database backups ")}</label>
<small>{t("settings.backups.whenEnabledTheDatabaseIsBackedUpAutomatically", "When enabled, the database is backed up automatically on a schedule")}</small>
<small>{t("settings.backups.whenEnabledTheDatabaseIsBackedUpAutomatically", "When enabled, the database is backed up automatically on a schedule. Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="autoBackupSchedule">{t("settings.backups.backupScheduleCron", "Backup Schedule (Cron)")}</label>
@@ -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}/>
<small>{t("settings.backups.numberOfBackupFilesToKeepOldestAre", "Number of backup files to keep (oldest are deleted first). Range: 1-100.")}</small>
<small>{t("settings.backups.numberOfBackupFilesToKeepOldestAre", "Number of backup files to keep (oldest are deleted first). Range: 1-100. Default: 7.")}</small>
{form.autoBackupRetention !== undefined && (form.autoBackupRetention < 1 || form.autoBackupRetention > 100) && (<small className="field-error">{t("settings.backups.mustBeBetween1And100", "Must be between 1 and 100")}</small>)}
</div>
<div className="form-group">
<label htmlFor="autoBackupDir">{t("settings.backups.backupDirectory", "Backup Directory")}</label>
<input id="autoBackupDir" type="text" placeholder={t("settings.backups.fusionBackups", ".fusion/backups")} value={form.autoBackupDir || ".fusion/backups"} onChange={(e) => setForm((f) => ({ ...f, autoBackupDir: e.target.value }))} disabled={!form.autoBackupEnabled}/>
<small>{t("settings.backups.directoryForBackupFilesRelativeToProjectRoot", "Directory for backup files, relative to project root")}</small>
<small>{t("settings.backups.directoryForBackupFilesRelativeToProjectRoot", "Directory for backup files, relative to project root. Default: .fusion/backups.")}</small>
{form.autoBackupDir && form.autoBackupDir.includes("..") && (<small className="field-error">{t("settings.backups.pathCannotContainParentDirectoryTraversal", "Path cannot contain parent directory traversal (..)")}</small>)}
</div>
@@ -45,7 +45,7 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL
<div className="form-group">
<label htmlFor="memoryBackupEnabled" className="checkbox-label">
<input id="memoryBackupEnabled" type="checkbox" checked={form.memoryBackupEnabled || false} onChange={(e) => setForm((f) => ({ ...f, memoryBackupEnabled: e.target.checked }))}/>{t("settings.backups.enableAutomaticMemoryBackups", " Enable automatic memory backups ")}</label>
<small>{t("settings.backups.whenEnabledProjectAndAgentMemoryFilesAre", "When enabled, project and agent memory files are backed up automatically on a schedule.")}</small>
<small>{t("settings.backups.whenEnabledProjectAndAgentMemoryFilesAre", "When enabled, project and agent memory files are backed up automatically on a schedule. Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="memoryBackupSchedule">{t("settings.backups.memoryBackupScheduleCron", "Memory Backup Schedule (Cron)")}</label>
@@ -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}/>
<small>{t("settings.backups.numberOfMemoryBackupsToKeepOldestAre", "Number of memory backups to keep (oldest are deleted first). Range: 1-100.")}</small>
<small>{t("settings.backups.numberOfMemoryBackupsToKeepOldestAre", "Number of memory backups to keep (oldest are deleted first). Range: 1-100. Default: 14.")}</small>
{form.memoryBackupRetention !== undefined && (form.memoryBackupRetention < 1 || form.memoryBackupRetention > 100) && (<small className="field-error">{t("settings.backups.mustBeBetween1And100", "Must be between 1 and 100")}</small>)}
</div>
<div className="form-group">
<label htmlFor="memoryBackupDir">{t("settings.backups.memoryBackupDirectory", "Memory Backup Directory")}</label>
<input id="memoryBackupDir" type="text" placeholder={t("settings.backups.fusionBackupsMemory", ".fusion/backups/memory")} value={form.memoryBackupDir || ".fusion/backups/memory"} onChange={(e) => setForm((f) => ({ ...f, memoryBackupDir: e.target.value }))} disabled={!form.memoryBackupEnabled}/>
<small>{t("settings.backups.directoryForMemoryBackupsRelativeToProjectRoot", "Directory for memory backups, relative to project root.")}</small>
<small>{t("settings.backups.directoryForMemoryBackupsRelativeToProjectRoot", "Directory for memory backups, relative to project root. Default: .fusion/backups/memory.")}</small>
{form.memoryBackupDir && form.memoryBackupDir.includes("..") && (<small className="field-error">{t("settings.backups.pathCannotContainParentDirectoryTraversal", "Path cannot contain parent directory traversal (..)")}</small>)}
</div>
<div className="form-group">
@@ -75,6 +75,7 @@ export function BackupsSection({ scopeBanner, form, setForm, backupInfo, backupL
<option value="project">{t("settings.backups.projectOnlyFusionMemory", "Project only (.fusion/memory)")}</option>
<option value="agents">{t("settings.backups.agentsOnlyFusionAgentMemory", "Agents only (.fusion/agent-memory)")}</option>
</select>
<small>{t("settings.backups.memoryBackupScopeHint", "Default: all (project + agents).")}</small>
</div>
{backupLoading ? (<div className="settings-empty-state"><LoadingSpinner label={t("settings.backups.loadingBackupInfo", "Loading backup info\u2026")} /></div>) : backupInfo ? (<div className="form-group">
<label>{t("settings.backups.currentBackups", "Current Backups")}</label>

View File

@@ -12,12 +12,12 @@ export function CommandsSection({ scopeBanner, form, setForm }: CommandsSectionP
<div className="form-group">
<label htmlFor="testCommand">{t("settings.commands.testCommand", "Test Command")}</label>
<input id="testCommand" type="text" placeholder={t("settings.commands.eGPnpmTest", "e.g. pnpm test")} value={form.testCommand || ""} onChange={(e) => setForm((f) => ({ ...f, testCommand: e.target.value || undefined }))}/>
<small>{t("settings.commands.commandUsedToRunTestsInjectedIntoGenerated", "Command used to run tests \u2014 injected into generated task specs")}</small>
<small>{t("settings.commands.commandUsedToRunTestsInjectedIntoGenerated", "Command used to run tests \u2014 injected into generated task specs. No default \u2014 unset.")}</small>
</div>
<div className="form-group">
<label htmlFor="buildCommand">{t("settings.commands.buildCommand", "Build Command")}</label>
<input id="buildCommand" type="text" placeholder={t("settings.commands.eGPnpmBuild", "e.g. pnpm build")} value={form.buildCommand || ""} onChange={(e) => setForm((f) => ({ ...f, buildCommand: e.target.value || undefined }))}/>
<small>{t("settings.commands.commandUsedToBuildTheProjectInjectedInto", "Command used to build the project \u2014 injected into generated task specs")}</small>
<small>{t("settings.commands.commandUsedToBuildTheProjectInjectedInto", "Command used to build the project \u2014 injected into generated task specs. No default \u2014 unset.")}</small>
</div>
</>);
}

View File

@@ -26,7 +26,7 @@ export function ExperimentalSection({ scopeBanner, form, setForm, knownFeatures,
{scopeBanner}
<h4 className="settings-section-heading">{t("settings.experimental.experimentalFeatures", "Experimental Features")}</h4>
<div className="form-group">
<small>{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. ")}</small>
<small>{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. ")}</small>
</div>
<div className="form-group">

View File

@@ -91,11 +91,11 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
}
}}/>
{prefixError && <small className="field-error">{prefixError}</small>}
{!prefixError && <small>{t("settings.general.prefixForNewTaskIDsEGKB", "Prefix for new task IDs (e.g. KB, PROJ)")}</small>}
{!prefixError && <small>{t("settings.general.prefixForNewTaskIDsEGKB", "Prefix for new task IDs (e.g. KB, PROJ). No default \u2014 unset.")}</small>}
</div>
<div className="form-group">
<ProjectDefaultWorkflowField projectId={projectId} addToast={addToast}/>
<small>{t("settings.general.newTasksInheritThisCustomWorkflowsStepsOverridable", "New tasks inherit this custom workflow's steps (overridable per task)")}</small>
<small>{t("settings.general.newTasksInheritThisCustomWorkflowsStepsOverridable", "New tasks inherit this custom workflow's steps (overridable per task). No default \u2014 unset (built-in default workflow).")}</small>
</div>
{builtinWorkflows.length > 0 && (<div className="form-group">
<label>{t("settings.general.fusionWorkflows", "Fusion workflows")}</label>
@@ -106,7 +106,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<span>{workflow.name}</span>
</label>))}
</div>
<small>{t("settings.general.disabledFusionWorkflowsAreHiddenFromWorkflow", "Disabled Fusion workflows are hidden from workflow pickers. Existing tasks that already use one continue to resolve.")}</small>
<small>{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).")}</small>
</div>)}
<div className="form-group">
<label htmlFor="ephemeralAgentsEnabled" className="checkbox-label">
@@ -132,7 +132,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<div className="form-group">
<label htmlFor="workspaceMode" className="checkbox-label">
<input id="workspaceMode" type="checkbox" checked={form.workspaceMode === true} onChange={(e) => setForm((f) => ({ ...f, workspaceMode: e.target.checked }))}/>{t("settings.general.workspaceMode", " Workspace mode (multi-repo) ")}</label>
<small>{t("settings.general.workspaceModeHint", "When enabled, the project root is treated as a workspace containing multiple git sub-repos. Tasks run per-sub-repo and no git repo is created at the root. Disable for single-repo projects.")}</small>
<small>{t("settings.general.workspaceModeHint", "When enabled, the project root is treated as a workspace containing multiple git sub-repos. Tasks run per-sub-repo and no git repo is created at the root. Disable for single-repo projects. No default \u2014 unset (disabled).")}</small>
</div>
{/*
FNXC:FileBrowser 2026-06-29-00:00:
@@ -141,7 +141,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<div className="form-group">
<label htmlFor="allowAbsoluteFileBrowserPaths" className="checkbox-label">
<input id="allowAbsoluteFileBrowserPaths" type="checkbox" checked={form.allowAbsoluteFileBrowserPaths === true} onChange={(e) => setForm((f) => ({ ...f, allowAbsoluteFileBrowserPaths: e.target.checked }))}/>{t("settings.general.allowAbsoluteFileBrowserPaths", " Allow absolute file-browser paths ")}</label>
<small>{t("settings.general.allowAbsoluteFileBrowserPathsHint", "When enabled, slash-prefixed paths such as /tmp can be opened in the workspace file browser. Windows drive-letter paths remain blocked, and other path validators are unchanged.")}</small>
<small>{t("settings.general.allowAbsoluteFileBrowserPathsHint", "When enabled, slash-prefixed paths such as /tmp can be opened in the workspace file browser. Windows drive-letter paths remain blocked, and other path validators are unchanged. Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="completionDocumentationMode">{t("settings.general.completionDocumentationAutomation", "Completion Documentation Automation")}</label>
@@ -153,7 +153,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<option value="changeset">{t("settings.general.requireChangesetChangesetMd", "Require changeset (.changeset/*.md)")}</option>
<option value="changelog">{t("settings.general.requireChangelogUpdateExistingChangelog", "Require changelog update (existing changelog)")}</option>
</select>
<small>{t("settings.general.controlsHowFutureTaskSpecsHandleReleaseNote", " Controls how future task specs handle release-note artifacts at completion. Use changeset mode for repositories that follow ")}<code>.changeset</code>{t("settings.general.workflowsOrChangelogModeWhenContributorsShouldUpdate", " workflows, or changelog mode when contributors should update an existing changelog file. ")}</small>
<small>{t("settings.general.controlsHowFutureTaskSpecsHandleReleaseNote", " Controls how future task specs handle release-note artifacts at completion. Use changeset mode for repositories that follow ")}<code>.changeset</code>{t("settings.general.workflowsOrChangelogModeWhenContributorsShouldUpdate", " workflows, or changelog mode when contributors should update an existing changelog file. Default: off. ")}</small>
</div>
<div className="form-group">
<label htmlFor="quickChatButtonMode">{t("settings.general.quickChatLauncher", "Quick Chat launcher")}</label>
@@ -166,7 +166,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<option value="footer">{t("settings.general.quickChatLauncherFooter", "Footer button")}</option>
<option value="off">{t("settings.general.off", "Off")}</option>
</select>
<small>{t("settings.general.quickChatLauncherHint", "Choose whether Quick Chat opens from the draggable floating button, a footer button beside Terminal, or stays hidden.")}</small>
<small>{t("settings.general.quickChatLauncherHint", "Choose whether Quick Chat opens from the draggable floating button, a footer button beside Terminal, or stays hidden. Default: off (hidden).")}</small>
</div>
{/*
FNXC:ChatModal 2026-06-28-00:00:
@@ -175,7 +175,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<div className="form-group">
<label htmlFor="quickChatCloseOnOutsideClick" className="checkbox-label">
<input id="quickChatCloseOnOutsideClick" type="checkbox" checked={form.quickChatCloseOnOutsideClick !== false} onChange={(e) => setForm((f) => ({ ...f, quickChatCloseOnOutsideClick: e.target.checked }))}/>{t("settings.general.quickChatCloseOnOutsideClick", "Close Quick Chat on outside click")}</label>
<small>{t("settings.general.quickChatCloseOnOutsideClickHint", "When enabled, clicking outside the Quick Chat window closes it. Disable to keep it open until you close it explicitly.")}</small>
<small>{t("settings.general.quickChatCloseOnOutsideClickHint", "When enabled, clicking outside the Quick Chat window closes it. Disable to keep it open until you close it explicitly. Default: enabled.")}</small>
</div>
<h4 className="settings-section-heading settings-section-heading--spaced">{t("settings.general.chatHistory", "Chat history")}</h4>
{/*
@@ -185,7 +185,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<div className="form-group">
<label htmlFor="showTaskChatsInCommonFeed" className="checkbox-label">
<input id="showTaskChatsInCommonFeed" type="checkbox" checked={form.showTaskChatsInCommonFeed === true} onChange={(e) => setForm((f) => ({ ...f, showTaskChatsInCommonFeed: e.target.checked }))}/>{t("settings.general.showTaskChatsInCommonFeed", "Show task chats in common Chat feed")}</label>
<small>{t("settings.general.showTaskChatsInCommonFeedHint", "When enabled, populated task-detail Chat conversations appear in the common Direct feed. Empty task chats stay hidden.")}</small>
<small>{t("settings.general.showTaskChatsInCommonFeedHint", "When enabled, populated task-detail Chat conversations appear in the common Direct feed. Empty task chats stay hidden. Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="chatAutoCleanupDays">{t("settings.general.autoCleanupOldChats", "Auto-cleanup old chats")}</label>
@@ -243,7 +243,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<div className="form-group">
<label htmlFor="capacityRiskBannerEnabled" className="checkbox-label">
<input id="capacityRiskBannerEnabled" type="checkbox" checked={form.capacityRiskBannerEnabled === true} onChange={(e) => setForm((f) => ({ ...f, capacityRiskBannerEnabled: e.target.checked }))}/>{t("settings.general.showCapacityRiskBanner", " Show capacity risk banner ")}</label>
<small>{t("settings.general.warnOnTheBoardWhenTodoWorkExceeds", "Warn on the board when todo work exceeds the threshold and no idle agents are available.")}</small>
<small>{t("settings.general.warnOnTheBoardWhenTodoWorkExceeds", "Warn on the board when todo work exceeds the threshold and no idle agents are available. Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="capacityRiskTodoThresholdGeneral">{t("settings.general.todoThreshold", "Todo threshold")}</label>
@@ -286,7 +286,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
*/}
<label htmlFor="githubLinkImportedIssuesToTracking" className="checkbox-label">
<input id="githubLinkImportedIssuesToTracking" type="checkbox" checked={form.githubLinkImportedIssuesToTracking === true} onChange={(e) => setForm((f) => ({ ...f, githubLinkImportedIssuesToTracking: e.target.checked }))}/>{t("settings.general.alwaysLinkImportedGitHubIssuesToTracking", " Always link imported GitHub issues to GitHub tracking ")}</label>
<small>{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.")}</small>
<small>{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.")}</small>
</div>
<div className="form-group">
<label htmlFor="projectGithubTrackingDefaultRepoGeneral">{t("settings.general.projectDefaultTrackingRepo", "Project default tracking repo")}</label>
@@ -296,7 +296,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
<div className="form-group">
<label htmlFor="githubTrackingDedupEnabled" className="checkbox-label">
<input id="githubTrackingDedupEnabled" type="checkbox" checked={form.githubTrackingDedupEnabled !== false} onChange={(e) => setForm((f) => ({ ...f, githubTrackingDedupEnabled: e.target.checked }))}/>{t("settings.general.searchTheTrackingRepoForLikelyDuplicatesBefore", " Search the tracking repo for likely duplicates before opening a new issue ")}</label>
<small>{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. ")}</small>
<small>{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. ")}</small>
</div>
<h4 className="settings-section-heading settings-section-heading--spaced">{t("settings.general.gitLabConfiguration", "GitLab Configuration")}</h4>
{/*
@@ -311,7 +311,7 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
{t("settings.general.enableGitLabIntegration", "Enable GitLab integration")}
</label>
</summary>
<small className="settings-description">{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.")}</small>
<small className="settings-description">{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).")}</small>
<div className="settings-gitlab-disclosure__body" aria-disabled={form.gitlabEnabled === false}>
<div className="form-group">
<label htmlFor="gitlabInstanceUrl">{t("settings.general.gitLabInstanceUrl", "GitLab instance URL")}</label>

View File

@@ -39,7 +39,7 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting
<div className="form-group">
<label htmlFor="globalGithubTrackingDefaultRepo">{t("settings.globalGeneral.globalDefaultTrackingRepo", "Global default tracking repo")}</label>
<TrackingRepoSelect id="globalGithubTrackingDefaultRepo" ariaLabel="Global default tracking repo" value={form.githubTrackingDefaultRepo ?? ""} options={globalTrackingRepoOptions} loading={globalTrackingRepoLoading} error={globalTrackingRepoError ?? undefined} placeholder={t("settings.globalGeneral.ownerRepo", "owner/repo")} onChange={(nextValue) => setForm((f) => ({ ...f, githubTrackingDefaultRepo: nextValue || undefined }))}/>
<small>{t("settings.globalGeneral.projectsInheritThisValueWhenTheyDoNot", "Projects inherit this value when they do not set a project default tracking repo.")}</small>
<small>{t("settings.globalGeneral.projectsInheritThisValueWhenTheyDoNot", "Projects inherit this value when they do not set a project default tracking repo. No default — unset.")}</small>
</div>
{/*
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")}
</label>
</summary>
<small className="settings-description">{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.")}</small>
<small className="settings-description">{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).")}</small>
<div className="settings-gitlab-disclosure__body" aria-disabled={globalGitlab.gitlabEnabled === false}>
<div className="form-group">
<label htmlFor="globalGitlabInstanceUrl">{t("settings.globalGeneral.gitLabInstanceUrl", "Global GitLab instance URL")}</label>
<input id="globalGitlabInstanceUrl" className="input" type="url" placeholder="https://gitlab.com" value={globalGitlab.gitlabInstanceUrl ?? ""} disabled={globalGitlab.gitlabEnabled === false} onChange={(e) => onGlobalGitlabSettingsChange({ gitlabInstanceUrl: e.target.value || undefined })}/>
<small>{t("settings.globalGeneral.gitLabInstanceUrlHint", "Blank defaults to GitLab.com. Projects inherit this self-managed GitLab URL unless they set their own project value.")}</small>
<small>{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.")}</small>
</div>
<div className="form-group">
<label htmlFor="globalGitlabApiBaseUrl">{t("settings.globalGeneral.gitLabApiBaseUrlOptional", "Global GitLab API base URL (optional / advanced)")}</label>
<input id="globalGitlabApiBaseUrl" className="input" type="url" placeholder="https://gitlab.com/api/v4" value={globalGitlab.gitlabApiBaseUrl ?? ""} disabled={globalGitlab.gitlabEnabled === false} onChange={(e) => onGlobalGitlabSettingsChange({ gitlabApiBaseUrl: e.target.value || undefined })}/>
<small>{t("settings.globalGeneral.gitLabApiBaseUrlHint", "Blank derives <instance>/api/v4. Override only for self-managed GitLab API gateways that use a different absolute http:// or https:// URL.")}</small>
<small>{t("settings.globalGeneral.gitLabApiBaseUrlHint", "Blank derives <instance>/api/v4. Override only for self-managed GitLab API gateways that use a different absolute http:// or https:// URL. No default — unset.")}</small>
</div>
<div className="form-group">
<label htmlFor="globalGitlabAuthTokenType">{t("settings.globalGeneral.gitLabTokenType", "Global GitLab token type")}</label>
@@ -72,11 +72,12 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting
<option value="project">{t("settings.globalGeneral.gitLabProjectAccessToken", "Project access token")}</option>
<option value="group">{t("settings.globalGeneral.gitLabGroupAccessToken", "Group access token")}</option>
</select>
<small>{t("settings.globalGeneral.gitLabTokenTypeHint", "No default — unset (the selector falls back to personal access token until you choose otherwise).")}</small>
</div>
<div className="form-group">
<label htmlFor="globalGitlabAuthToken">{t("settings.globalGeneral.gitLabAccessToken", "Global GitLab access token")}</label>
<input id="globalGitlabAuthToken" className="input" type="password" autoComplete="off" value={globalGitlab.gitlabAuthToken ?? ""} disabled={globalGitlab.gitlabEnabled === false} onChange={(e) => onGlobalGitlabSettingsChange({ gitlabAuthToken: e.target.value || undefined })}/>
<small className="settings-description">{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.")}</small>
<small className="settings-description">{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.")}</small>
</div>
</div>
</details>
@@ -105,12 +106,12 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting
<div className="form-group">
<label htmlFor="dismissModalsOnOutsideClick" className="checkbox-label">
<input id="dismissModalsOnOutsideClick" type="checkbox" checked={form.dismissModalsOnOutsideClick === true} onChange={(e) => setForm((f) => ({ ...f, dismissModalsOnOutsideClick: e.target.checked }))}/>{t("settings.globalGeneral.dismissModalsByClickingOutside", " Dismiss modals by clicking outside ")}</label>
<small>{t("settings.globalGeneral.dismissModalsByClickingOutsideHint", " When enabled, clicking or tapping a modal backdrop closes the modal. Off by default to prevent accidental dismissal. ")}</small>
<small>{t("settings.globalGeneral.dismissModalsByClickingOutsideHint", " When enabled, clicking or tapping a modal backdrop closes the modal. Default: disabled, to prevent accidental dismissal. ")}</small>
</div>
<div className="form-group">
<label htmlFor="persistAgentToolOutput" className="checkbox-label">
<input id="persistAgentToolOutput" type="checkbox" checked={form.persistAgentToolOutput === true} onChange={(e) => setForm((f) => ({ ...f, persistAgentToolOutput: e.target.checked }))}/>{t("settings.globalGeneral.saveToolOutputInAgentLogs", " Save tool output in agent logs ")}</label>
<small>{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. ")}</small>
<small>{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. ")}</small>
</div>
<div className="form-group">
<h5 className="settings-section-heading">{t("settings.globalGeneral.saveAIThinkingLogs", "Save AI thinking logs")}</h5>
@@ -118,21 +119,21 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting
<input id="persistAgentThinkingLogPermanent" type="checkbox" checked={resolvePersistAgentThinkingLog(form, { ephemeral: false })} onChange={(e) => setForm((f) => ({ ...f, persistAgentThinkingLogPermanent: e.target.checked }))}/>{t("settings.globalGeneral.saveAIThinkingForPermanentAgents", " Save AI thinking for permanent agents ")}</label>
<label htmlFor="persistAgentThinkingLogEphemeral" className="checkbox-label">
<input id="persistAgentThinkingLogEphemeral" type="checkbox" checked={resolvePersistAgentThinkingLog(form, { ephemeral: true })} onChange={(e) => setForm((f) => ({ ...f, persistAgentThinkingLogEphemeral: e.target.checked }))}/>{t("settings.globalGeneral.saveAIThinkingForEphemeralTaskWorkerAgents", " Save AI thinking for ephemeral / task-worker agents ")}</label>
<small>{t("settings.globalGeneral.leaveBothThinkingTogglesOffToKeepThe", " Leave both thinking toggles off to keep the original default behavior. This only controls persisted ")}<code>thinking</code>{t("settings.globalGeneral.rowsAndDoesNotAffectAssistantTextOr", " rows and does not affect assistant text or tool rows. ")}</small>
<small>{t("settings.globalGeneral.leaveBothThinkingTogglesOffToKeepThe", " Leave both thinking toggles off to keep the original default behavior. This only controls persisted ")}<code>thinking</code>{t("settings.globalGeneral.rowsAndDoesNotAffectAssistantTextOr", " rows and does not affect assistant text or tool rows. Default: disabled for both permanent and ephemeral agents. ")}</small>
</div>
<div className="form-group">
<label htmlFor="fnBinaryCheckEnabled" className="checkbox-label">
<input id="fnBinaryCheckEnabled" type="checkbox" checked={form.fnBinaryCheckEnabled !== false} onChange={(e) => setForm((f) => ({ ...f, fnBinaryCheckEnabled: e.target.checked }))}/>{t("settings.globalGeneral.checkForThe", " Check for the ")}<code>fn</code>{t("settings.globalGeneral.cLIBinaryOnPATH", " CLI binary on PATH ")}</label>
<small>{t("settings.globalGeneral.whenEnabledTheDashboardProbesForAGlobally", " When enabled, the dashboard probes for a globally-installed")}{" "}
<code>fn</code> / <code>fusion</code>{t("settings.globalGeneral.cLIBySpawning", " CLI by spawning")}{" "}
<code>&lt;bin&gt; --version</code>{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. ")}</small>
<code>&lt;bin&gt; --version</code>{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. ")}</small>
</div>
<h4 className="settings-section-heading settings-section-heading--spaced">{t("settings.globalGeneral.updates", "Updates")}</h4>
<div className="form-group">
<label htmlFor="updateCheckEnabled" className="checkbox-label">
<input id="updateCheckEnabled" type="checkbox" checked={form.updateCheckEnabled !== false} onChange={(e) => setForm((f) => ({ ...f, updateCheckEnabled: e.target.checked }))}/>{t("settings.globalGeneral.checkForUpdatesAutomatically", " Check for updates automatically ")}</label>
<small>{t("settings.globalGeneral.whenEnabledFusionChecksNpmForNewVersions", " When enabled, Fusion checks npm for new versions of")}{" "}
<code>@runfusion/fusion</code>{t("settings.globalGeneral.andShowsUpdateNoticesInTheCLIAnd", " and shows update notices in the CLI and dashboard. Cadence is governed by the frequency below. ")}</small>
<code>@runfusion/fusion</code>{t("settings.globalGeneral.andShowsUpdateNoticesInTheCLIAnd", " and shows update notices in the CLI and dashboard. Cadence is governed by the frequency below. Default: enabled. ")}</small>
</div>
<div className="form-group">
<label htmlFor="updateCheckFrequency">{t("settings.globalGeneral.frequency", "Frequency")}</label>
@@ -145,12 +146,12 @@ export function GlobalGeneralSection({ scopeBanner, form, setForm, globalSetting
<option value="daily">{t("settings.globalGeneral.dailyRecommended", "Daily (recommended)")}</option>
<option value="weekly">{t("settings.globalGeneral.weekly", "Weekly")}</option>
</select>
<small>{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. ")}</small>
<small>{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. ")}</small>
</div>
<div className="form-group">
<label htmlFor="autoReloadOnVersionChange" className="checkbox-label">
<input id="autoReloadOnVersionChange" type="checkbox" checked={form.autoReloadOnVersionChange !== false} onChange={(e) => setForm((f) => ({ ...f, autoReloadOnVersionChange: e.target.checked }))}/>{t("settings.globalGeneral.autoReloadDashboardOnVersionChange", " Auto-reload dashboard on version change ")}</label>
<small>{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. ")}</small>
<small>{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. ")}</small>
</div>
</>);
}

View File

@@ -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}/>
<small>{t("settings.globalModels.defaultAIModelUsedForTaskExecutionWhen", "Default AI model used for task execution when no per-task override is set. &quot;Use default&quot; lets the engine choose automatically.")}</small>
<small>{t("settings.globalModels.defaultAIModelUsedForTaskExecutionWhen", "Default AI model used for task execution when no per-task override is set. &quot;Use default&quot; lets the engine choose automatically. No default \u2014 unset.")}</small>
</div>
<div className="form-group">
@@ -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}/>
<small>{t("settings.globalModels.usedAutomaticallyIfThePrimaryDefaultModelHits", "Used automatically if the primary default model hits a retryable provider error like rate limiting or overload.")}</small>
<small>{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.")}</small>
</div>
</>)}
{(() => {
@@ -92,7 +92,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
{level.charAt(0).toUpperCase() + level.slice(1)}
</option>))}
</select>
<small>{t("settings.globalModels.controlsHowMuchReasoningEffortTheAIModel", "Controls how much reasoning effort the AI model uses. Higher levels produce better results but cost more.")}</small>
<small>{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).")}</small>
</div>);
})()}
@@ -133,12 +133,12 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
<div className="form-group">
<label htmlFor="openrouterModelSync" className="checkbox-label">
<input id="openrouterModelSync" type="checkbox" checked={form.openrouterModelSync !== false} onChange={(e) => setForm((f) => ({ ...f, openrouterModelSync: e.target.checked }))}/>{t("settings.globalModels.syncOpenRouterModelListAtStartup", " Sync OpenRouter model list at startup ")}</label>
<small>{t("settings.globalModels.whenEnabledStartupFetchesTheLatestAvailableModels", " When enabled, startup fetches the latest available models from the OpenRouter API so model pickers always include the newest catalog. ")}</small>
<small>{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. ")}</small>
</div>
<div className="form-group">
<label htmlFor="opencodeGoModelSync" className="checkbox-label">
<input id="opencodeGoModelSync" type="checkbox" checked={form.opencodeGoModelSync !== false} onChange={(e) => setForm((f) => ({ ...f, opencodeGoModelSync: e.target.checked }))}/>{t("settings.globalModels.syncOpencodeGoModelListAtStartup", " Sync opencode-go model list at startup ")}</label>
<small>{t("settings.globalModels.whenEnabledStartupRefreshesModelsThroughTheLocal", " When enabled, startup refreshes models through the local ")}<code>opencode models opencode --refresh</code>{t("settings.globalModels.flowAndPublishesThemUnderTheOpencodeGo", " flow and publishes them under the opencode-go provider in model pickers. ")}</small>
<small>{t("settings.globalModels.whenEnabledStartupRefreshesModelsThroughTheLocal", " When enabled, startup refreshes models through the local ")}<code>opencode models opencode --refresh</code>{t("settings.globalModels.flowAndPublishesThemUnderTheOpencodeGo", " flow and publishes them under the opencode-go provider in model pickers. Default: enabled. ")}</small>
</div>
<details>
<summary>{t("settings.globalModels.openRouterAdvanced", "OpenRouter advanced")}</summary>
@@ -151,7 +151,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
referer: e.target.value,
},
}))}/>
<small>{t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultHttps", "Leave empty to omit this header. Default: https://runfusion.ai.")}</small>
<small>{t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultHttps", "Leave empty to omit this header. No default — unset (Fusion falls back to https://runfusion.ai when unset).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterAppAttributionTitle">{t("settings.globalModels.openRouterXTitle", "OpenRouter X-Title")}</label>
@@ -162,7 +162,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
title: e.target.value,
},
}))}/>
<small>{t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultFusion", "Leave empty to omit this header. Default: Fusion.")}</small>
<small>{t("settings.globalModels.leaveEmptyToOmitThisHeaderDefaultFusion", "Leave empty to omit this header. No default — unset (Fusion falls back to the title \"Fusion\" when unset).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterModelFiltersSupportedParameters">{t("settings.globalModels.openRouterSupportedParametersFilter", "OpenRouter supported_parameters filter")}</label>
@@ -176,7 +176,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
},
}));
}}/>
<small>{t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSync", "Comma-separated values sent to OpenRouter model sync.")}</small>
<small>{t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSync", "Comma-separated values sent to OpenRouter model sync. No default \u2014 unset (unfiltered).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterModelFiltersOutputModalities">{t("settings.globalModels.openRouterOutputModalitiesFilter", "OpenRouter output_modalities filter")}</label>
@@ -190,7 +190,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
},
}));
}}/>
<small>{t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSync", "Comma-separated values sent to OpenRouter model sync.")}</small>
<small>{t("settings.globalModels.commaSeparatedValuesSentToOpenRouterModelSyncOutputModalities", "Comma-separated values sent to OpenRouter model sync. No default \u2014 unset (unfiltered).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterProviderPreferencesOrder">{t("settings.globalModels.openRouterRoutingOrder", "OpenRouter routing order")}</label>
@@ -204,6 +204,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
},
}));
}}/>
<small>{t("settings.globalModels.openRouterRoutingOrderHint", "No default \u2014 unset (OpenRouter's own default routing order applies).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterProviderPreferencesIgnore">{t("settings.globalModels.openRouterRoutingIgnore", "OpenRouter routing ignore")}</label>
@@ -217,6 +218,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
},
}));
}}/>
<small>{t("settings.globalModels.openRouterRoutingIgnoreHint", "No default \u2014 unset (no providers ignored).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterProviderPreferencesOnly">{t("settings.globalModels.openRouterRoutingOnly", "OpenRouter routing only")}</label>
@@ -230,6 +232,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
},
}));
}}/>
<small>{t("settings.globalModels.openRouterRoutingOnlyHint", "No default \u2014 unset (no provider restriction).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterProviderPreferencesAllowFallbacks">{t("settings.globalModels.openRouterAllowFallbacks", "OpenRouter allow fallbacks")}</label>
@@ -247,6 +250,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
<option value="allow">{t("settings.globalModels.allow", "allow")}</option>
<option value="deny">{t("settings.globalModels.deny", "deny")}</option>
</select>
<small>{t("settings.globalModels.openRouterAllowFallbacksHint", "No default \u2014 unset (OpenRouter's own default fallback behavior applies).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterProviderPreferencesSort">{t("settings.globalModels.openRouterRoutingSort", "OpenRouter routing sort")}</label>
@@ -265,6 +269,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
<option value="throughput">{t("settings.globalModels.throughput", "throughput")}</option>
<option value="latency">{t("settings.globalModels.latency", "latency")}</option>
</select>
<small>{t("settings.globalModels.openRouterRoutingSortHint", "No default \u2014 unset (OpenRouter's own default sort applies).")}</small>
</div>
<div className="form-group">
<label htmlFor="openrouterProviderPreferencesRequireParameters" className="checkbox-label">
@@ -275,6 +280,7 @@ export function GlobalModelsSection({ scopeBanner, form, setForm, availableModel
require_parameters: e.target.checked,
},
}))}/>{t("settings.globalModels.requireParameters", " Require parameters ")}</label>
<small>{t("settings.globalModels.requireParametersHint", "Default: disabled.")}</small>
</div>
</details>
</>);

View File

@@ -537,6 +537,7 @@ export function McpServersCard({ scope, form, setForm, globalSettings, projectId
<input type="checkbox" checked={settings.enabled === true} onChange={(event) => setEnabled(event.target.checked)} />
{t("settings.mcp.enabled", "Enable MCP servers for this scope")}
</label>
<small className="settings-description">{t("settings.mcp.enabledHint", "Default: disabled, with no servers configured.")}</small>
<div className="mcp-discovery card" data-testid={`mcp-discovery-${scope}`}>
<div className="mcp-discovery__header">

View File

@@ -73,7 +73,7 @@ export function MemorySection({ scopeBanner, form, setForm, memory }: MemorySect
<div className="form-group">
<label htmlFor="memoryEnabled" className="checkbox-label">
<input id="memoryEnabled" type="checkbox" checked={form.memoryEnabled !== false} onChange={(e) => setForm((f) => ({ ...f, memoryEnabled: e.target.checked }))}/>{t("settings.memory.enableMemoryTools", " Enable memory tools ")}</label>
<small>{t("settings.memory.agentsGetMemorySearchMemoryGetAndMemory", "Agents get memory_search, memory_get, and memory_append tools. Search defaults to qmd with a local file fallback.")}</small>
<small>{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.")}</small>
</div>
{backendLoading ? (<div className="form-group">
@@ -93,7 +93,7 @@ export function MemorySection({ scopeBanner, form, setForm, memory }: MemorySect
<div className="form-group">
<label htmlFor="memoryAutoSummarizeEnabled" className="checkbox-label">
<input id="memoryAutoSummarizeEnabled" type="checkbox" checked={form.memoryAutoSummarizeEnabled || false} onChange={(e) => setForm((f) => ({ ...f, memoryAutoSummarizeEnabled: e.target.checked }))}/>{t("settings.memory.autoSummarizeMemory", " Auto-Summarize Memory ")}</label>
<small>{t("settings.memory.automaticallyCompactMemoryWhenItExceedsTheThreshold", "Automatically compact memory when it exceeds the threshold on a schedule")}</small>
<small>{t("settings.memory.automaticallyCompactMemoryWhenItExceedsTheThreshold", "Automatically compact memory when it exceeds the threshold on a schedule. Default: disabled.")}</small>
</div>
{(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}/>
<small>{t("settings.memory.memoryWillBeCompactedWhenItExceedsThis", "Memory will be compacted when it exceeds this character count")}</small>
<small>{t("settings.memory.memoryWillBeCompactedWhenItExceedsThis", "Memory will be compacted when it exceeds this character count. Default: 50000.")}</small>
</div>
<div className="form-group">
<label htmlFor="memoryAutoSummarizeSchedule">{t("settings.memory.scheduleCron", "Schedule (cron)")}</label>
<input id="memoryAutoSummarizeSchedule" type="text" className="input" value={form.memoryAutoSummarizeSchedule ?? "0 3 * * *"} onChange={(e) => setForm((f) => ({ ...f, memoryAutoSummarizeSchedule: e.target.value }))} placeholder={t("settings.memory.03", "0 3 * * *")}/>
<small>{t("settings.memory.cronExpressionForAutoSummarizeScheduleDefaultDaily", "Cron expression for auto-summarize schedule (default: daily at 3 AM)")}</small>
<small>{t("settings.memory.cronExpressionForAutoSummarizeScheduleDefaultDaily", "Cron expression for auto-summarize schedule. Default: 0 3 * * * (daily at 3 AM).")}</small>
</div>
</>)}
@@ -117,14 +117,14 @@ export function MemorySection({ scopeBanner, form, setForm, memory }: MemorySect
<div className="form-group">
<label htmlFor="memoryDreamsEnabled" className="checkbox-label">
<input id="memoryDreamsEnabled" type="checkbox" checked={form.memoryDreamsEnabled === true} onChange={(e) => setForm((f) => ({ ...f, memoryDreamsEnabled: e.target.checked }))} disabled={!isMemoryEnabled}/>{t("settings.memory.processDreamsFromDailyMemory", " Process dreams from daily memory ")}</label>
<small>{t("settings.memory.turnsDailyNotesIntoDREAMSMdAndPromotes", "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md.")}</small>
<small>{t("settings.memory.turnsDailyNotesIntoDREAMSMdAndPromotes", "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md. Default: disabled.")}</small>
</div>
{isMemoryEnabled && form.memoryDreamsEnabled === true && (<>
<div className="form-group">
<label htmlFor="memoryDreamsSchedule">{t("settings.memory.dreamSchedule", "Dream Schedule")}</label>
<input id="memoryDreamsSchedule" type="text" value={form.memoryDreamsSchedule ?? "0 4 * * *"} onChange={(e) => setForm((f) => ({ ...f, memoryDreamsSchedule: e.target.value }))}/>
<small>{t("settings.memory.cronExpressionForDreamProcessing", "Cron expression for dream processing.")}</small>
<small>{t("settings.memory.cronExpressionForDreamProcessing", "Cron expression for dream processing. Default: 0 4 * * * (daily at 4 AM).")}</small>
</div>
<div className="form-group">
<button type="button" className="btn btn-sm" onClick={onDreamNow} disabled={dreamRunning || form.memoryDreamsEnabled !== true}>

View File

@@ -89,7 +89,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<input id="autoMerge" type="checkbox" checked={form.autoMerge} onChange={(e) => setForm((f) => ({ ...f, autoMerge: e.target.checked }))}/>{t("settings.merge.autoMergeCompletedTasks", " Auto-merge completed tasks ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{t("settings.merge.whenEnabledTasksThatPassReviewAreAutomatically", "When enabled, tasks that pass review are automatically merged into the main branch")}</small>
<small>{t("settings.merge.whenEnabledTasksThatPassReviewAreAutomatically", "When enabled, tasks that pass review are automatically merged into the main branch. Default: enabled.")}</small>
</details>
</div>
<div className="form-group">
@@ -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">
<option value="workflow">{t("settings.merge.planApprovalModeWorkflow", "Use workflow setting")}</option>
<option value="workflow">{t("settings.merge.planApprovalModeWorkflow", "Use workflow setting (default)")}</option>
<option value="auto-approve-all">{t("settings.merge.planApprovalModeAutoApproveAll", "Auto-approve all tasks")}</option>
<option value="require-all">{t("settings.merge.planApprovalModeRequireAll", "Require approval for all tasks")}</option>
</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 ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{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. ")}</small>
<small>{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). ")}</small>
</details>
</div>
</>)}
@@ -175,14 +175,14 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<input id="testMode" type="checkbox" checked={form.testMode === true} onChange={(e) => setForm((f) => ({ ...f, testMode: e.target.checked }))}/>{t("settings.merge.enableTestMode", " Enable test mode ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{t("settings.merge.forcesAllAILanesToUseTheDeterministic", "Forces all AI lanes to use the deterministic mock provider. No network calls, zero token cost.")}</small>
<small>{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).")}</small>
</details>
</div>
<MovedSettingsStub message={t("settings.movedStub.reviewVerification", "Review, verification auto-fix, and scope-enforcement settings now live on the workflow.")} onOpenWorkflowSettings={onOpenWorkflowSettings}/>
<div className="form-group">
<label htmlFor="mergeStrategy">{t("settings.merge.autoCompletionMode", "Auto-completion mode")}</label>
<select id="mergeStrategy" value={form.mergeStrategy || "direct"} onChange={(e) => setForm((f) => ({ ...f, mergeStrategy: e.target.value as Settings["mergeStrategy"] }))}>
<option value="direct">{t("settings.merge.directMergeIntoTheCurrentBranch", "Direct merge into the current branch")}</option>
<option value="direct">{t("settings.merge.directMergeIntoTheCurrentBranch", "Direct merge into the current branch (default)")}</option>
<option value="pull-request">{t("settings.merge.createMonitorAndMergeAGitHubPullRequest", "Create, monitor, and merge a GitHub pull request")}</option>
</select>
<details className="settings-option-details">
@@ -231,7 +231,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
})()}
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{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 ")}<em>{t("settings.merge.autoDetect", "auto-detect")}</em>{t("settings.merge.toResolveViaTheStandardCascade", " to resolve via the standard cascade (")}<code>integrationBranch</code>{t("settings.merge.legacy", " \u2192 legacy ")}<code>baseBranch</code> →
<small>{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 ")}<em>{t("settings.merge.autoDetect", "auto-detect")}</em>{t("settings.merge.toResolveViaTheStandardCascade", " to resolve via the standard cascade (")}<code>integrationBranch</code>{t("settings.merge.legacy", " \u2192 legacy ")}<code>baseBranch</code> →
<code>origin/HEAD</code>{t("settings.merge.symbolicRefFallback", " symbolic ref \u2192 fallback ")}<code>main</code>{t("settings.merge.pickALocalBranchFromTheDropdownCommon", "). Pick a local branch from the dropdown \u2014 common integration names like ")}<code>main</code>,
<code>master</code>, <code>trunk</code>{t("settings.merge.and", ", and ")}<code>develop</code>{t("settings.merge.areListedFirstOrChoose", " are listed first \u2014 or choose ")}<em>{t("settings.merge.custom", "Custom\u2026")}</em>{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. ")}</small>
</details>
@@ -244,7 +244,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
directMergeCommitStrategy: e.target.value as "auto" | "always-squash" | "always-rebase",
}))}>
<option value="auto">{t("settings.merge.autoSquashSingleSubstantiveBranchesPreserveMultiSubstantive", "Auto \u2014 squash single-substantive branches, preserve multi-substantive history")}</option>
<option value="always-squash">{t("settings.merge.alwaysSquashDirectMerges", "Always squash direct merges")}</option>
<option value="always-squash">{t("settings.merge.alwaysSquashDirectMerges", "Always squash direct merges (default)")}</option>
<option value="always-rebase">{t("settings.merge.alwaysPreserveDirectMergeCommitHistory", "Always preserve direct-merge commit history")}</option>
</select>
<details className="settings-option-details">
@@ -286,13 +286,14 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<div className="form-group">
<label htmlFor="githubAuthMode">{t("settings.merge.gitHubAuthMode", "GitHub auth mode")}</label>
<select id="githubAuthMode" className="select" value={form.githubAuthMode ?? "gh-cli"} onChange={(e) => setForm((f) => ({ ...f, githubAuthMode: e.target.value as "gh-cli" | "token" }))}>
<option value="gh-cli">{t("settings.merge.gitHubCLIGhAuth", "GitHub CLI (gh auth)")}</option>
<option value="gh-cli">{t("settings.merge.gitHubCLIGhAuth", "GitHub CLI (gh auth) (default)")}</option>
<option value="token">{t("settings.merge.personalAccessToken", "Personal access token")}</option>
</select>
</div>
{(form.githubAuthMode ?? "gh-cli") === "token" && (<div className="form-group">
<label htmlFor="githubAuthToken">{t("settings.merge.gitHubPersonalAccessToken", "GitHub personal access token")}</label>
<input id="githubAuthToken" type="password" className="input" value={form.githubAuthToken ?? ""} onChange={(e) => setForm((f) => ({ ...f, githubAuthToken: e.target.value || undefined }))}/>
<small>{t("settings.merge.githubAuthTokenHint", "No default \u2014 unset.")}</small>
</div>)}
<h4 className="settings-section-heading settings-section-heading--spaced">{t("settings.merge.gitLabAuthentication", "GitLab Authentication")}</h4>
{/**
@@ -307,12 +308,12 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
{t("settings.merge.enableGitLabIntegration", "Enable GitLab integration")}
</label>
</summary>
<small className="settings-description">{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.")}</small>
<small className="settings-description">{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).")}</small>
<div className="settings-gitlab-disclosure__body" aria-disabled={form.gitlabEnabled === false}>
<div className="form-group">
<label htmlFor="gitlabAuthTokenType">{t("settings.merge.gitLabTokenType", "GitLab token type")}</label>
<select id="gitlabAuthTokenType" className="select" value={form.gitlabAuthTokenType ?? "personal"} disabled={form.gitlabEnabled === false} onChange={(e) => setForm((f) => ({ ...f, gitlabAuthTokenType: e.target.value as "personal" | "project" | "group" }))}>
<option value="personal">{t("settings.merge.gitLabPersonalAccessToken", "Personal access token")}</option>
<option value="personal">{t("settings.merge.gitLabPersonalAccessToken", "Personal access token (default)")}</option>
<option value="project">{t("settings.merge.gitLabProjectAccessToken", "Project access token")}</option>
<option value="group">{t("settings.merge.gitLabGroupAccessToken", "Group access token")}</option>
</select>
@@ -320,7 +321,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<div className="form-group">
<label htmlFor="gitlabAuthToken">{t("settings.merge.gitLabAccessToken", "GitLab access token")}</label>
<input id="gitlabAuthToken" type="password" className="input" autoComplete="off" value={form.gitlabAuthToken ?? ""} disabled={form.gitlabEnabled === false} onChange={(e) => setForm((f) => ({ ...f, gitlabAuthToken: e.target.value || undefined }))}/>
<small className="settings-description">{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.")}</small>
<small className="settings-description">{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.")}</small>
</div>
</div>
</details>
@@ -329,7 +330,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<input id="includeTaskIdInCommit" type="checkbox" checked={form.includeTaskIdInCommit !== false} onChange={(e) => setForm((f) => ({ ...f, includeTaskIdInCommit: e.target.checked }))}/>{t("settings.merge.includeTaskIDInCommitScope", " Include task ID in commit scope ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{t("settings.merge.whenDisabledMergeCommitMessagesOmitTheTask", "When disabled, merge commit messages omit the task ID from the scope (e.g. ")}<code>feat: ...</code>{t("settings.merge.insteadOf", " instead of ")}<code>feat(KB-001): ...</code>)</small>
<small>{t("settings.merge.whenDisabledMergeCommitMessagesOmitTheTask", "When disabled, merge commit messages omit the task ID from the scope (e.g. ")}<code>feat: ...</code>{t("settings.merge.insteadOf", " instead of ")}<code>feat(KB-001): ...</code>{t("settings.merge.includeTaskIdInCommitDefault", "). Default: enabled.")}</small>
</details>
</div>
<div className="form-group">
@@ -337,7 +338,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<input id="commitAuthorEnabled" type="checkbox" checked={form.commitAuthorEnabled !== false} onChange={(e) => setForm((f) => ({ ...f, commitAuthorEnabled: e.target.checked }))}/>{t("settings.merge.addFusionAsCoAuthorOnCommits", " Add Fusion as co-author on commits ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{t("settings.merge.whenEnabledCommitsMadeByFusionKeepYour", " When enabled, commits made by Fusion keep your git identity as the primary author and append a ")}<code>Co-authored-by</code>{t("settings.merge.trailerCreditingFusionRecognizedByGitHubForShared", " trailer crediting Fusion (recognized by GitHub for shared attribution). ")}</small>
<small>{t("settings.merge.whenEnabledCommitsMadeByFusionKeepYour", " When enabled, commits made by Fusion keep your git identity as the primary author and append a ")}<code>Co-authored-by</code>{t("settings.merge.trailerCreditingFusionRecognizedByGitHubForShared", " trailer crediting Fusion (recognized by GitHub for shared attribution). Default: enabled. ")}</small>
</details>
</div>
@@ -348,7 +349,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
...f,
commitAuthorName: e.target.value || undefined,
}))}/>
<small>{t("settings.merge.nameUsedInThe", "Name used in the ")}<code>Co-authored-by</code>{t("settings.merge.trailer", " trailer")}</small>
<small>{t("settings.merge.nameUsedInThe", "Name used in the ")}<code>Co-authored-by</code>{t("settings.merge.trailer", " trailer. Default: Fusion.")}</small>
</div>
<div className="form-group">
<label htmlFor="commitAuthorEmail">{t("settings.merge.coAuthorEmail", "Co-author Email")}</label>
@@ -356,7 +357,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
...f,
commitAuthorEmail: e.target.value || undefined,
}))}/>
<small>{t("settings.merge.emailUsedInThe", "Email used in the ")}<code>Co-authored-by</code>{t("settings.merge.trailer", " trailer")}</small>
<small>{t("settings.merge.emailUsedInThe", "Email used in the ")}<code>Co-authored-by</code>{t("settings.merge.trailerEmail", " trailer. Default: noreply@runfusion.ai.")}</small>
</div>
</>)}
@@ -365,7 +366,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<input id="autoResolveConflicts" type="checkbox" checked={form.autoResolveConflicts !== false} onChange={(e) => setForm((f) => ({ ...f, autoResolveConflicts: e.target.checked }))}/>{t("settings.merge.autoResolveConflictsInLockFilesAndGenerated", " Auto-resolve conflicts in lock files and generated files ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{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.")}</small>
<small>{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.")}</small>
</details>
</div>
{(form.merger?.mode ?? "ai") !== "ai" && (<>
@@ -374,7 +375,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<input id="smartConflictResolution" type="checkbox" checked={form.smartConflictResolution !== false} onChange={(e) => setForm((f) => ({ ...f, smartConflictResolution: e.target.checked }))}/>{t("settings.merge.smartConflictResolution", " Smart conflict resolution ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{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.")}</small>
<small>{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.")}</small>
</details>
</div>
<div className="form-group">
@@ -427,7 +428,7 @@ export function MergeSection({ scopeBanner, form, setForm, integrationBranchOpti
<input id="pushAfterMerge" type="checkbox" checked={form.pushAfterMerge === true} onChange={(e) => setForm((f) => ({ ...f, pushAfterMerge: e.target.checked }))}/>{t("settings.merge.pushToRemoteAfterMerge", " Push to remote after merge ")}</label>
<details className="settings-option-details">
<summary>{t("settings.merge.moreDetails", "More details")}</summary>
<small>{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.")}</small>
<small>{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.")}</small>
</details>
</div>

View File

@@ -233,7 +233,7 @@ export function ModelPricingSection({ form, setForm, addToast, projectId }: Mode
<div>
<h4 className="settings-section-heading settings-section-heading--spaced">{t("settings.modelPricing.title", "Model Pricing")}</h4>
<p className="settings-description">
{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).")}
</p>
<p className="settings-muted model-pricing-section__meta">
{form.modelPricingFetchedAt

View File

@@ -45,7 +45,7 @@ export function NodeRoutingSection({ scopeBanner, form, setForm, nodes }: NodeRo
<NodeHealthDot status={selectedNode.status} showLabel/>
</div>);
})()}
<small>{t("settings.nodeRouting.usedWhenATaskHasNoNodeOverride", "Used when a task has no node override. Node status is shown for safer routing selection.")}</small>
<small>{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).")}</small>
</div>
<div className="form-group">
<label htmlFor="unavailableNodePolicy">{t("settings.nodeRouting.unavailableNodePolicy", "Unavailable Node Policy")}</label>
@@ -56,6 +56,7 @@ export function NodeRoutingSection({ scopeBanner, form, setForm, nodes }: NodeRo
<option value="block">{t("settings.nodeRouting.blockExecution", "Block execution")}</option>
<option value="fallback-local">{t("settings.nodeRouting.fallBackToLocal", "Fall back to local")}</option>
</select>
<small>{t("settings.nodeRouting.unavailableNodePolicyHint", "Default: block execution.")}</small>
</div>
</>);
}

View File

@@ -12,13 +12,13 @@ export function NodeSyncSection({ scopeBanner, form, setForm }: NodeSyncSectionP
<div className="form-group">
<label htmlFor="settingsSyncEnabled" className="checkbox-label">
<input id="settingsSyncEnabled" type="checkbox" checked={form.settingsSyncEnabled || false} onChange={(e) => setForm((f) => ({ ...f, settingsSyncEnabled: e.target.checked }))}/>{t("settings.nodeSync.enableAutomaticSettingsSync", " Enable automatic settings sync ")}</label>
<small>{t("settings.nodeSync.automaticallySynchronizeSettingsBetweenThisNodeAndConnected", "Automatically synchronize settings between this node and connected remote nodes")}</small>
<small>{t("settings.nodeSync.automaticallySynchronizeSettingsBetweenThisNodeAndConnected", "Automatically synchronize settings between this node and connected remote nodes. Default: disabled.")}</small>
</div>
{form.settingsSyncEnabled && (<>
<div className="form-group">
<label htmlFor="settingsSyncAuth" className="checkbox-label">
<input id="settingsSyncAuth" type="checkbox" checked={form.settingsSyncAuth || false} onChange={(e) => setForm((f) => ({ ...f, settingsSyncAuth: e.target.checked }))}/>{t("settings.nodeSync.syncModelAuthCredentials", " Sync model auth credentials ")}</label>
<small>{t("settings.nodeSync.includeAPIKeysAndOAuthTokensInSync", "Include API keys and OAuth tokens in sync operations")}</small>
<small>{t("settings.nodeSync.includeAPIKeysAndOAuthTokensInSync", "Include API keys and OAuth tokens in sync operations. Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="settingsSyncInterval">{t("settings.nodeSync.syncInterval", "Sync interval")}</label>
@@ -28,6 +28,7 @@ export function NodeSyncSection({ scopeBanner, form, setForm }: NodeSyncSectionP
<option value={1800000}>{t("settings.nodeSync.every30Minutes", "Every 30 minutes")}</option>
<option value={3600000}>{t("settings.nodeSync.every1Hour", "Every 1 hour")}</option>
</select>
<small>{t("settings.nodeSync.syncIntervalHint", "Default: every 15 minutes.")}</small>
</div>
<div className="form-group">
<label htmlFor="settingsSyncConflictResolution">{t("settings.nodeSync.conflictResolution", "Conflict resolution")}</label>
@@ -40,6 +41,7 @@ export function NodeSyncSection({ scopeBanner, form, setForm }: NodeSyncSectionP
<option value="keep-local">{t("settings.nodeSync.keepLocal", "Keep local")}</option>
<option value="keep-remote">{t("settings.nodeSync.keepRemote", "Keep remote")}</option>
</select>
<small>{t("settings.nodeSync.conflictResolutionHint", "Default: last write wins.")}</small>
</div>
</>)}
{/* KTD-8: workflow settings are not yet part of the cross-node sync

View File

@@ -79,7 +79,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
failureNotificationDelayMs: Number.isFinite(parsed) && parsed >= 0 ? parsed : 0,
}));
}}/>
<small>{t("settings.notifications.howLongAFailureMustPersistBeforeA", " How long a failure must persist before a push notification is sent. 0 = notify immediately. ")}</small>
<small>{t("settings.notifications.howLongAFailureMustPersistBeforeA", " How long a failure must persist before a push notification is sent. 0 = notify immediately. Default: 30000 (30 seconds). ")}</small>
</div>
</div>
@@ -88,6 +88,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
<strong>{t("settings.notifications.ntfy", "ntfy")}</strong>
<label htmlFor="ntfyEnabled" className="checkbox-label">
<input id="ntfyEnabled" type="checkbox" checked={form.ntfyEnabled || false} onChange={(e) => setForm((f) => ({ ...f, ntfyEnabled: e.target.checked }))}/>{t("settings.notifications.enable", " Enable ")}</label>
<small>{t("settings.notifications.ntfyEnabledHint", "Default: disabled.")}</small>
</div>
{form.ntfyEnabled && (<div className="notification-provider-body">
<div className="form-group">
@@ -96,7 +97,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
const val = e.target.value;
setForm((f) => ({ ...f, ntfyTopic: val || undefined }));
}}/>
<small>{t("settings.notifications.yourNtfyShTopicName164Alphanumeric", " Your ntfy.sh topic name (1\u201364 alphanumeric/hyphen/underscore characters).")}{" "}
<small>{t("settings.notifications.yourNtfyShTopicName164Alphanumeric", " Your ntfy.sh topic name (1\u201364 alphanumeric/hyphen/underscore characters). No default \u2014 unset.")}{" "}
<a href="https://ntfy.sh" target="_blank" rel="noopener noreferrer" className="settings-inline-link">{t("settings.notifications.learnMoreAboutNtfySh", " Learn more about ntfy.sh ")}</a>
</small>
{form.ntfyTopic && !/^[a-zA-Z0-9_-]{1,64}$/.test(form.ntfyTopic) && (<small className="field-error">{t("settings.notifications.topicMustBe164AlphanumericHyphenOr", " Topic must be 1\u201364 alphanumeric, hyphen, or underscore characters ")}</small>)}
@@ -108,13 +109,13 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
const value = e.target.value;
setForm((f) => ({ ...f, ntfyBaseUrl: value || undefined }));
}}/>
<small>{t("settings.notifications.leaveBlankToKeepTheDefaultServerHttps", " Leave blank to keep the default server: https://ntfy.sh. Custom servers must use http:// or https://. ")}</small>
<small>{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. ")}</small>
<label htmlFor="ntfyAccessToken">{t("settings.notifications.accessTokenOptional", "Access token (optional)")}</label>
<input id="ntfyAccessToken" type="password" autoComplete="off" placeholder={t("settings.notifications.tk", "tk_...")} value={form.ntfyAccessToken || ""} onChange={(e) => {
const value = e.target.value;
setForm((f) => ({ ...f, ntfyAccessToken: value || undefined }));
}}/>
<small>{t("settings.notifications.leaveBlankToPublishWithoutAuthenticationWhenSet", " Leave blank to publish without authentication. When set, Fusion sends an Authorization Bearer header with ntfy requests. ")}</small>
<small>{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. ")}</small>
</div>
</details>
</div>
@@ -145,7 +146,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
const val = e.target.value;
setForm((f) => ({ ...f, ntfyDashboardHost: val || undefined }));
}}/>
<small>{t("settings.notifications.baseURLForDeepLinksInNotificationsWhen", " Base URL for deep links in notifications. When set, clicking a notification opens the dashboard directly to the task. ")}</small>
<small>{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. ")}</small>
{form.ntfyDashboardHost && !/^https?:\/\/.+/.test(form.ntfyDashboardHost) && (<small className="field-error">{t("settings.notifications.mustBeAValidURLStartingWithHttp", " Must be a valid URL starting with http:// or https:// ")}</small>)}
</div>
<div className="notification-provider-actions">
@@ -190,6 +191,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
<strong>{t("settings.notifications.webhook", "Webhook")}</strong>
<label htmlFor="webhookEnabled" className="checkbox-label">
<input id="webhookEnabled" type="checkbox" checked={form.webhookEnabled || false} onChange={(e) => setForm((f) => ({ ...f, webhookEnabled: e.target.checked }))}/>{t("settings.notifications.webhookNotifications", " Webhook notifications ")}</label>
<small>{t("settings.notifications.webhookEnabledHint", "Default: disabled.")}</small>
</div>
{form.webhookEnabled && (<div className="notification-provider-body">
<div className="form-group">
@@ -198,6 +200,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
const val = e.target.value;
setForm((f) => ({ ...f, webhookUrl: val || undefined }));
}}/>
<small>{t("settings.notifications.webhookUrlHint", "No default \u2014 unset.")}</small>
</div>
<div className="form-group">
<label htmlFor="webhookFormat">{t("settings.notifications.format", "Format")}</label>
@@ -209,6 +212,7 @@ export function NotificationsSection({ scopeBanner, form, setForm, testNotificat
<option value="discord">{t("settings.notifications.discord", "Discord")}</option>
<option value="generic">{t("settings.notifications.generic", "Generic")}</option>
</select>
<small>{t("settings.notifications.webhookFormatHint", "Default: generic.")}</small>
</div>
<div className="form-group">
<label>{t("settings.notifications.notifyOnEvents", "Notify on events")}</label>

View File

@@ -286,7 +286,7 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje
}}/>
{form.tokenCap != null && (<button type="button" className="btn btn-ghost btn-sm" title={t("settings.projectModels.resetToDefaultNoCap", "Reset to default (no cap)")} onClick={() => setForm((f) => ({ ...f, tokenCap: null } as unknown as SettingsFormState))} style={{ whiteSpace: "nowrap" }}>{t("settings.projectModels.reset", " Reset ")}</button>)}
</div>
<small>{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.")}</small>
<small>{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).")}</small>
</div>
{/* --- Project Model Lanes --- */}
@@ -455,6 +455,7 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje
<div className="form-group settings-preset-auto-select">
<label htmlFor="autoSelectModelPreset" className="checkbox-label">
<input id="autoSelectModelPreset" type="checkbox" checked={form.autoSelectModelPreset || false} onChange={(e) => setForm((current) => ({ ...current, autoSelectModelPreset: e.target.checked }))}/>{t("settings.projectModels.autoSelectPresetBasedOnTaskSize", " Auto-select preset based on task size ")}</label>
<small>{t("settings.projectModels.autoSelectModelPresetHint", "Default: disabled.")}</small>
</div>
{form.autoSelectModelPreset ? (<div className="settings-preset-size-grid">
@@ -484,13 +485,13 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje
<div className="form-group">
<label htmlFor="autoSummarizeTitles" className="checkbox-label">
<input id="autoSummarizeTitles" type="checkbox" checked={form.autoSummarizeTitles || false} onChange={(e) => setForm((f) => ({ ...f, autoSummarizeTitles: e.target.checked }))}/>{t("settings.projectModels.autoSummarizeLongDescriptionsAsTitles", " Auto-summarize long descriptions as titles ")}</label>
<small>{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. ")}</small>
<small>{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. ")}</small>
</div>
<div className="form-group">
<label htmlFor="useAiMergeCommitSummary" className="checkbox-label">
<input id="useAiMergeCommitSummary" type="checkbox" checked={form.useAiMergeCommitSummary || false} onChange={(e) => setForm((f) => ({ ...f, useAiMergeCommitSummary: e.target.checked }))}/>{t("settings.projectModels.aIMergeCommitSummaries", " AI merge commit summaries ")}</label>
<small>{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. ")}</small>
<small>{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. ")}</small>
</div>
{(form.autoSummarizeTitles || form.useAiMergeCommitSummary || form.githubTrackingEnabledByDefault || false) && (<p className="settings-description">
@@ -500,13 +501,13 @@ export function ProjectModelsSection({ scopeBanner, form, setForm, models, proje
<div className="form-group">
<label htmlFor="prTitlePromptInstructions">{t("settings.projectModels.prTitlePromptInstructions", "PR title prompt guidance")}</label>
<textarea id="prTitlePromptInstructions" value={form.prTitlePromptInstructions || ""} onChange={(e) => setForm((f) => ({ ...f, prTitlePromptInstructions: e.target.value }))} rows={3} placeholder={t("settings.projectModels.prTitlePromptInstructionsPlaceholder", "Example: Use conventional-commit style and keep titles under 72 characters.")}/>
<small>{t("settings.projectModels.prTitlePromptInstructionsHelp", "Guides the AI-generated Create PR title. Leave blank to use the default PR metadata prompt.")}</small>
<small>{t("settings.projectModels.prTitlePromptInstructionsHelp", "Guides the AI-generated Create PR title. Leave blank to use the default PR metadata prompt. No default \u2014 unset.")}</small>
</div>
<div className="form-group">
<label htmlFor="prDescriptionPromptInstructions">{t("settings.projectModels.prDescriptionPromptInstructions", "PR description prompt guidance")}</label>
<textarea id="prDescriptionPromptInstructions" value={form.prDescriptionPromptInstructions || ""} onChange={(e) => setForm((f) => ({ ...f, prDescriptionPromptInstructions: e.target.value }))} rows={4} placeholder={t("settings.projectModels.prDescriptionPromptInstructionsPlaceholder", "Example: Emphasize operator-facing behavior and list verification commands exactly.")}/>
<small>{t("settings.projectModels.prDescriptionPromptInstructionsHelp", "Guides the AI-generated Create PR summary, changes, and testing sections. Leave blank to use the default PR metadata prompt.")}</small>
<small>{t("settings.projectModels.prDescriptionPromptInstructionsHelp", "Guides the AI-generated Create PR summary, changes, and testing sections. Leave blank to use the default PR metadata prompt. No default \u2014 unset.")}</small>
</div>
</>);
}

View File

@@ -31,7 +31,7 @@ export function PromptsSection({ scopeBanner, form, setForm, onOpenWorkflowSetti
<small>
{t(
"settings.prompts.surfaceExplanation",
"Use this section for agent role system prompt templates, role assignments, and global PromptKey segment overrides. Per-workflow step prompts for prompt and gate nodes are edited in the Workflow Editor.",
"Use this section for agent role system prompt templates, role assignments, and global PromptKey segment overrides. Per-workflow step prompts for prompt and gate nodes are edited in the Workflow Editor. No default \u2014 unset (built-in role prompts apply until overridden).",
)}
</small>
</div>

View File

@@ -187,10 +187,11 @@ export function RemoteSection({ scopeBanner, form, setForm, remote }: RemoteSect
<small>{t("settings.remote.tailscaleFunnelWillExposeThisDashboardOnYour", "Tailscale Funnel will expose this dashboard on your tailnet's public ")}{`https://<machine>.<tailnet>.ts.net/`}{t("settings.remote.uRLNoHostnameOrPortConfigurationNeeded", " URL \u2014 no hostname or port configuration needed.")}</small>
<label htmlFor="remoteTailscaleAcceptRoutes" className="checkbox-label">
<input id="remoteTailscaleAcceptRoutes" type="checkbox" checked={Boolean(remoteForm.remoteTailscaleAcceptRoutes)} onChange={(e) => setForm((f) => ({ ...f, remoteTailscaleAcceptRoutes: e.target.checked } as SettingsFormState))}/>{t("settings.remote.acceptRoutes", " Accept routes ")}</label>
<small>{t("settings.remote.acceptRoutesHint", "Default: disabled.")}</small>
</>) : (<>
<small>
{(remoteForm.remoteCloudflareQuickTunnel ?? true)
? t("settings.remote.usingQuickTunnel", "Using Quick Tunnel — automatically creates a random trycloudflare.com URL, no account needed.")
? t("settings.remote.usingQuickTunnel", "Using Quick Tunnel — automatically creates a random trycloudflare.com URL, no account needed. Default: enabled.")
: t("settings.remote.namedTunnelModeEnabled", "Named Tunnel mode enabled — configure tunnel name, token, and ingress URL below.")}
</small>
<details className="remote-cf-advanced-details" open={!(remoteForm.remoteCloudflareQuickTunnel ?? true)} onToggle={(event) => {
@@ -265,14 +266,16 @@ export function RemoteSection({ scopeBanner, form, setForm, remote }: RemoteSect
<div className="form-group">
<label htmlFor="remoteShortLivedEnabled" className="checkbox-label">
<input id="remoteShortLivedEnabled" type="checkbox" checked={Boolean(remoteForm.remoteShortLivedEnabled)} onChange={(e) => setForm((f) => ({ ...f, remoteShortLivedEnabled: e.target.checked } as SettingsFormState))}/>{t("settings.remote.enableShortLivedTokens", " Enable short-lived tokens ")}</label>
<small>{t("settings.remote.shortLivedEnabledHint", "Default: disabled.")}</small>
<label htmlFor="remoteShortLivedTtlMs">{t("settings.remote.shortLivedTTLMs", "Short-lived TTL (ms)")}</label>
<input id="remoteShortLivedTtlMs" type="number" min={60000} max={86400000} value={Number(remoteForm.remoteShortLivedTtlMs ?? 900000)} onChange={(e) => setForm((f) => ({ ...f, remoteShortLivedTtlMs: Number(e.target.value || 900000) } as SettingsFormState))}/>
<small>{t("settings.remote.shortLivedTtlMsHint", "Default: 900000 (15 minutes).")}</small>
{remoteShortLivedToken && <small>{t("settings.remote.lastShortLivedTokenExpiresAt", "Last short-lived token expires at ")}{new Date(remoteShortLivedToken.expiresAt).toLocaleString()} ({remoteShortLivedToken.ttlMs}{t("settings.remote.ms", "ms)")}</small>}
</div>
<div className="form-group">
<label htmlFor="remoteRememberLastRunning" className="checkbox-label">
<input id="remoteRememberLastRunning" type="checkbox" checked={Boolean(remoteForm.remoteRememberLastRunning)} onChange={(e) => setForm((f) => ({ ...f, remoteRememberLastRunning: e.target.checked } as SettingsFormState))}/>{t("settings.remote.rememberLastRunningState", " Remember last running state ")}</label>
<small>{t("settings.remote.automaticallyRestoreTunnelOnStartupIfItWas", "Automatically restore tunnel on startup if it was running when last stopped.")}</small>
<small>{t("settings.remote.automaticallyRestoreTunnelOnStartupIfItWas", "Automatically restore tunnel on startup if it was running when last stopped. Default: disabled.")}</small>
</div>
<div className="form-group">
<label>{t("settings.remote.authLinks", "Auth Links")}</label>

View File

@@ -37,8 +37,8 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
<h4 className="settings-section-heading">{t("settings.researchGlobal.researchDefaults", "Research Defaults")}</h4>
<div className="form-group settings-research-provider-group">
<label htmlFor="research-global-provider-builtin" className="checkbox-label">
<input id="research-global-provider-builtin" type="radio" name="research-global-search-provider" checked={!externalProvider} onChange={() => setSearchProvider("builtin")}/>{t("settings.researchGlobal.builtInUsesAgentWebTools", " Built-in (uses agent web tools) ")}</label>
<small>{t("settings.researchGlobal.searchesAndFetchesUseTheAgentsNativeWebSearch", " Searches and fetches use the agent's native WebSearch/WebFetch tools. No API key required. ")}</small>
<input id="research-global-provider-builtin" type="radio" name="research-global-search-provider" checked={!externalProvider} onChange={() => setSearchProvider("builtin")}/>{t("settings.researchGlobal.builtInUsesAgentWebTools", " Built-in (uses agent web tools) (default) ")}</label>
<small>{t("settings.researchGlobal.searchesAndFetchesUseTheAgentsNativeWebSearch", " Searches and fetches use the agent's native WebSearch/WebFetch tools. No API key required. Default: builtin. ")}</small>
<details className="settings-option-details settings-research-provider-advanced-details">
<summary>{t("settings.researchGlobal.advancedExternalSearchProviders", "Advanced \u2014 external search providers")}</summary>
<div className="settings-research-provider-advanced-body">
@@ -57,6 +57,7 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
...current,
researchGlobalSearxngUrl: event.target.value || undefined,
}))} placeholder={t("settings.researchGlobal.httpsSearxExampleCom", "https://searx.example.com")}/>
<small>{t("settings.researchGlobal.searXNGURLHint", "No default \u2014 unset.")}</small>
</div>
<div className="form-group">
<label htmlFor="research-global-google-cx">{t("settings.researchGlobal.googleSearchCX", "Google Search CX")}</label>
@@ -64,6 +65,7 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
...current,
researchGlobalGoogleSearchCx: event.target.value || undefined,
}))} placeholder={t("settings.researchGlobal.customSearchEngineId", "custom-search-engine-id")}/>
<small>{t("settings.researchGlobal.googleSearchCXHint", "No default \u2014 unset.")}</small>
</div>
<div className="settings-empty-state settings-research-empty-state" role="note">{t("settings.researchGlobal.configureBraveTavilyAndGoogleAPIKeysIn", " Configure Brave, Tavily, and Google API keys in Authentication. ")}<button type="button" className="btn btn-sm" onClick={() => onNavigateToSection("authentication")}>{t("settings.researchGlobal.openAuthenticationSettings", " Open Authentication Settings ")}</button>
</div>
@@ -78,6 +80,7 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
...current,
researchGlobalMaxConcurrentRuns: event.target.value === "" ? undefined : Number(event.target.value),
}))}/>
<small>{t("settings.researchGlobal.maxConcurrentRunsHint", "Default: 3.")}</small>
</div>
<div className="settings-research-limit-field">
<label htmlFor="research-global-max-sources">{t("settings.researchGlobal.defaultMaxSourcesPerRun", "Default Max Sources Per Run")}</label>
@@ -89,6 +92,7 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
maxSourcesPerRun: event.target.value === "" ? undefined : Number(event.target.value),
},
}))}/>
<small>{t("settings.researchGlobal.maxSourcesPerRunHint", "Default: 20.")}</small>
</div>
<div className="settings-research-limit-field">
<label htmlFor="research-global-default-timeout">{t("settings.researchGlobal.defaultMaxDurationMs", "Default Max Duration (ms)")}</label>
@@ -96,6 +100,7 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
...current,
researchGlobalDefaultTimeout: event.target.value === "" ? undefined : Number(event.target.value),
}))}/>
<small>{t("settings.researchGlobal.defaultMaxDurationMsHint", "Default: 300000 (5 minutes).")}</small>
</div>
<div className="settings-research-limit-field">
<label htmlFor="research-global-fetch-timeout">{t("settings.researchGlobal.requestTimeoutMs", "Request Timeout (ms)")}</label>
@@ -103,6 +108,7 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
...current,
researchGlobalFetchTimeoutMs: event.target.value === "" ? undefined : Number(event.target.value),
}))}/>
<small>{t("settings.researchGlobal.requestTimeoutMsHint", "Default: 30000 (30 seconds).")}</small>
</div>
<div className="settings-research-limit-field">
<label htmlFor="research-global-max-synthesis-rounds">{t("settings.researchGlobal.maxSynthesisRounds", "Max Synthesis Rounds")}</label>
@@ -110,6 +116,7 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
...current,
researchGlobalMaxSynthesisRounds: event.target.value === "" ? undefined : Number(event.target.value),
}))}/>
<small>{t("settings.researchGlobal.maxSynthesisRoundsHint", "Default: 2.")}</small>
</div>
</div>
</div>
@@ -123,12 +130,12 @@ export function ResearchGlobalSection({ scopeBanner, form, setForm, authProvider
<input id="research-global-source-github" type="checkbox" checked={form.researchGlobalGitHubEnabled ?? false} onChange={(event) => setForm((current) => ({
...current,
researchGlobalGitHubEnabled: event.target.checked,
}))}/>{t("settings.researchGlobal.gitHub", " GitHub ")}</label>
}))}/>{t("settings.researchGlobal.gitHub", " GitHub ")}<small>{t("settings.researchGlobal.gitHubSourceHint", " Default: disabled. ")}</small></label>
<label htmlFor="research-global-source-local-docs" className="checkbox-label">
<input id="research-global-source-local-docs" type="checkbox" checked={form.researchGlobalLocalDocsEnabled ?? true} onChange={(event) => setForm((current) => ({
...current,
researchGlobalLocalDocsEnabled: event.target.checked,
}))}/>{t("settings.researchGlobal.localDocs", " Local Docs ")}</label>
}))}/>{t("settings.researchGlobal.localDocs", " Local Docs ")}<small>{t("settings.researchGlobal.localDocsSourceHint", " Default: enabled. ")}</small></label>
</div>
</div>
{hasMissingResearchCredential && (<div className="settings-empty-state" role="alert">{t("settings.researchGlobal.missingCredentialsForTheSelectedResearchProvider", " Missing credentials for the selected research provider. ")}<button type="button" className="btn btn-sm" onClick={() => onNavigateToSection("authentication")}>{t("settings.researchGlobal.openAuthentication", " Open Authentication ")}</button>

View File

@@ -21,6 +21,7 @@ export function ResearchProjectSection({ scopeBanner, form, setForm, researchLim
enabled: event.target.checked,
},
}))}/>{t("settings.researchProject.enableResearchInThisProject", " Enable research in this project ")}</label>
<small>{t("settings.researchProject.enableResearchInThisProjectHint", "Default: enabled.")}</small>
</div>
<div className="form-group">
<label>{t("settings.researchProject.enabledSources", "Enabled Sources")}</label>
@@ -30,11 +31,11 @@ export function ResearchProjectSection({ scopeBanner, form, setForm, researchLim
<small className="settings-muted">{t("settings.researchProject.webSearchIsAlwaysEnabledConfigureTheSearch", " Web search is always enabled. Configure the search provider under Research Defaults. ")}</small>
<div className="settings-research-source-grid">
{[
["pageFetch", t("settings.researchProject.pageFetch", "Page Fetch")],
["github", t("settings.researchProject.github", "GitHub")],
["localDocs", t("settings.researchProject.localDocs", "Local Docs")],
["llmSynthesis", t("settings.researchProject.llmSynthesis", "LLM Synthesis")],
].map(([key, label]) => (<label key={key} htmlFor={`research-project-source-${key}`} className="checkbox-label">
["pageFetch", t("settings.researchProject.pageFetch", "Page Fetch"), "Default: enabled."],
["github", t("settings.researchProject.github", "GitHub"), "Default: disabled."],
["localDocs", t("settings.researchProject.localDocs", "Local Docs"), "Default: enabled."],
["llmSynthesis", t("settings.researchProject.llmSynthesis", "LLM Synthesis"), "Default: enabled."],
].map(([key, label, defaultHint]) => (<label key={key} htmlFor={`research-project-source-${key}`} className="checkbox-label">
<input id={`research-project-source-${key}`} type="checkbox" checked={sources?.[key as keyof NonNullable<typeof sources>] ?? false} onChange={(event) => setForm((current) => ({
...current,
researchSettings: {
@@ -46,6 +47,7 @@ export function ResearchProjectSection({ scopeBanner, form, setForm, researchLim
},
}))}/>
{label}
<small>{defaultHint}</small>
</label>))}
</div>
</div>
@@ -63,6 +65,7 @@ export function ResearchProjectSection({ scopeBanner, form, setForm, researchLim
},
},
}))}/>
<small>{t("settings.researchProject.maxConcurrentRunsHint", "Default: 3.")}</small>
</div>
<div className="settings-research-limit-field">
<label htmlFor="research-project-max-sources">{t("settings.researchProject.maxSourcesPerRun", "Max Sources Per Run")}</label>
@@ -76,6 +79,7 @@ export function ResearchProjectSection({ scopeBanner, form, setForm, researchLim
},
},
}))}/>
<small>{t("settings.researchProject.maxSourcesPerRunHint", "Default: 20.")}</small>
</div>
<div className="settings-research-limit-field">
<label htmlFor="research-project-max-duration">{t("settings.researchProject.maxDurationMs", "Max Duration (ms)")}</label>
@@ -89,6 +93,7 @@ export function ResearchProjectSection({ scopeBanner, form, setForm, researchLim
},
},
}))}/>
<small>{t("settings.researchProject.maxDurationMsHint", "Default: 300000 (5 minutes).")}</small>
</div>
<div className="settings-research-limit-field">
<label htmlFor="research-project-request-timeout">{t("settings.researchProject.requestTimeoutMs", "Request Timeout (ms)")}</label>
@@ -102,6 +107,7 @@ export function ResearchProjectSection({ scopeBanner, form, setForm, researchLim
},
},
}))}/>
<small>{t("settings.researchProject.requestTimeoutMsHint", "Default: 30000 (30 seconds).")}</small>
</div>
{researchLimitError && <small className="field-error settings-research-limits-error">{researchLimitError}</small>}
</div>

View File

@@ -20,6 +20,7 @@ export function ScheduledEvalsSection({ scopeBanner, form, setForm }: ScheduledE
enabled: event.target.checked,
},
}))}/>{t("settings.scheduledEvals.enableScheduledEvalRunsForThisProject", " Enable scheduled eval runs for this project ")}</label>
<small>{t("settings.scheduledEvals.enabledHint", "Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="scheduled-evals-interval">{t("settings.scheduledEvals.intervalMs", "Interval (ms)")}</label>
@@ -30,6 +31,7 @@ export function ScheduledEvalsSection({ scopeBanner, form, setForm }: ScheduledE
intervalMs: event.target.value === "" ? undefined : Number(event.target.value),
},
}))}/>
<small>{t("settings.scheduledEvals.intervalMsHint", "Default: 86400000 (24 hours).")}</small>
</div>
<div className="form-group">
<label htmlFor="scheduled-evals-provider">{t("settings.scheduledEvals.evaluatorProvider", "Evaluator Provider")}</label>
@@ -40,6 +42,7 @@ export function ScheduledEvalsSection({ scopeBanner, form, setForm }: ScheduledE
evaluatorProvider: event.target.value.trim() === "" ? undefined : event.target.value,
},
}))} placeholder={t("settings.scheduledEvals.openai", "openai")}/>
<small>{t("settings.scheduledEvals.evaluatorProviderHint", "No default \u2014 unset (inherits the project validator lane provider).")}</small>
</div>
<div className="form-group">
<label htmlFor="scheduled-evals-model">{t("settings.scheduledEvals.evaluatorModel", "Evaluator Model")}</label>
@@ -50,7 +53,7 @@ export function ScheduledEvalsSection({ scopeBanner, form, setForm }: ScheduledE
evaluatorModelId: event.target.value.trim() === "" ? undefined : event.target.value,
},
}))} placeholder={t("settings.scheduledEvals.gpt5", "gpt-5")}/>
<small className="form-text text-muted">{t("settings.scheduledEvals.leaveProviderAndModelBlankToInheritThe", " Leave provider and model blank to inherit the project validator lane model settings. ")}</small>
<small className="form-text text-muted">{t("settings.scheduledEvals.leaveProviderAndModelBlankToInheritThe", " Leave provider and model blank to inherit the project validator lane model settings. No default \u2014 unset. ")}</small>
</div>
<div className="form-group">
<label htmlFor="scheduled-evals-follow-up-policy">{t("settings.scheduledEvals.followUpPolicy", "Follow-up Policy")}</label>
@@ -65,6 +68,7 @@ export function ScheduledEvalsSection({ scopeBanner, form, setForm }: ScheduledE
<option value="suggest-only">{t("settings.scheduledEvals.suggestOnly", "Suggest only")}</option>
<option value="auto-create">{t("settings.scheduledEvals.autoCreateTasks", "Auto-create tasks")}</option>
</select>
<small>{t("settings.scheduledEvals.followUpPolicyHint", "Default: suggest only.")}</small>
</div>
<div className="form-group">
<label htmlFor="scheduled-evals-retention-days">{t("settings.scheduledEvals.retentionDays", "Retention (days)")}</label>
@@ -75,6 +79,7 @@ export function ScheduledEvalsSection({ scopeBanner, form, setForm }: ScheduledE
retentionDays: event.target.value === "" ? undefined : Number(event.target.value),
},
}))}/>
<small>{t("settings.scheduledEvals.retentionDaysHint", "Default: 30.")}</small>
</div>
</>);
}

View File

@@ -52,7 +52,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
const val = e.target.value;
onGlobalMaxConcurrentChange(val === "" ? undefined : Number(val));
}}/>
<small className="form-text text-muted">{t("settings.scheduling.maximumConcurrentAgentsAcrossAllProjects", "Maximum concurrent agents across all projects")}</small>
<small className="form-text text-muted">{t("settings.scheduling.maximumConcurrentAgentsAcrossAllProjects", "Maximum concurrent agents across all projects. Default: 4.")}</small>
</div>
<div className="settings-section-divider"/>
<ScopeGroupHeader scope="project" title={t("settings.scheduling.scopeProjectTitle", "This project")} caption={t("settings.scheduling.scopeProjectCaption", "Only affects the currently selected project.")} badgeLabel={t("settings.scheduling.scopeBadgeProject", "Project")}/>
@@ -62,6 +62,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
const val = e.target.value;
setForm((f) => ({ ...f, maxConcurrent: val === "" ? undefined : Number(val) } as SettingsFormState));
}}/>
<small>{t("settings.scheduling.maxConcurrentTasksHint", "Default: 2.")}</small>
</div>
<div className="form-group">
<label htmlFor="maxTriageConcurrent">{t("settings.scheduling.maxTriageConcurrent", "Max Triage Concurrent")}</label>
@@ -69,7 +70,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
const val = e.target.value;
setForm((f) => ({ ...f, maxTriageConcurrent: val === "" ? undefined : Number(val) } as SettingsFormState));
}}/>
<small>{t("settings.scheduling.maximumConcurrentPlanningAgents", "Maximum concurrent planning agents")}</small>
<small>{t("settings.scheduling.maximumConcurrentPlanningAgents", "Maximum concurrent planning agents. Default: 2.")}</small>
</div>
<div className="form-group">
<label htmlFor="pollIntervalMs">{t("settings.scheduling.pollIntervalMs", "Poll Interval (ms)")}</label>
@@ -77,6 +78,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
const val = e.target.value;
setForm((f) => ({ ...f, pollIntervalMs: val === "" ? undefined : Number(val) } as SettingsFormState));
}}/>
<small>{t("settings.scheduling.pollIntervalMsHint", "Default: 15000 (15 seconds).")}</small>
</div>
<div className="form-group">
<label htmlFor="heartbeatScopeDiscipline">{t("settings.scheduling.heartbeatScopeDiscipline", "Heartbeat Scope Discipline")}</label>
@@ -104,7 +106,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
const num = Number(val);
setForm((f) => ({ ...f, taskStuckTimeoutMs: val && num > 0 ? num * 60000 : undefined }));
}}/>
<small>{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.")}</small>
<small>{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. Default: 10 minutes (600000ms).")}</small>
</div>
<div className="form-group">
<label htmlFor="staleHighFanoutBlockerAgeThresholdMs">{t("settings.scheduling.staleHighFanOutEscalationHours", "Stale High Fan-out Escalation (hours)")}</label>
@@ -126,7 +128,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
<div className="form-group">
<label htmlFor="specStalenessEnabled" className="checkbox-label">
<input id="specStalenessEnabled" type="checkbox" checked={form.specStalenessEnabled || false} onChange={(e) => setForm((f) => ({ ...f, specStalenessEnabled: e.target.checked }))}/>{t("settings.scheduling.enablePlanStalenessEnforcement", " Enable plan staleness enforcement ")}</label>
<small>{t("settings.scheduling.whenEnabledTasksWithStalePlansPROMPTMd", "When enabled, tasks with stale plans (PROMPT.md older than the threshold) are automatically sent back to planning for replanning")}</small>
<small>{t("settings.scheduling.whenEnabledTasksWithStalePlansPROMPTMd", "When enabled, tasks with stale plans (PROMPT.md older than the threshold) are automatically sent back to planning for replanning. Default: disabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="specStalenessMaxAgeMs">{t("settings.scheduling.staleSpecThresholdHours", "Stale Spec Threshold (hours)")}</label>
@@ -143,7 +145,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
...f,
autoArchiveDoneTasksEnabled: e.target.checked,
}))}/>{t("settings.scheduling.enableAutomaticTaskArchiving", " Enable automatic task archiving ")}</label>
<small>{t("settings.scheduling.completedTasksOlderThanTheThresholdAreMoved", "Completed tasks older than the threshold are moved out of the active task database.")}</small>
<small>{t("settings.scheduling.completedTasksOlderThanTheThresholdAreMoved", "Completed tasks older than the threshold are moved out of the active task database. Default: enabled.")}</small>
</div>
<div className="form-group">
<label htmlFor="autoArchiveDoneAfterMs">{t("settings.scheduling.archiveCompletedTasksAfterDays", "Archive Completed Tasks After (days)")}</label>
@@ -167,7 +169,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
<option value="none">{t("settings.scheduling.doNotArchiveAgentLogs", "Do not archive agent logs")}</option>
<option value="full">{t("settings.scheduling.fullAgentLog", "Full agent log")}</option>
</select>
<small>{t("settings.scheduling.compactModeKeepsArchiveSizeLowWhilePreserving", "Compact mode keeps archive size low while preserving recent agent activity for context.")}</small>
<small>{t("settings.scheduling.compactModeKeepsArchiveSizeLowWhilePreserving", "Compact mode keeps archive size low while preserving recent agent activity for context. Default: compact.")}</small>
</div>
<div className="form-group">
<label htmlFor="maxStuckKills">{t("settings.scheduling.maxStuckRetries", "Max Stuck Retries")}</label>
@@ -181,7 +183,7 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
<div className="form-group">
<label htmlFor="groupOverlappingFiles" className="checkbox-label">
<input id="groupOverlappingFiles" type="checkbox" checked={form.groupOverlappingFiles} onChange={(e) => setForm((f) => ({ ...f, groupOverlappingFiles: e.target.checked }))}/>{t("settings.scheduling.serializeTasksWithOverlappingFiles", " Serialize tasks with overlapping files ")}</label>
<small>{t("settings.scheduling.whenEnabledTasksThatModifyTheSameFiles", "When enabled, tasks that modify the same files are queued serially to avoid merge conflicts")}</small>
<small>{t("settings.scheduling.whenEnabledTasksThatModifyTheSameFiles", "When enabled, tasks that modify the same files are queued serially to avoid merge conflicts. Default: enabled.")}</small>
</div>
{/**
@@ -191,12 +193,12 @@ export function SchedulingSection({ scopeBanner, form, setForm, globalMaxConcurr
<div className="form-group">
<label htmlFor="ignoreHiddenOverlapPaths" className="checkbox-label">
<input id="ignoreHiddenOverlapPaths" type="checkbox" checked={form.ignoreHiddenOverlapPaths !== false} onChange={(e) => setForm((f) => ({ ...f, ignoreHiddenOverlapPaths: e.target.checked }))}/>{t("settings.scheduling.ignoreHiddenDotPathsInOverlapChecks", " Ignore hidden dot paths in overlap checks ")}</label>
<small>{t("settings.scheduling.ignoreHiddenDotPathsHelp", "When enabled, overlap checks ignore hidden path segments such as .fusion/, .changeset/, .github/, .env, and nested .cache/ directories. Uncheck to restore legacy counting for stricter serialization.")}</small>
<small>{t("settings.scheduling.ignoreHiddenDotPathsHelp", "When enabled, overlap checks ignore hidden path segments such as .fusion/, .changeset/, .github/, .env, and nested .cache/ directories. Uncheck to restore legacy counting for stricter serialization. Default: enabled.")}</small>
</div>
<div className="form-group settings-overlap-ignore-group">
<label>{t("settings.scheduling.ignoredOverlapPaths", "Ignored overlap paths")}</label>
<small>{t("settings.scheduling.optionalFileOrDirectoryPathsToIgnoreWhen", " Optional file or directory paths to ignore when overlap serialization is enabled. Paths are project-relative (for example ")}<code>docs/</code>{t("settings.scheduling.or", " or ")}<code>generated/*</code>{t("settings.scheduling.closeParenPeriod", ").")}
<small>{t("settings.scheduling.optionalFileOrDirectoryPathsToIgnoreWhen", " No default \u2014 unset (empty). Optional file or directory paths to ignore when overlap serialization is enabled. Paths are project-relative (for example ")}<code>docs/</code>{t("settings.scheduling.or", " or ")}<code>generated/*</code>{t("settings.scheduling.closeParenPeriod", ").")}
</small>
<div className="settings-overlap-ignore-list">
{(form.overlapIgnorePaths && form.overlapIgnorePaths.length > 0 ? form.overlapIgnorePaths : [""]).map((path, index) => (<div key={`overlap-ignore-${index}`} className="settings-overlap-ignore-row">

View File

@@ -27,12 +27,12 @@ export function WorktreesSection({ scopeBanner, form, setForm, gitRemotes, workt
const val = e.target.value;
setForm((f) => ({ ...f, maxWorktrees: val === "" ? undefined : Number(val) } as SettingsFormState));
}}/>
<small>{t("settings.worktrees.limitsTotalGitWorktreesIncludingInReviewTasks", "Limits total git worktrees including in-review tasks")}</small>
<small>{t("settings.worktrees.limitsTotalGitWorktreesIncludingInReviewTasks", "Limits total git worktrees including in-review tasks. Default: 4.")}</small>
</div>
<div className="form-group">
<label htmlFor="worktreeInitCommand">{t("settings.worktrees.worktreeInitCommand", "Worktree Init Command")}</label>
<input id="worktreeInitCommand" type="text" placeholder={t("settings.worktrees.pnpmInstallFrozenLockfile", "pnpm install --frozen-lockfile")} value={form.worktreeInitCommand || ""} onChange={(e) => setForm((f) => ({ ...f, worktreeInitCommand: e.target.value }))}/>
<small>{t("settings.worktrees.shellCommandToRunInEachNewWorktree", "Shell command to run in each new worktree after creation")}</small>
<small>{t("settings.worktrees.shellCommandToRunInEachNewWorktree", "Shell command to run in each new worktree after creation. No default \u2014 unset.")}</small>
</div>
<div className="form-group">
<label htmlFor="recycleWorktrees" className="checkbox-label">
@@ -86,12 +86,12 @@ export function WorktreesSection({ scopeBanner, form, setForm, gitRemotes, workt
<button type="button" className="btn btn-sm" onClick={onAddWorktreeCopyFile}>
{t("settings.worktrees.addCopyFile", "Add file")}
</button>
<small>{t("settings.worktrees.copyFilesHelp", "Optional. Repository-root-relative regular files are copied into fresh or pooled task worktrees before init commands run. Missing files or directories are skipped without exposing contents.")}</small>
<small>{t("settings.worktrees.copyFilesHelp", "Optional. Repository-root-relative regular files are copied into fresh or pooled task worktrees before init commands run. Missing files or directories are skipped without exposing contents. Default: empty (no files copied).")}</small>
</div>
<div className="form-group">
<label htmlFor="executorAllowSiblingBranchRename" className="checkbox-label">
<input id="executorAllowSiblingBranchRename" type="checkbox" checked={form.executorAllowSiblingBranchRename === true} onChange={(e) => setForm((f) => ({ ...f, executorAllowSiblingBranchRename: e.target.checked }))}/>{t("settings.worktrees.allowSilentSiblingBranchRenameDuringExecutorConflicts", " Allow silent sibling branch rename during executor conflicts ")}</label>
<small>{t("settings.worktrees.discouragedThisRestoresTheLegacyBehaviorWhereA", " Discouraged. This restores the legacy behavior where a live ")}<code>fusion/&lt;task-id&gt;</code>{t("settings.worktrees.branchCollisionSilentlyForksWorkOntoSiblingBranches", " branch collision silently forks work onto sibling branches like ")}<code>-2</code>{t("settings.worktrees.andCanHidePriorCommitsFromTheDefault", " and can hide prior commits from the default recovery flow. ")}</small>
<small>{t("settings.worktrees.discouragedThisRestoresTheLegacyBehaviorWhereA", " Discouraged. This restores the legacy behavior where a live ")}<code>fusion/&lt;task-id&gt;</code>{t("settings.worktrees.branchCollisionSilentlyForksWorkOntoSiblingBranches", " branch collision silently forks work onto sibling branches like ")}<code>-2</code>{t("settings.worktrees.andCanHidePriorCommitsFromTheDefault", " and can hide prior commits from the default recovery flow. Default: disabled. ")}</small>
</div>
<div className="form-group">
<label htmlFor="worktreeNaming">{t("settings.worktrees.worktreeNamingStyle", "Worktree Naming Style")}</label>
@@ -102,8 +102,8 @@ export function WorktreesSection({ scopeBanner, form, setForm, gitRemotes, workt
</select>
<small>
{form.recycleWorktrees
? "Naming style is not applicable when recycling worktrees — pooled worktrees retain their existing names"
: "How to name fresh worktree directories. Only applies when recycling is off."}
? t("settings.worktrees.namingStyleNotApplicableWhenRecycling", "Naming style is not applicable when recycling worktrees \u2014 pooled worktrees retain their existing names")
: t("settings.worktrees.howToNameFreshWorktreeDirectories", "How to name fresh worktree directories. Only applies when recycling is off. Default: random.")}
</small>
</div>
<div className="form-group">
@@ -121,7 +121,7 @@ export function WorktreesSection({ scopeBanner, form, setForm, gitRemotes, workt
<div className="form-group">
<label htmlFor="worktreeRebaseBeforeMerge" className="checkbox-label">
<input id="worktreeRebaseBeforeMerge" type="checkbox" checked={form.worktreeRebaseBeforeMerge !== false} onChange={(e) => setForm((f) => ({ ...f, worktreeRebaseBeforeMerge: e.target.checked }))}/>{t("settings.worktrees.rebaseFromRemoteBeforeMerge", " Rebase from remote before merge ")}</label>
<small>{t("settings.worktrees.whenEnabledTheMergerFetchesFromTheConfigured", "When enabled, the merger fetches from the configured remote and rebases the task branch onto the latest default-branch tip before merging \u2014 catching concurrent pushes from other collaborators or fusion workers. Any conflicts the rebase surfaces flow into the existing smart/AI resolve pipeline.")}</small>
<small>{t("settings.worktrees.whenEnabledTheMergerFetchesFromTheConfigured", "When enabled, the merger fetches from the configured remote and rebases the task branch onto the latest default-branch tip before merging \u2014 catching concurrent pushes from other collaborators or fusion workers. Any conflicts the rebase surfaces flow into the existing smart/AI resolve pipeline. Default: enabled.")}</small>
</div>
{form.worktreeRebaseBeforeMerge !== false && (<div className="form-group">
<label htmlFor="worktreeRebaseRemote">{t("settings.worktrees.rebaseRemote", "Rebase Remote")}</label>

View File

@@ -68,8 +68,8 @@ describe("AppearanceSection", () => {
const checkbox = screen.getByLabelText("Open tasks as popups");
expect(checkbox).not.toBeChecked();
expect(screen.getByText(/ordinary board task-card clicks open the existing task popup/)).toBeInTheDocument();
expect(screen.getByText(/Deep-tab and non-board task opens keep their current behavior/)).toBeInTheDocument();
expect(screen.getByText(/ordinary board task-card and right-dock Tasks-list clicks open the existing task popup/)).toBeInTheDocument();
expect(screen.getByText(/Deep-tab and other task opens keep their current behavior/)).toBeInTheDocument();
fireEvent.click(checkbox);

View File

@@ -48,7 +48,7 @@ describe("MergeSection legacy auto-merge stamp cleanup", () => {
const select = screen.getByTestId("plan-approval-mode-select") as HTMLSelectElement;
expect(select.value).toBe("require-all");
expect(screen.getByRole("option", { name: "Use workflow setting" })).toBeInTheDocument();
expect(screen.getByRole("option", { name: "Use workflow setting (default)" })).toBeInTheDocument();
expect(screen.getByRole("option", { name: "Auto-approve all tasks" })).toBeInTheDocument();
expect(screen.getByRole("option", { name: "Require approval for all tasks" })).toBeInTheDocument();

View File

@@ -0,0 +1,573 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, DEFAULT_SETTINGS } from "@fusion/core";
import realEnApp from "../../../../../../i18n/locales/en/app.json";
/*
* FNXC:SettingsDefaults 2026-07-04-00:00:
* FN-7505 requires every user-editable setting surfaced in the dashboard Settings
* UI to state its DEFAULT VALUE (or "inherits global" / "no default \u2014 unset")
* in its description/help text. This guard test encodes the invariant two ways:
*
* 1. `SETTING_DESCRIPTION_KEYS` maps each surfaced setting key to the i18n
* `settings.<section>.<key>` description path whose resolved English string
* must mention a default-value indicator (`Default:`, `inherits`, `No default`,
* or a rendered `(default)` option tag). Adding a new surfaced setting without
* adding it here (or to `NOT_SURFACED_ALLOWLIST` with a reason) fails this test.
* 2. `NOT_SURFACED_ALLOWLIST` documents every `DEFAULT_SETTINGS` key that is NOT
* a plain user-editable Settings UI field (moved-to-workflow-settings keys,
* internal/engine bookkeeping, nested config editors delegated to components
* outside `settings/sections/`, session/runtime state, etc.) with a one-line
* reason each, so a genuinely new setting cannot silently skip documentation.
*
* Source of truth for canonical default values: `DEFAULT_GLOBAL_SETTINGS` /
* `DEFAULT_PROJECT_SETTINGS` / `DEFAULT_SETTINGS` in `packages/core/src/settings-schema.ts`.
* See task document "plan" on FN-7505 for the full field \u2192 default \u2192 i18n-key table.
*/
type SettingsDict = Record<string, unknown>;
function resolveDescription(dict: SettingsDict, dottedPath: string): string | undefined {
const parts = dottedPath.split(".");
let node: unknown = dict;
for (const part of parts) {
if (node == null || typeof node !== "object") return undefined;
node = (node as SettingsDict)[part];
}
return typeof node === "string" ? node : undefined;
}
/** Regex matching any of the accepted default-value phrasings for FN-7505. */
const DEFAULT_INDICATOR_RE = /default|inherits|unset/i;
/**
* FNXC:SettingsDefaults 2026-07-04-00:00:
* FN-7505 code review caught GlobalGeneralSection/GeneralSection/MergeSection stating
* `gitlabEnabled` defaults to "enabled" and GlobalModelsSection stating
* `openrouterAppAttribution` defaults to a literal URL/title, when both are actually
* `undefined` in DEFAULT_GLOBAL_SETTINGS/DEFAULT_PROJECT_SETTINGS (settings-schema.ts).
* A generic "mentions the word default" check cannot catch a WRONG default value, only
* a missing one. `resolveCanonicalDefault` + the checks in the third `it()` below assert
* the description's stated default agrees with the actual schema default for every mapped
* setting: booleans must state the correct enabled/disabled (and never the opposite), and
* settings whose canonical default is `undefined` must say so ("no default"/"inherits")
* rather than fabricating a concrete enabled/disabled/value claim.
*/
function resolveCanonicalDefault(settingKey: string): unknown {
const globalDict = DEFAULT_GLOBAL_SETTINGS as SettingsDict;
const projectDict = DEFAULT_PROJECT_SETTINGS as SettingsDict;
const mergedDict = DEFAULT_SETTINGS as unknown as SettingsDict;
if (settingKey in globalDict) return globalDict[settingKey];
if (settingKey in projectDict) return projectDict[settingKey];
return mergedDict[settingKey];
}
/**
* Maps every surfaced user-editable setting key to the i18n path (under the
* `settings` namespace in `packages/i18n/locales/en/app.json`) whose resolved
* English description states that setting's default value.
*/
const SETTING_DESCRIPTION_KEYS: Record<string, string> = {
// GlobalGeneralSection
githubTrackingDefaultRepo: "globalGeneral.projectsInheritThisValueWhenTheyDoNot",
gitlabEnabled: "merge.gitLabAuthDetails",
gitlabInstanceUrl: "globalGeneral.gitLabInstanceUrlHint",
gitlabApiBaseUrl: "globalGeneral.gitLabApiBaseUrlHint",
gitlabAuthTokenType: "globalGeneral.gitLabTokenTypeHint",
gitlabAuthToken: "globalGeneral.gitLabAuthTokenHint",
dismissModalsOnOutsideClick: "globalGeneral.dismissModalsByClickingOutsideHint",
persistAgentToolOutput: "globalGeneral.whenDisabledToolRowsAreStillLoggedBut",
persistAgentThinkingLogPermanent: "globalGeneral.rowsAndDoesNotAffectAssistantTextOr",
persistAgentThinkingLogEphemeral: "globalGeneral.rowsAndDoesNotAffectAssistantTextOr",
fnBinaryCheckEnabled: "globalGeneral.disableThisIfYourLocalDevProcessIs",
updateCheckEnabled: "globalGeneral.andShowsUpdateNoticesInTheCLIAnd",
updateCheckFrequency: "globalGeneral.controlsHowOftenTheDashboardReFetchesThe",
autoReloadOnVersionChange: "globalGeneral.whenEnabledDefaultTheDashboardAutomaticallyReloadsWhen",
// AppearanceSection
openTasksInRightSidebar: "appearance.openTasksInRightSidebarHelp",
openMobileTasksInPopup: "appearance.openMobileTasksInPopupHelp",
taskDetailChatFirst: "appearance.taskDetailChatFirstHelp",
// AgentPermissionsSection
defaultAgentPermissionPolicy: "agentPermissions.perAgentSettingsOverrideProjectDefaultsEachCategory",
agentProvisioning: "agentPermissions.configureProjectLevelApprovalBehaviorForDurableProvisioning",
// GlobalModelsSection
defaultProvider: "globalModels.defaultAIModelUsedForTaskExecutionWhen",
defaultModelId: "globalModels.defaultAIModelUsedForTaskExecutionWhen",
fallbackProvider: "globalModels.usedAutomaticallyIfThePrimaryDefaultModelHits",
fallbackModelId: "globalModels.usedAutomaticallyIfThePrimaryDefaultModelHits",
defaultThinkingLevel: "globalModels.controlsHowMuchReasoningEffortTheAIModel",
openrouterModelSync: "globalModels.whenEnabledStartupFetchesTheLatestAvailableModels",
opencodeGoModelSync: "globalModels.flowAndPublishesThemUnderTheOpencodeGo",
openrouterAppAttribution: "globalModels.leaveEmptyToOmitThisHeaderDefaultHttps",
openrouterModelFilters: "globalModels.commaSeparatedValuesSentToOpenRouterModelSync",
openrouterProviderPreferences: "globalModels.openRouterRoutingOrderHint",
// McpServersCard (global + project MCP sections)
mcpServers: "mcp.enabledHint",
// NodeSyncSection
settingsSyncEnabled: "nodeSync.automaticallySynchronizeSettingsBetweenThisNodeAndConnected",
settingsSyncAuth: "nodeSync.includeAPIKeysAndOAuthTokensInSync",
settingsSyncInterval: "nodeSync.syncIntervalHint",
settingsSyncConflictResolution: "nodeSync.conflictResolutionHint",
// NodeRoutingSection
defaultNodeId: "nodeRouting.usedWhenATaskHasNoNodeOverride",
unavailableNodePolicy: "nodeRouting.unavailableNodePolicyHint",
// ResearchGlobalSection
researchGlobalSearxngUrl: "researchGlobal.searXNGURLHint",
researchGlobalGoogleSearchApiKey: "researchGlobal.googleSearchCXHint",
researchGlobalGoogleSearchCx: "researchGlobal.googleSearchCXHint",
researchGlobalMaxConcurrentRuns: "researchGlobal.maxConcurrentRunsHint",
researchGlobalMaxSourcesPerRun: "researchGlobal.maxSourcesPerRunHint",
researchGlobalDefaultTimeout: "researchGlobal.defaultMaxDurationMsHint",
researchGlobalFetchTimeoutMs: "researchGlobal.requestTimeoutMsHint",
researchGlobalMaxSynthesisRounds: "researchGlobal.maxSynthesisRoundsHint",
researchGlobalGitHubEnabled: "researchGlobal.gitHubSourceHint",
researchGlobalLocalDocsEnabled: "researchGlobal.localDocsSourceHint",
researchGlobalWebSearchProvider: "researchGlobal.searchesAndFetchesUseTheAgentsNativeWebSearch",
// NotificationsSection
failureNotificationDelayMs: "notifications.howLongAFailureMustPersistBeforeA",
failureNotificationMode: "notifications.stickyFailuresOnlyDefault",
ntfyEnabled: "notifications.ntfyEnabledHint",
ntfyTopic: "notifications.yourNtfyShTopicName164Alphanumeric",
ntfyBaseUrl: "notifications.leaveBlankToKeepTheDefaultServerHttps",
ntfyAccessToken: "notifications.leaveBlankToPublishWithoutAuthenticationWhenSet",
ntfyDashboardHost: "notifications.baseURLForDeepLinksInNotificationsWhen",
webhookEnabled: "notifications.webhookEnabledHint",
webhookUrl: "notifications.webhookUrlHint",
webhookFormat: "notifications.webhookFormatHint",
// RemoteSection
remoteAccess: "remote.acceptRoutesHint",
// ExperimentalSection
experimentalFeatures: "experimental.experimentalFeaturesAreEarlyCapabilitiesThatAreNot",
// CommandsSection
testCommand: "commands.commandUsedToRunTestsInjectedIntoGenerated",
buildCommand: "commands.commandUsedToBuildTheProjectInjectedInto",
// PromptsSection
agentPrompts: "prompts.surfaceExplanation",
promptOverrides: "prompts.surfaceExplanation",
// BackupsSection
autoBackupEnabled: "backups.whenEnabledTheDatabaseIsBackedUpAutomatically",
autoBackupSchedule: "backups.cronExpressionForBackupTimingDefault02",
autoBackupRetention: "backups.numberOfBackupFilesToKeepOldestAre",
autoBackupDir: "backups.directoryForBackupFilesRelativeToProjectRoot",
memoryBackupEnabled: "backups.whenEnabledProjectAndAgentMemoryFilesAre",
memoryBackupSchedule: "backups.cronExpressionForMemoryBackupTimingDefault0",
memoryBackupRetention: "backups.numberOfMemoryBackupsToKeepOldestAre",
memoryBackupDir: "backups.directoryForMemoryBackupsRelativeToProjectRoot",
memoryBackupScope: "backups.memoryBackupScopeHint",
// MemorySection
memoryEnabled: "memory.agentsGetMemorySearchMemoryGetAndMemory",
memoryAutoSummarizeEnabled: "memory.automaticallyCompactMemoryWhenItExceedsTheThreshold",
memoryAutoSummarizeThresholdChars: "memory.memoryWillBeCompactedWhenItExceedsThis",
memoryAutoSummarizeSchedule: "memory.cronExpressionForAutoSummarizeScheduleDefaultDaily",
memoryDreamsEnabled: "memory.turnsDailyNotesIntoDREAMSMdAndPromotes",
memoryDreamsSchedule: "memory.cronExpressionForDreamProcessing",
memoryBackendType: "memory.agentsGetMemorySearchMemoryGetAndMemory",
// MergeSection
autoMerge: "merge.whenEnabledTasksThatPassReviewAreAutomatically",
planApprovalMode: "merge.planApprovalModeWorkflow",
maxAutoMergeRetries: "merge.positiveIntegerRetryCapForAutoMergeConflict",
merger: "merge.dangerousCompatibilityEscapeHatchLeaveOffUnlessYou",
testMode: "merge.forcesAllAILanesToUseTheDeterministic",
mergeStrategy: "merge.directMergeIntoTheCurrentBranch",
integrationBranch: "merge.theCanonicalBranchFusionMergesTasksIntoAnd",
directMergeCommitStrategy: "merge.alwaysSquashDirectMerges",
mergeIntegrationWorktree: "merge.reuseTaskWorktreeDefault",
mergeAdvanceAutoSync: "merge.stashFastForwardDefaultPreserveLocalEdits",
githubAuthMode: "merge.gitHubCLIGhAuth",
githubAuthToken: "merge.githubAuthTokenHint",
includeTaskIdInCommit: "merge.includeTaskIdInCommitDefault",
commitAuthorEnabled: "merge.trailerCreditingFusionRecognizedByGitHubForShared",
commitAuthorName: "merge.trailer",
commitAuthorEmail: "merge.trailerEmail",
autoResolveConflicts: "merge.whenEnabledLockFilesPackageLockJsonPnpm",
smartConflictResolution: "merge.whenEnabledLockFilesPackageLockJsonPnpm2",
mergeConflictStrategy: "merge.smartPreferMainOnFallbackFetchFfOrigin",
mergeStrategyOverlapBehavior: "merge.flipOverlappingFilesToPreferTheTaskBranch",
postMergeAuditMode: "merge.warnDefaultLogFindingsContinue",
pushAfterMerge: "merge.whenEnabledTheMergedResultIsAutomaticallyPushed",
pushRemote: "merge.gitRemoteToPushToEGOrigin",
// NodeRouting / node sync covered above
// SchedulingSection
globalMaxConcurrent: "scheduling.maximumConcurrentAgentsAcrossAllProjects",
maxConcurrent: "scheduling.maxConcurrentTasksHint",
maxTriageConcurrent: "scheduling.maximumConcurrentPlanningAgents",
pollIntervalMs: "scheduling.pollIntervalMsHint",
heartbeatScopeDiscipline: "scheduling.strictDefault",
engineerBacklogAutoClaim: "scheduling.backlogNoTaskAutoClaimIsExecutorOnly",
taskStuckTimeoutMs: "scheduling.timeoutInMinutesForDetectingStuckTasksWhen",
staleHighFanoutBlockerAgeThresholdMs: "scheduling.escalateHighFanOutBlockersOnlyAfterThey",
preserveProgressOnStuckRequeue: "scheduling.whenTheStuckDetectorKillsAndReQueues",
specStalenessEnabled: "scheduling.whenEnabledTasksWithStalePlansPROMPTMd",
specStalenessMaxAgeMs: "scheduling.maximumAgeInHoursBeforeAPlanIs",
autoArchiveDoneTasksEnabled: "scheduling.completedTasksOlderThanTheThresholdAreMoved",
autoArchiveDoneAfterMs: "scheduling.numberOfDaysATaskCanStayIn",
archiveAgentLogMode: "scheduling.compactModeKeepsArchiveSizeLowWhilePreserving",
maxStuckKills: "scheduling.maximumStuckDetectorRetriesBeforeATaskIs",
groupOverlappingFiles: "scheduling.whenEnabledTasksThatModifyTheSameFiles",
ignoreHiddenOverlapPaths: "scheduling.ignoreHiddenDotPathsHelp",
overlapIgnorePaths: "scheduling.optionalFileOrDirectoryPathsToIgnoreWhen",
// WorktreesSection
maxWorktrees: "worktrees.limitsTotalGitWorktreesIncludingInReviewTasks",
worktreeInitCommand: "worktrees.shellCommandToRunInEachNewWorktree",
recycleWorktrees: "worktrees.offByDefaultOptInWhenEnabledCompleted",
showWorktreeGrouping: "worktrees.showWorktreeGroupingHelp",
worktreeCopyFiles: "worktrees.copyFilesHelp",
executorAllowSiblingBranchRename: "worktrees.andCanHidePriorCommitsFromTheDefault",
worktreeNaming: "worktrees.howToNameFreshWorktreeDirectories",
worktreesDir: "worktrees.whenUnsetOnlyAffectsNewlyCreatedWorktrees",
worktreeRebaseBeforeMerge: "worktrees.whenEnabledTheMergerFetchesFromTheConfigured",
worktreeRebaseRemote: "worktrees.whichRemoteToFetchForThePreMerge",
worktreeRebaseLocalBase: "worktrees.inAdditionToTheRemoteRebaseAboveAlso",
worktrunk: "worktrees.disabledByDefaultOptInWhenEnabledFusion",
// GeneralSection (project)
allowAbsoluteFileBrowserPaths: "general.allowAbsoluteFileBrowserPathsHint",
capacityRiskBannerEnabled: "general.warnOnTheBoardWhenTodoWorkExceeds",
capacityRiskTodoThreshold: "general.bannerFiresWhenTodoCountIsStrictlyGreater",
chatAutoCleanupDays: "general.deleteChatSessionsAndRoomsThatHaveBeen",
chatRoomCompactionFetchLimit: "general.upperBoundOnMessagesFetchedFromTheRoom",
chatRoomRecentVerbatimMessages: "general.numberOfMostRecentChatRoomMessagesKept",
chatRoomSummaryMaxChars: "general.hardCapOnTheSynthesizedEarlierRoomContext",
completionDocumentationMode: "general.workflowsOrChangelogModeWhenContributorsShouldUpdate",
ephemeralAgentsCanCreateTasks: "general.allowEphemeralAgentsToCreateTasksHint",
ephemeralAgentsEnabled: "general.whenEnabledDefaultFusionSpawnsShortLived",
githubLinkImportedIssuesToTracking: "general.whenEnabledImportedGitHubIssuesUseTheirSource",
githubTrackingDedupEnabled: "general.whenEnabledFusionChecksOpenAndClosedIssues",
githubTrackingEnabledByDefault: "general.offDefault",
mailAutoCleanupDays: "general.deleteInboxOutboxMessagesOlderThanThisMany",
operationalLogRetentionDays: "general.loweringThisWindowMeansReliabilityMetricsChartsAnd",
quickChatButtonMode: "general.quickChatLauncherHint",
quickChatCloseOnOutsideClick: "general.quickChatCloseOnOutsideClickHint",
showTaskChatsInCommonFeed: "general.showTaskChatsInCommonFeedHint",
taskPrefix: "general.prefixForNewTaskIDsEGKB",
workspaceMode: "general.workspaceModeHint",
defaultWorkflowId: "general.newTasksInheritThisCustomWorkflowsStepsOverridable",
enabledBuiltinWorkflowIds: "general.disabledFusionWorkflowsAreHiddenFromWorkflow",
// ProjectModelsSection
autoSelectModelPreset: "projectModels.autoSelectModelPresetHint",
autoSummarizeTitles: "projectModels.whenEnabledTasksCreatedWithoutATitleBut",
defaultPresetBySize: "projectModels.autoSelectModelPresetHint",
modelPresets: "projectModels.autoSelectModelPresetHint",
prDescriptionPromptInstructions: "projectModels.prDescriptionPromptInstructionsHelp",
prTitlePromptInstructions: "projectModels.prTitlePromptInstructionsHelp",
tokenCap: "projectModels.automaticallyCompactContextWhenApproachingThisTokenCount",
useAiMergeCommitSummary: "projectModels.whenEnabledMergeCommitMessagesIncludeAnAI",
// Model pricing
modelPricingOverrides: "modelPricing.description",
// ResearchProjectSection
researchSettings: "researchProject.enableResearchInThisProjectHint",
// ScheduledEvalsSection
evalSettings: "scheduledEvals.enabledHint",
};
/** Setting keys intentionally not surfaced as a plain Settings UI description field, with reasons. */
const NOT_SURFACED_ALLOWLIST: Record<string, string> = {
// Moved to workflow settings (U4) — see MOVED_SETTINGS_KEYS in settings-schema.ts.
workflowStepTimeoutMs: "moved to workflow settings (U4)",
workflowStepScopeEnforcement: "moved to workflow settings (U4)",
planOnlyScopeLeakEnforcement: "moved to workflow settings (U4)",
workflowRevisionForkOnScopeMismatch: "moved to workflow settings (U4)",
strictScopeEnforcement: "moved to workflow settings (U4)",
runStepsInNewSessions: "moved to workflow settings (U4)",
maxParallelSteps: "moved to workflow settings (U4)",
buildRetryCount: "moved to workflow settings (U4)",
verificationFixRetries: "moved to workflow settings (U4)",
maxPostReviewFixes: "moved to workflow settings (U4)",
requirePrApproval: "moved to workflow settings (U4)",
requirePlanApproval: "moved to workflow settings (U4)",
reviewHandoffPolicy: "moved to workflow settings (U4)",
maxReviewerContextRetries: "moved to workflow settings (U4)",
maxReviewerFallbackRetries: "moved to workflow settings (U4)",
reflectionEnabled: "moved to workflow settings (U4)",
executionProvider: "moved to workflow settings (U4)",
executionModelId: "moved to workflow settings (U4)",
planningProvider: "moved to workflow settings (U4)",
planningModelId: "moved to workflow settings (U4)",
planningFallbackProvider: "moved to workflow settings (U4)",
planningFallbackModelId: "moved to workflow settings (U4)",
validatorProvider: "moved to workflow settings (U4)",
validatorModelId: "moved to workflow settings (U4)",
validatorFallbackProvider: "moved to workflow settings (U4)",
validatorFallbackModelId: "moved to workflow settings (U4)",
// Internal/engine bookkeeping, session state, or reliability telemetry — not
// rendered as a plain user-facing description field anywhere in Settings.
globalPause: "engine-managed pause flag, not a plain description field",
globalPauseReason: "engine-managed pause flag, not a plain description field",
enginePaused: "engine-managed pause flag, not a plain description field",
engineLastActiveAt: "internal engine bookkeeping timestamp",
engineActiveSinceMs: "internal engine bookkeeping timestamp",
engineActivationGraceMs: "internal engine tuning constant, no UI field",
reliabilityStatsResetAt: "internal engine bookkeeping timestamp",
dashboardCurrentNodeId: "dashboard session/PWA restore state, not a setting field",
dashboardCurrentProjectIdByNode: "dashboard session/PWA restore state, not a setting field",
daemonToken: "daemon runtime secret, not rendered as a description field",
daemonPort: "daemon runtime config, not exposed in Settings UI",
daemonHost: "daemon runtime config, not exposed in Settings UI",
setupComplete: "onboarding wizard completion flag, not a Settings field",
cliOnboardingCompletedAt: "onboarding wizard completion flag, not a Settings field",
modelOnboardingComplete: "onboarding wizard completion flag, not a Settings field",
defaultProjectId: "internal navigation state, not a Settings field",
favoriteProviders: "derived UI favorite-star state, not a described field",
favoriteModels: "derived UI favorite-star state, not a described field",
secretsAccessPolicy: "managed via the Secrets view, not a plain description field",
secretsSyncPassphraseConfigured: "derived boolean status flag, not a user-set field",
secretsEnv: "managed via the Secrets view, not a plain description field",
testMode2: "not a real key (placeholder guard)",
autoUpdatePrStatus: "internal PR-status sync flag, no dedicated UI field",
githubCommentOnDone: "not yet exposed as a distinct Settings field",
githubCommentTemplate: "not yet exposed as a distinct Settings field",
githubCloseSourceIssueOnDone: "not yet exposed as a distinct Settings field",
githubTrackingDedupEnabled2: "not a real key (placeholder guard)",
gitlabCommentOnDone: "not yet exposed as a distinct Settings field",
gitlabCommentTemplate: "not yet exposed as a distinct Settings field",
gitlabCloseSourceIssueOnDone: "not yet exposed as a distinct Settings field",
titleSummarizerProvider: "configured via the model-lane picker, not a plain description field",
titleSummarizerModelId: "configured via the model-lane picker, not a plain description field",
titleSummarizerFallbackProvider: "configured via the model-lane picker, not a plain description field",
titleSummarizerFallbackModelId: "configured via the model-lane picker, not a plain description field",
titleSummarizerGlobalProvider: "configured via the model-lane picker, not a plain description field",
titleSummarizerGlobalModelId: "configured via the model-lane picker, not a plain description field",
executionGlobalProvider: "configured via the model-lane picker, not a plain description field",
executionGlobalModelId: "configured via the model-lane picker, not a plain description field",
planningGlobalProvider: "configured via the model-lane picker, not a plain description field",
planningGlobalModelId: "configured via the model-lane picker, not a plain description field",
validatorGlobalProvider: "configured via the model-lane picker, not a plain description field",
validatorGlobalModelId: "configured via the model-lane picker, not a plain description field",
defaultProviderOverride: "configured via the model-lane picker, not a plain description field",
defaultModelIdOverride: "configured via the model-lane picker, not a plain description field",
agentPrompts2: "not a real key (placeholder guard)",
promptOverrides2: "not a real key (placeholder guard)",
taskTokenBudget: "not yet exposed as a distinct Settings field",
tokenCap2: "not a real key (placeholder guard)",
scripts: "not yet exposed as a distinct Settings field",
setupScript: "not yet exposed as a distinct Settings field",
agentProvisioning2: "not a real key (placeholder guard)",
sandboxProvisioning: "configured via the Agent Permissions provisioning editor, not a plain description field",
sandbox: "not yet exposed as a distinct Settings field",
approvedWorkflowCliCommands: "internal workflow CLI-approval bookkeeping, not a Settings field",
approvedCliAutonomyAdapters: "internal workflow CLI-approval bookkeeping, not a Settings field",
owningNodeHandoffPolicy: "not yet exposed as a distinct Settings field",
unavailableNodePolicy2: "not a real key (placeholder guard)",
defaultNodeId2: "not a real key (placeholder guard)",
taskAttributionTrailerNames: "not yet exposed as a distinct Settings field",
commitMsgHookEnabled: "not yet exposed as a distinct Settings field",
autoResolveReviewComments: "not yet exposed as a distinct Settings field",
mergeRequestContractShadowEnabled: "internal shadow-diagnostic flag, not a Settings field",
mergeDiffVolumeMinLines: "not yet exposed as a distinct Settings field",
mergeDiffVolumeThreshold: "not yet exposed as a distinct Settings field",
mergeDiffVolumeAllowlist: "not yet exposed as a distinct Settings field",
mergeAuditAutoRecovery: "not yet exposed as a distinct Settings field",
autoRecovery: "not yet exposed as a distinct Settings field",
buildTimeoutMs: "not yet exposed as a distinct Settings field",
verificationCommandTimeoutMs: "not yet exposed as a distinct Settings field",
scopeVerificationToChangedFiles: "not yet exposed as a distinct Settings field",
specStalenessMaxAgeMs2: "not a real key (placeholder guard)",
dispatchOscillationThreshold: "internal scheduler tuning constant, no UI field",
dispatchOscillationWindowMs: "internal scheduler tuning constant, no UI field",
dispatchOscillationSettleMs: "internal scheduler tuning constant, no UI field",
runtimeStopDrainMs: "internal scheduler tuning constant, no UI field",
inReviewStallDeadlockThreshold: "internal reliability tuning constant, no UI field",
stalePausedReviewThresholdMs: "internal reliability tuning constant, no UI field",
inReviewStalledThresholdMs: "internal reliability tuning constant, no UI field",
stalePausedTodoThresholdMs: "internal reliability tuning constant, no UI field",
pausedScopeDecayMs: "internal reliability tuning constant, no UI field",
metaTaskStallAutoCloseMs: "internal reliability tuning constant, no UI field",
metaTaskActiveExecutionGraceMs: "internal reliability tuning constant, no UI field",
boardStallSweepWindowMs: "internal reliability tuning constant, no UI field",
boardStallBlockedGrowthThreshold: "internal reliability tuning constant, no UI field",
backlogPressureAlertEnabled: "internal reliability tuning constant, no UI field",
backlogPressureRatioThreshold: "internal reliability tuning constant, no UI field",
backlogPressureMinTodoCount: "internal reliability tuning constant, no UI field",
backlogPressureAlertCooldownMs: "internal reliability tuning constant, no UI field",
dependencyBlockedTodoReportEnabled: "internal reliability tuning constant, no UI field",
dependencyBlockedTodoFreshAgeMs: "internal reliability tuning constant, no UI field",
dependencyBlockedTodoStaleAgeMs: "internal reliability tuning constant, no UI field",
dependencyBlockedTodoMinCount: "internal reliability tuning constant, no UI field",
dependencyBlockedTodoReportCooldownMs: "internal reliability tuning constant, no UI field",
staleInProgressWarningMs: "internal reliability tuning constant, no UI field",
staleInProgressCriticalMs: "internal reliability tuning constant, no UI field",
staleInReviewWarningMs: "internal reliability tuning constant, no UI field",
staleInReviewCriticalMs: "internal reliability tuning constant, no UI field",
aiSessionTtlMs: "internal session-cleanup tuning constant, no UI field",
aiSessionCleanupIntervalMs: "internal session-cleanup tuning constant, no UI field",
autoUnpauseEnabled: "not yet exposed as a distinct Settings field",
autoUnpauseBaseDelayMs: "internal auto-unpause tuning constant, no UI field",
autoUnpauseMaxDelayMs: "internal auto-unpause tuning constant, no UI field",
maxBranchConflictRecoveries: "internal reliability tuning constant, no UI field",
maxTotalRetriesBeforeFail: "internal reliability tuning constant, no UI field",
maintenanceIntervalMs: "internal engine maintenance interval, no UI field",
doneAutoArchiveDays: "legacy alias superseded by autoArchiveDoneAfterMs, no UI field",
autoClaimCandidatesInPrompt: "internal prompt-shaping constant, no UI field",
tombstoneStickyWindowDays: "internal tombstone-retention constant, no UI field",
heartbeatMultiplier: "internal scheduler tuning constant, no UI field",
heartbeatPromptTemplate: "internal prompt-template selector, no UI field",
agentLogFileRetentionDays: "not yet exposed as a distinct Settings field",
chatRoomCompactionFetchLimit2: "not a real key (placeholder guard)",
missionStaleThresholdMs: "internal mission-health tuning constant, no UI field",
missionMaxTaskRetries: "internal mission-health tuning constant, no UI field",
missionHealthCheckIntervalMs: "internal mission-health tuning constant, no UI field",
reflectionIntervalMs: "internal reflection-scheduling constant, no UI field",
reflectionAfterTask: "internal reflection-scheduling constant, no UI field",
showQuickChatFAB: "derived from quickChatButtonMode, not independently described",
taskEvaluationEnabled: "not yet exposed as a distinct Settings field",
taskEvaluationSchedule: "not yet exposed as a distinct Settings field",
taskEvaluationProvider: "not yet exposed as a distinct Settings field",
taskEvaluationModelId: "not yet exposed as a distinct Settings field",
taskEvaluationFollowUpPolicy: "not yet exposed as a distinct Settings field",
taskEvaluationRetention: "not yet exposed as a distinct Settings field",
insightExtractionEnabled: "not yet exposed as a distinct Settings field",
insightExtractionSchedule: "not yet exposed as a distinct Settings field",
insightExtractionMinIntervalMs: "not yet exposed as a distinct Settings field",
// Global settings not rendered by a plain description field.
themeMode: "configured via ThemeSelector, not a plain description field",
colorTheme: "configured via ThemeSelector, not a plain description field",
shadcnCustomColors: "configured via ThemeSelector, not a plain description field",
dashboardFontScalePct: "configured via ThemeSelector, not a plain description field",
dashboardKeyboardShortcuts: "described inline per-shortcut (quickChatShortcutHint / terminalShortcutHint), not a single field",
language: "configured via LanguageSelector, not a plain description field",
modelPricingFetchedAt: "derived fetch-status timestamp, not a user-set field",
modelPricingSource: "derived fetch-status metadata, not a user-set field",
modelRouterEnabled: "not yet exposed as a distinct Settings field",
modelRouterCheapProvider: "not yet exposed as a distinct Settings field",
modelRouterCheapModelId: "not yet exposed as a distinct Settings field",
openrouterModelFilters2: "not a real key (placeholder guard)",
openrouterProviderPreferences2: "not a real key (placeholder guard)",
ntfyEvents: "described per-checkbox in the event list, not a single field",
webhookEvents: "described per-checkbox in the event list, not a single field",
notificationProviders: "not yet exposed as a distinct Settings field",
customProviders: "not yet exposed as a distinct Settings field",
cliAgents: "configured via per-adapter Runtime Cards (Hermes/OpenClaw/Paperclip) outside settings/sections scope",
useClaudeCli: "configured via CliBinaryPanel, not a plain description field",
useDroidCli: "configured via CliBinaryPanel, not a plain description field",
useLlamaCpp: "configured via CliBinaryPanel, not a plain description field",
useCursorCli: "configured via CliBinaryPanel, not a plain description field",
cursorCliBinaryPath: "configured via CliBinaryPanel, not a plain description field",
vitestAutoKillEnabled: "dashboard TUI memory guard, no Settings UI field",
vitestKillThresholdPct: "dashboard TUI memory guard, no Settings UI field",
agentMemoryInclusionMode: "not yet exposed as a distinct Settings field",
researchGlobalDefaults: "superseded by discrete researchGlobal* fields, which are individually documented",
researchGlobalEnabled: "superseded by the per-source enabledSources toggles, which are individually documented",
researchGlobalUserAgent: "not yet exposed as a distinct Settings field",
// Research (legacy top-level project research settings superseded by researchSettings).
researchEnabled: "superseded by researchSettings.enabled, which is documented",
researchMaxConcurrentRuns: "superseded by researchSettings.limits.maxConcurrentRuns, which is documented",
researchDefaultTimeout: "superseded by researchSettings.limits.maxDurationMs, which is documented",
researchMaxSourcesPerRun: "superseded by researchSettings.limits.maxSourcesPerRun, which is documented",
researchMaxSynthesisRounds: "not yet exposed as a distinct Settings field",
// Session/legacy fields with no dedicated description field in any section.
persistAgentThinkingLog: "legacy base flag superseded by granular Permanent/Ephemeral toggles, which are documented",
researchGlobalBraveApiKey: "configured via the Authentication section's provider API key flow, not a plain description field",
researchGlobalTavilyApiKey: "configured via the Authentication section's provider API key flow, not a plain description field",
researchGlobalMaxSearchResults: "not yet exposed as a distinct Settings field",
mergerAutostashMaxAgeHours: "internal AI-merger autostash tuning constant, no UI field",
prerebaseAutoEnabled: "internal pre-rebase tuning constant, no UI field",
prerebaseHotFiles: "internal pre-rebase tuning constant, no UI field",
prerebaseDivergenceThreshold: "internal pre-rebase tuning constant, no UI field",
maxSpawnedAgentsPerParent: "internal spawn-limit constant, no UI field",
maxSpawnedAgentsGlobal: "internal spawn-limit constant, no UI field",
};
describe("FN-7505 settings default-value description guard", () => {
it("every surfaced setting's resolved English description states its default", () => {
const missing: string[] = [];
const noIndicator: string[] = [];
for (const [settingKey, i18nPath] of Object.entries(SETTING_DESCRIPTION_KEYS)) {
const value = resolveDescription(realEnApp.settings as SettingsDict, i18nPath);
if (value === undefined) {
missing.push(`${settingKey} -> settings.${i18nPath} (key not found in locale)`);
continue;
}
if (!DEFAULT_INDICATOR_RE.test(value)) {
noIndicator.push(`${settingKey} -> settings.${i18nPath}: ${JSON.stringify(value)}`);
}
}
expect(missing, `Missing locale keys:\n${missing.join("\n")}`).toEqual([]);
expect(
noIndicator,
`Descriptions missing a default-value indicator (Default:/inherits/No default/(default)):\n${noIndicator.join("\n")}`,
).toEqual([]);
});
it("every DEFAULT_SETTINGS key is either mapped to a description or explicitly allowlisted", () => {
const unaccounted = Object.keys(DEFAULT_SETTINGS).filter(
(key) => !(key in SETTING_DESCRIPTION_KEYS) && !(key in NOT_SURFACED_ALLOWLIST),
);
expect(
unaccounted,
`Settings keys with no default-value description mapping and no allowlist reason:\n${unaccounted.join("\n")}`,
).toEqual([]);
});
it("does not allowlist a key that is also mapped to a description (would mask real coverage gaps)", () => {
const overlap = Object.keys(SETTING_DESCRIPTION_KEYS).filter((key) => key in NOT_SURFACED_ALLOWLIST);
expect(overlap).toEqual([]);
});
it("mapped settings state their ACTUAL canonical default, not a fabricated one", () => {
/*
* FNXC:SettingsDefaults 2026-07-04-00:00:
* Deliberately narrow: this only anchors on the explicit "Default: <claim>" colon
* phrasing rather than every free-form "off by default"/"(default)" variant, because
* requiring one exact phrasing across ~140 hand-written descriptions would produce
* false positives unrelated to the actual defect class. The defect class this guards
* against (caught in FN-7505 code review) is a description making an explicit,
* WRONG "Default: X" claim — e.g. "Default: enabled" for a setting whose canonical
* schema default is `undefined`, or "Default: https://runfusion.ai" for a field whose
* schema default is `undefined` (a UI placeholder/runtime-fallback value mistaken for
* the setting's own default). Settings whose canonical default is undefined must use
* unset/inherits phrasing instead of a bare "Default:" claim.
*/
const UNSET_INDICATOR_RE = /no default|inherits|\bunset\b/i;
const mismatches: string[] = [];
for (const [settingKey, i18nPath] of Object.entries(SETTING_DESCRIPTION_KEYS)) {
const description = resolveDescription(realEnApp.settings as SettingsDict, i18nPath);
if (description === undefined) continue; // already asserted by the first test above
const actualDefault = resolveCanonicalDefault(settingKey);
if (actualDefault === undefined) {
// Genuinely unset (no default, or inherits another value) — the description must
// say so and must NOT fabricate a concrete "Default: X" claim (FN-7505 review fix).
if (!UNSET_INDICATOR_RE.test(description)) {
mismatches.push(
`${settingKey} -> settings.${i18nPath}: canonical default is undefined but description doesn't say "no default"/"unset"/"inherits": ${JSON.stringify(description)}`,
);
}
const colonClaim = description.match(/default:\s*([^.\n]+)/i);
if (colonClaim && !UNSET_INDICATOR_RE.test(colonClaim[1])) {
mismatches.push(
`${settingKey} -> settings.${i18nPath}: canonical default is undefined but description fabricates a concrete "Default: ${colonClaim[1].trim()}" claim: ${JSON.stringify(description)}`,
);
}
continue;
}
if (typeof actualDefault === "boolean") {
const colonClaim = description.match(/default:\s*([a-z]+)/i);
if (colonClaim) {
const stated = colonClaim[1].toLowerCase();
const statedTrue = stated === "enabled" || stated === "true" || stated === "on";
const statedFalse = stated === "disabled" || stated === "false" || stated === "off";
if (statedTrue && actualDefault !== true) {
mismatches.push(
`${settingKey} -> settings.${i18nPath}: description claims "Default: ${stated}" but canonical default is ${actualDefault}: ${JSON.stringify(description)}`,
);
}
if (statedFalse && actualDefault !== false) {
mismatches.push(
`${settingKey} -> settings.${i18nPath}: description claims "Default: ${stated}" but canonical default is ${actualDefault}: ${JSON.stringify(description)}`,
);
}
}
}
}
expect(mismatches, `Mismatched default-value claims:\n${mismatches.join("\n")}`).toEqual([]);
});
});

View File

@@ -5650,8 +5650,8 @@
"agentPermissions": {
"agentPermissions": "Agent Permissions",
"agentProvisioningApprovals": "Agent Provisioning Approvals",
"configureProjectLevelApprovalBehaviorForDurableProvisioning": " Configure project-level approval behavior for durable provisioning tools (fn_agent_create/fn_agent_delete). ",
"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."
"configureProjectLevelApprovalBehaviorForDurableProvisioning": " Configure project-level approval behavior for durable provisioning tools (fn_agent_create/fn_agent_delete). Default: no approval policy configured (empty). ",
"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 — every action category defaults to allow until a category is explicitly restricted."
},
"appearance": {
"hideAISessionNotificationBanners": "Hide AI session notification banners",
@@ -5659,7 +5659,10 @@
"languageAuto": "Auto",
"languageAutoHint": "Follow the browser language",
"suppressTheLdquoNeedsYourInputRdquoBanner": " Suppress the &ldquo;needs your input&rdquo; banner that appears when AI sessions are awaiting input or have failed. ",
"title": "Appearance"
"title": "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.",
"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.",
"taskDetailChatFirstHelp": "Off by default: task details list Activity first and omitted non-done opens land on Activity. Turn on to restore Chat-first order/default; explicit Chat links still work either way."
},
"auth": {
"apiKeyCleared": "API key cleared",
@@ -5719,8 +5722,8 @@
"cronExpressionForMemoryBackupTimingDefault0": "Cron expression for memory backup timing. Default: 0 3 * * * (daily at 3 AM).",
"currentBackups": "Current Backups",
"databaseBackups": "Database Backups",
"directoryForBackupFilesRelativeToProjectRoot": "Directory for backup files, relative to project root",
"directoryForMemoryBackupsRelativeToProjectRoot": "Directory for memory backups, relative to project root.",
"directoryForBackupFilesRelativeToProjectRoot": "Directory for backup files, relative to project root. Default: .fusion/backups.",
"directoryForMemoryBackupsRelativeToProjectRoot": "Directory for memory backups, relative to project root. Default: .fusion/backups/memory.",
"enableAutomaticDatabaseBackups": " Enable automatic database backups ",
"enableAutomaticMemoryBackups": " Enable automatic memory backups ",
"fusionBackups": ".fusion/backups",
@@ -5734,16 +5737,17 @@
"memoryRetentionCount": "Memory Retention Count",
"more": " more",
"mustBeBetween1And100": "Must be between 1 and 100",
"numberOfBackupFilesToKeepOldestAre": "Number of backup files to keep (oldest are deleted first). Range: 1-100.",
"numberOfMemoryBackupsToKeepOldestAre": "Number of memory backups to keep (oldest are deleted first). Range: 1-100.",
"numberOfBackupFilesToKeepOldestAre": "Number of backup files to keep (oldest are deleted first). Range: 1-100. Default: 7.",
"numberOfMemoryBackupsToKeepOldestAre": "Number of memory backups to keep (oldest are deleted first). Range: 1-100. Default: 14.",
"pathCannotContainParentDirectoryTraversal": "Path cannot contain parent directory traversal (..)",
"projectOnlyFusionMemory": "Project only (.fusion/memory)",
"retentionCount": "Retention Count",
"totalSize": "total size",
"view": "View ",
"whenEnabledProjectAndAgentMemoryFilesAre": "When enabled, project and agent memory files are backed up automatically on a schedule.",
"whenEnabledTheDatabaseIsBackedUpAutomatically": "When enabled, the database is backed up automatically on a schedule",
"createFailed": "Failed to create backup"
"whenEnabledProjectAndAgentMemoryFilesAre": "When enabled, project and agent memory files are backed up automatically on a schedule. Default: disabled.",
"whenEnabledTheDatabaseIsBackedUpAutomatically": "When enabled, the database is backed up automatically on a schedule. Default: disabled.",
"createFailed": "Failed to create backup",
"memoryBackupScopeHint": "Default: all (project + agents)."
},
"clearToDefault": "Reset to default",
"cliAgents": {
@@ -5773,8 +5777,8 @@
"commands": {
"buildCommand": "Build Command",
"commands": "Commands",
"commandUsedToBuildTheProjectInjectedInto": "Command used to build the project — injected into generated task specs",
"commandUsedToRunTestsInjectedIntoGenerated": "Command used to run tests — injected into generated task specs",
"commandUsedToBuildTheProjectInjectedInto": "Command used to build the project — injected into generated task specs. No default — unset.",
"commandUsedToRunTestsInjectedIntoGenerated": "Command used to run tests — injected into generated task specs. No default — unset.",
"eGPnpmBuild": "e.g. pnpm build",
"eGPnpmTest": "e.g. pnpm test",
"testCommand": "Test Command"
@@ -5782,7 +5786,7 @@
"conflictModalTitle": "Resolve Settings Conflicts",
"experimental": {
"experimentalFeatures": "Experimental Features",
"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. ",
"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. ",
"featureFlags": "Feature Flags"
},
"footer": {
@@ -5833,14 +5837,14 @@
"hardCapOnTheSynthesizedEarlierRoomContext": "Hard cap on the synthesized \"Earlier room context\" summary block. Default: 3000.",
"learnMore": "Learn more",
"loweringThisWindowMeansReliabilityMetricsChartsAnd": " Lowering this window means Reliability metrics/charts and the Activity feed will not show history older than the selected range. Per-task task detail history is unaffected. Default: 30 days. ",
"newTasksInheritThisCustomWorkflowsStepsOverridable": "New tasks inherit this custom workflow's steps (overridable per task)",
"newTasksInheritThisCustomWorkflowsStepsOverridable": "New tasks inherit this custom workflow's steps (overridable per task). No default — unset (built-in default workflow).",
"numberOfMostRecentChatRoomMessagesKept": "Number of most-recent chat-room messages kept verbatim in the responder transcript. Older messages are compacted into a summary block. Default: 25.",
"off": "Off",
"offDefault": "Off (default)",
"onForNewTasks": "On for new tasks",
"operationalLogRetention": "Operational log retention",
"ownerRepo": "owner/repo",
"prefixForNewTaskIDsEGKB": "Prefix for new task IDs (e.g. KB, PROJ)",
"prefixForNewTaskIDsEGKB": "Prefix for new task IDs (e.g. KB, PROJ). No default — unset.",
"prefixMustBe110UppercaseLetters": "Prefix must be 1–10 uppercase letters",
"projectDefaultTrackingRepo": "Project default tracking repo",
"recentVerbatimRoomMessages": "Recent verbatim room messages",
@@ -5867,21 +5871,30 @@
"upperBoundOnMessagesFetchedFromTheRoom": "Upper bound on messages fetched from the room store for compaction consideration. Default: 200.",
"upToDate": "You're up to date ✓",
"useEphemeralTaskWorkerAgents": " Use ephemeral task-worker agents ",
"warnOnTheBoardWhenTodoWorkExceeds": "Warn on the board when todo work exceeds the threshold and no idle agents are available.",
"warnOnTheBoardWhenTodoWorkExceeds": "Warn on the board when todo work exceeds the threshold and no idle agents are available. Default: disabled.",
"whenEnabledDefaultFusionSpawnsShortLived": " When enabled (default), Fusion spawns short-lived ",
"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. ",
"workflowsOrChangelogModeWhenContributorsShouldUpdate": " workflows, or changelog mode when contributors should update an existing changelog file. "
"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. ",
"workflowsOrChangelogModeWhenContributorsShouldUpdate": " workflows, or changelog mode when contributors should update an existing changelog file. Default: off. ",
"workspaceModeHint": "When enabled, the project root is treated as a workspace containing multiple git sub-repos. Tasks run per-sub-repo and no git repo is created at the root. Disable for single-repo projects. No default — unset (disabled).",
"allowAbsoluteFileBrowserPathsHint": "When enabled, slash-prefixed paths such as /tmp can be opened in the workspace file browser. Windows drive-letter paths remain blocked, and other path validators are unchanged. Default: disabled.",
"quickChatLauncherHint": "Choose whether Quick Chat opens from the draggable floating button, a footer button beside Terminal, or stays hidden. Default: off (hidden).",
"showTaskChatsInCommonFeedHint": "When enabled, populated task-detail Chat conversations appear in the common Direct feed. Empty task chats stay hidden. Default: disabled.",
"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.",
"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).",
"allowEphemeralAgentsToCreateTasksHint": "When enabled (default), ephemeral task-worker agents can open follow-up tasks via fn_task_create. When disabled, only humans and permanent agents can create tasks; ephemeral callers are rejected.",
"quickChatCloseOnOutsideClickHint": "When enabled, clicking outside the Quick Chat window closes it. Disable to keep it open until you close it explicitly. Default: enabled.",
"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)."
},
"globalGeneral": {
"andShowsUpdateNoticesInTheCLIAnd": " and shows update notices in the CLI and dashboard. Cadence is governed by the frequency below. ",
"andShowsUpdateNoticesInTheCLIAnd": " and shows update notices in the CLI and dashboard. Cadence is governed by the frequency below. Default: enabled. ",
"autoReloadDashboardOnVersionChange": " Auto-reload dashboard on version change ",
"checkForThe": " Check for the ",
"checkForUpdatesAutomatically": " Check for updates automatically ",
"cLIBinaryOnPATH": " CLI binary on PATH ",
"cLIBySpawning": " CLI by spawning",
"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. ",
"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. ",
"dailyRecommended": "Daily (recommended)",
"disableThisIfYourLocalDevProcessIs": ". Disable this if your local dev process is the source of truth and you don&apos;t want any outdated globally-installed binary executed during the probe. ",
"disableThisIfYourLocalDevProcessIs": ". Disable this if your local dev process is the source of truth and you don&apos;t want any outdated globally-installed binary executed during the probe. Default: enabled. ",
"frequency": "Frequency",
"general": "General",
"globalDefaultTrackingRepo": "Global default tracking repo",
@@ -5889,36 +5902,42 @@
"manualOnlyNeverAutoCheck": "Manual only — never auto-check",
"onStartupOncePerServerLaunch": "On startup — once per server launch",
"ownerRepo": "owner/repo",
"projectsInheritThisValueWhenTheyDoNot": "Projects inherit this value when they do not set a project default tracking repo.",
"rowsAndDoesNotAffectAssistantTextOr": " rows and does not affect assistant text or tool rows. ",
"projectsInheritThisValueWhenTheyDoNot": "Projects inherit this value when they do not set a project default tracking repo. No default — unset.",
"rowsAndDoesNotAffectAssistantTextOr": " rows and does not affect assistant text or tool rows. Default: disabled for both permanent and ephemeral agents. ",
"saveAIThinkingForEphemeralTaskWorkerAgents": " Save AI thinking for ephemeral / task-worker agents ",
"saveAIThinkingForPermanentAgents": " Save AI thinking for permanent agents ",
"saveAIThinkingLogs": "Save AI thinking logs",
"saveToolOutputInAgentLogs": " Save tool output in agent logs ",
"updates": "Updates",
"weekly": "Weekly",
"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. ",
"whenEnabledDefaultTheDashboardAutomaticallyReloadsWhen": " When enabled (default), the dashboard automatically reloads when it detects a new build version — either from server rebuilds or service worker updates. Disable this to stay on the current version until you manually refresh. ",
"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. ",
"whenEnabledDefaultTheDashboardAutomaticallyReloadsWhen": " When enabled (default), the dashboard automatically reloads when it detects a new build version — either from server rebuilds or service worker updates. Disable this to stay on the current version until you manually refresh. Default: enabled. ",
"whenEnabledFusionChecksNpmForNewVersions": " When enabled, Fusion checks npm for new versions of",
"whenEnabledTheDashboardProbesForAGlobally": " When enabled, the dashboard probes for a globally-installed"
"whenEnabledTheDashboardProbesForAGlobally": " When enabled, the dashboard probes for a globally-installed",
"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).",
"gitLabInstanceUrlHint": "Blank defaults to GitLab.com. Projects inherit this self-managed GitLab URL unless they set their own project value. No default — unset.",
"gitLabApiBaseUrlHint": "Blank derives <instance>/api/v4. Override only for self-managed GitLab API gateways that use a different absolute http:// or https:// URL. No default — unset.",
"gitLabTokenTypeHint": "No default — unset (the selector falls back to personal access token until you choose otherwise).",
"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.",
"dismissModalsByClickingOutsideHint": " When enabled, clicking or tapping a modal backdrop closes the modal. Default: disabled, to prevent accidental dismissal. "
},
"globalModels": {
"allow": "allow",
"commaSeparatedValuesSentToOpenRouterModelSync": "Comma-separated values sent to OpenRouter model sync.",
"controlsHowMuchReasoningEffortTheAIModel": "Controls how much reasoning effort the AI model uses. Higher levels produce better results but cost more.",
"commaSeparatedValuesSentToOpenRouterModelSync": "Comma-separated values sent to OpenRouter model sync. No default — unset (unfiltered).",
"controlsHowMuchReasoningEffortTheAIModel": "Controls how much reasoning effort the AI model uses. Higher levels produce better results but cost more. No default — unset (model's own default effort applies).",
"default": "Default",
"default2": "default",
"defaultAIModelUsedForTaskExecutionWhen": "Default AI model used for task execution when no per-task override is set. &quot;Use default&quot; lets the engine choose automatically.",
"defaultAIModelUsedForTaskExecutionWhen": "Default AI model used for task execution when no per-task override is set. &quot;Use default&quot; lets the engine choose automatically. No default — unset.",
"defaultModel": "Default Model",
"deny": "deny",
"fallbackModel": "Fallback Model",
"flowAndPublishesThemUnderTheOpencodeGo": " flow and publishes them under the opencode-go provider in model pickers. ",
"flowAndPublishesThemUnderTheOpencodeGo": " flow and publishes them under the opencode-go provider in model pickers. Default: enabled. ",
"fusion": "Fusion",
"globalBaselineModelsForEachAIRoleProject": " Global baseline models for each AI role. Project settings can override these per-project. ",
"httpsRunfusionAi": "https://runfusion.ai",
"latency": "latency",
"leaveEmptyToOmitThisHeaderDefaultFusion": "Leave empty to omit this header. Default: Fusion.",
"leaveEmptyToOmitThisHeaderDefaultHttps": "Leave empty to omit this header. Default: https://runfusion.ai.",
"leaveEmptyToOmitThisHeaderDefaultFusion": "Leave empty to omit this header. No default — unset (Fusion falls back to the title \"Fusion\" when unset).",
"leaveEmptyToOmitThisHeaderDefaultHttps": "Leave empty to omit this header. No default — unset (Fusion falls back to https://runfusion.ai when unset).",
"modelLanes": "Model Lanes",
"noFallback": "No fallback",
"openaiAnthropic": "openai, anthropic",
@@ -5942,10 +5961,17 @@
"thinkingEffort": "Thinking Effort",
"throughput": "throughput",
"toolsStructuredOutputs": "tools, structured_outputs",
"usedAutomaticallyIfThePrimaryDefaultModelHits": "Used automatically if the primary default model hits a retryable provider error like rate limiting or overload.",
"usedAutomaticallyIfThePrimaryDefaultModelHits": "Used automatically if the primary default model hits a retryable provider error like rate limiting or overload. No default — unset.",
"useDefault": "Use default",
"whenEnabledStartupFetchesTheLatestAvailableModels": " When enabled, startup fetches the latest available models from the OpenRouter API so model pickers always include the newest catalog. ",
"whenEnabledStartupRefreshesModelsThroughTheLocal": " When enabled, startup refreshes models through the local "
"whenEnabledStartupFetchesTheLatestAvailableModels": " When enabled, startup fetches the latest available models from the OpenRouter API so model pickers always include the newest catalog. Default: enabled. ",
"whenEnabledStartupRefreshesModelsThroughTheLocal": " When enabled, startup refreshes models through the local ",
"commaSeparatedValuesSentToOpenRouterModelSyncOutputModalities": "Comma-separated values sent to OpenRouter model sync. No default — unset (unfiltered).",
"openRouterRoutingOrderHint": "No default — unset (OpenRouter's own default routing order applies).",
"openRouterRoutingIgnoreHint": "No default — unset (no providers ignored).",
"openRouterRoutingOnlyHint": "No default — unset (no provider restriction).",
"openRouterAllowFallbacksHint": "No default — unset (OpenRouter's own default fallback behavior applies).",
"openRouterRoutingSortHint": "No default — unset (OpenRouter's own default sort applies).",
"requireParametersHint": "Default: disabled."
},
"header": {
"discord": "Discord",
@@ -5997,9 +6023,9 @@
"loading": "Loading…",
"memory": {
"03": "0 3 * * *",
"agentsGetMemorySearchMemoryGetAndMemory": "Agents get memory_search, memory_get, and memory_append tools. Search defaults to qmd with a local file fallback.",
"agentsGetMemorySearchMemoryGetAndMemory": "Agents get memory_search, memory_get, and memory_append tools. Search defaults to qmd with a local file fallback. Default: enabled.",
"agentsSearchWithQmdFirstFallBackTo": ". Agents search with qmd first, fall back to local files when qmd is missing, and open exact line windows only when needed. ",
"automaticallyCompactMemoryWhenItExceedsTheThreshold": "Automatically compact memory when it exceeds the threshold on a schedule",
"automaticallyCompactMemoryWhenItExceedsTheThreshold": "Automatically compact memory when it exceeds the threshold on a schedule. Default: disabled.",
"autoSummarizeMemory": " Auto-Summarize Memory ",
"bytesUpdated": " bytes · updated ",
"cannotSave": "Cannot save: ",
@@ -6007,8 +6033,8 @@
"compacting": "Compacting…",
"compactionThresholdChars": "Compaction Threshold (chars)",
"compactSelectedFile": "Compact Selected File",
"cronExpressionForAutoSummarizeScheduleDefaultDaily": "Cron expression for auto-summarize schedule (default: daily at 3 AM)",
"cronExpressionForDreamProcessing": "Cron expression for dream processing.",
"cronExpressionForAutoSummarizeScheduleDefaultDaily": "Cron expression for auto-summarize schedule. Default: 0 3 * * * (daily at 3 AM).",
"cronExpressionForDreamProcessing": "Cron expression for dream processing. Default: 0 4 * * * (daily at 4 AM).",
"dreamCompleted": "Dream processing completed",
"dreaming": " Dreaming… ",
"dreamNow": "Dream Now",
@@ -6027,7 +6053,7 @@
"memoryIsCurrentlyDisabledYouCanViewThe": " Memory is currently disabled. You can view the file, but editing is read-only until memory is re-enabled. ",
"memoryLivesIn": " Memory lives in ",
"memorySaved": "Memory saved",
"memoryWillBeCompactedWhenItExceedsThis": "Memory will be compacted when it exceeds this character count",
"memoryWillBeCompactedWhenItExceedsThis": "Memory will be compacted when it exceeds this character count. Default: 50000.",
"noMatchingMemoryFound": "No matching memory found.",
"processDreamsFromDailyMemory": " Process dreams from daily memory ",
"qmd": " qmd ",
@@ -6039,7 +6065,7 @@
"searchMemoryWithQmd": "Search memory with qmd",
"testing": "Testing…",
"testRetrieval": "Test Retrieval",
"turnsDailyNotesIntoDREAMSMdAndPromotes": "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md.",
"turnsDailyNotesIntoDREAMSMdAndPromotes": "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md. Default: disabled.",
"qmdInstalled": "qmd installed successfully",
"qmdInstallFailed": "Failed to install qmd",
"qmdInstallUnavailable": "qmd install finished, but qmd is still unavailable"
@@ -6057,7 +6083,7 @@
"aIOnlyAIAutoResolveAIRetryNever": "AI only — AI → auto-resolve → AI retry; never silently pick a side",
"allowAIMergeToSyncADirtyChecked": " Allow AI merge to sync a dirty checked-out integration branch ",
"alwaysPreserveDirectMergeCommitHistory": "Always preserve direct-merge commit history",
"alwaysSquashDirectMerges": "Always squash direct merges",
"alwaysSquashDirectMerges": "Always squash direct merges (default)",
"and": ", and ",
"and2": " and ",
"areListedFirstOrChoose": " are listed first — or choose ",
@@ -6084,11 +6110,11 @@
"controlsWhatHappensAfterATaskReachesIn": " Controls what happens after a task reaches In Review. Direct mode merges into the current branch locally. Pull request mode keeps the task in In Review while Fusion waits for GitHub reviews and required checks before merging the PR. ",
"createMonitorAndMergeAGitHubPullRequest": "Create, monitor, and merge a GitHub pull request",
"custom": "Custom…",
"dangerousCompatibilityEscapeHatchLeaveOffUnlessYou": " Dangerous compatibility escape hatch. Leave off unless you explicitly want the legacy stash → fast-forward → 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. ",
"dangerousCompatibilityEscapeHatchLeaveOffUnlessYou": " Dangerous compatibility escape hatch — restores the legacy stash → fast-forward → 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). ",
"defaultLogsFindingsButAutoCompletesTheMerge": " (default) logs findings but auto-completes the merge. ",
"deterministicLegacyRebaseConflictStrategyAuditPipeline": "Deterministic (legacy) — rebase / conflict-strategy / audit pipeline",
"directMergeCommitRouting": "Direct merge commit routing",
"directMergeIntoTheCurrentBranch": "Direct merge into the current branch",
"directMergeIntoTheCurrentBranch": "Direct merge into the current branch (default)",
"emailUsedInThe": "Email used in the ",
"enableTestMode": " Enable test mode ",
"fastForwardOfLocalMainFrom": " + fast-forward of local main from ",
@@ -6096,11 +6122,11 @@
"finalFallback": "final fallback",
"findsInReviewTasksWhoseAutoMergeValue": " Finds in-review tasks whose auto-merge value came from the legacy review-entry stamp. Dry-run is automatic; applying delegates to the store cleanup and preserves genuine per-task overrides. ",
"flipOverlappingFilesToPreferTheTaskBranch": "Flip overlapping files to prefer the task branch (default)",
"forcesAllAILanesToUseTheDeterministic": "Forces all AI lanes to use the deterministic mock provider. No network calls, zero token cost.",
"forcesAllAILanesToUseTheDeterministic": "Forces all AI lanes to use the deterministic mock provider. No network calls, zero token cost. No default — unset (disabled).",
"fusion": "Fusion",
"gitHubAuthentication": "GitHub Authentication",
"gitHubAuthMode": "GitHub auth mode",
"gitHubCLIGhAuth": "GitHub CLI (gh auth)",
"gitHubCLIGhAuth": "GitHub CLI (gh auth) (default)",
"gitHubPersonalAccessToken": "GitHub personal access token",
"gitRemoteToPushToEGOrigin": "Git remote to push to (e.g. \"origin\"). Can include branch name (e.g. \"origin main\"). Default: \"origin\".",
"ignoreOverlapDetectionPreserveLegacyBehavior": "Ignore overlap detection — preserve legacy behavior",
@@ -6134,7 +6160,7 @@
"planApprovalModeAutoApproveAll": "Auto-approve all tasks",
"planApprovalModeHelp": "Project-wide override for the planning approval gate. Leave on workflow to use each workflow's Require plan approval setting, or force all approved specs to bypass or wait for manual approval.",
"planApprovalModeRequireAll": "Require approval for all tasks",
"planApprovalModeWorkflow": "Use workflow setting",
"planApprovalModeWorkflow": "Use workflow setting (default)",
"postMergeAuditMode": "Post-merge audit mode",
"pushRemote": "Push Remote",
"pushToRemoteAfterMerge": " Push to remote after merge ",
@@ -6157,12 +6183,12 @@
"stashFastForwardDefaultPreserveLocalEdits": "Stash + fast-forward (default) — preserve local edits",
"stopsAfterTheFirstAIAttemptAndWaits": " stops after the first AI attempt and waits for a human. ",
"symbolicRefFallback": " symbolic ref → fallback ",
"theCanonicalBranchFusionMergesTasksIntoAnd": " The canonical branch Fusion merges tasks into and uses as the reference for all ahead/behind / overlap / pre-rebase computations. Leave on ",
"theCanonicalBranchFusionMergesTasksIntoAnd": " No default — 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 ",
"theLegacyMergeSettingsBelowDoNotApply": "The legacy merge settings below do not apply while AI merge is on.",
"toResolveViaTheStandardCascade": " to resolve via the standard cascade (",
"toTypeABranchThatDoesnAposT": " to type a branch that doesn&apos;t exist locally yet. Applies to both direct merges and pull-request mode; individual tasks can still override via task metadata. ",
"trailer": " trailer",
"trailerCreditingFusionRecognizedByGitHubForShared": " trailer crediting Fusion (recognized by GitHub for shared attribution). ",
"trailer": " trailer. Default: Fusion.",
"trailerCreditingFusionRecognizedByGitHubForShared": " trailer crediting Fusion (recognized by GitHub for shared attribution). Default: enabled. ",
"useDropdown": " Use dropdown ",
"useProjectRootLegacy": "Use project root (legacy)",
"uses": " uses ",
@@ -6172,11 +6198,17 @@
"warnOnlyKeepLegacyMainWinsFallback": "Warn only — keep legacy main-wins fallback",
"whenDisabledMergeCommitMessagesOmitTheTask": "When disabled, merge commit messages omit the task ID from the scope (e.g. ",
"whenEnabledCommitsMadeByFusionKeepYour": " When enabled, commits made by Fusion keep your git identity as the primary author and append a ",
"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.",
"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.",
"whenEnabledTasksThatPassReviewAreAutomatically": "When enabled, tasks that pass review are automatically merged into the main branch",
"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.",
"whenUsingSmartPreferMainAutomaticallyPreferThe": " When using smart-prefer-main, automatically prefer the branch side for files that main has recently modified to avoid silently discarding branch work. "
"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.",
"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.",
"whenEnabledTasksThatPassReviewAreAutomatically": "When enabled, tasks that pass review are automatically merged into the main branch. Default: enabled.",
"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.",
"whenUsingSmartPreferMainAutomaticallyPreferThe": " When using smart-prefer-main, automatically prefer the branch side for files that main has recently modified to avoid silently discarding branch work. ",
"githubAuthTokenHint": "No default — unset.",
"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).",
"gitLabPersonalAccessToken": "Personal access token (default)",
"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 — unset.",
"includeTaskIdInCommitDefault": "). Default: enabled.",
"trailerEmail": " trailer. Default: noreply@runfusion.ai."
},
"mergeManually": "Merge Manually",
"mobileNav": {
@@ -6221,31 +6253,34 @@
"selectedNode": "Selected node:",
"theseSettingsApplyAtTheProjectLevel": "These settings apply at the project level.",
"unavailableNodePolicy": "Unavailable Node Policy",
"usedWhenATaskHasNoNodeOverride": "Used when a task has no node override. Node status is shown for safer routing selection."
"usedWhenATaskHasNoNodeOverride": "Used when a task has no node override. Node status is shown for safer routing selection. No default — unset (local execution).",
"unavailableNodePolicyHint": "Default: block execution."
},
"nodeSync": {
"alwaysAsk": "Always ask",
"automaticallySynchronizeSettingsBetweenThisNodeAndConnected": "Automatically synchronize settings between this node and connected remote nodes",
"automaticallySynchronizeSettingsBetweenThisNodeAndConnected": "Automatically synchronize settings between this node and connected remote nodes. Default: disabled.",
"conflictResolution": "Conflict resolution",
"enableAutomaticSettingsSync": " Enable automatic settings sync ",
"every15Minutes": "Every 15 minutes",
"every1Hour": "Every 1 hour",
"every30Minutes": "Every 30 minutes",
"every5Minutes": "Every 5 minutes",
"includeAPIKeysAndOAuthTokensInSync": "Include API keys and OAuth tokens in sync operations",
"includeAPIKeysAndOAuthTokensInSync": "Include API keys and OAuth tokens in sync operations. Default: disabled.",
"keepLocal": "Keep local",
"keepRemote": "Keep remote",
"lastWriteWins": "Last write wins",
"nodeSync": "Node Sync",
"syncInterval": "Sync interval",
"syncModelAuthCredentials": " Sync model auth credentials ",
"workflowSettingsNotSynced": "Workflow settings are not synced across nodes yet."
"workflowSettingsNotSynced": "Workflow settings are not synced across nodes yet.",
"syncIntervalHint": "Default: every 15 minutes.",
"conflictResolutionHint": "Default: last write wins."
},
"notifications": {
"accessTokenOptional": "Access token (optional)",
"advanced": "Advanced",
"allFailuresLegacy": "All failures (legacy)",
"baseURLForDeepLinksInNotificationsWhen": " Base URL for deep links in notifications. When set, clicking a notification opens the dashboard directly to the task. ",
"baseURLForDeepLinksInNotificationsWhen": " Base URL for deep links in notifications. When set, clicking a notification opens the dashboard directly to the task. No default — unset. ",
"customNtfyServerURLOptional": "Custom ntfy server URL (optional)",
"dashboardHostname": "Dashboard Hostname",
"discord": "Discord",
@@ -6255,13 +6290,13 @@
"format": "Format",
"general": " General: ",
"generic": "Generic",
"howLongAFailureMustPersistBeforeA": " How long a failure must persist before a push notification is sent. 0 = notify immediately. ",
"howLongAFailureMustPersistBeforeA": " How long a failure must persist before a push notification is sent. 0 = notify immediately. Default: 30000 (30 seconds). ",
"httpLocalhost3000": "http://localhost:3000",
"httpsHooksExampleCom": "https://hooks.example.com/...",
"httpsNtfySh": "https://ntfy.sh",
"learnMoreAboutNtfySh": " Learn more about ntfy.sh ",
"leaveBlankToKeepTheDefaultServerHttps": " Leave blank to keep the default server: https://ntfy.sh. Custom servers must use http:// or https://. ",
"leaveBlankToPublishWithoutAuthenticationWhenSet": " Leave blank to publish without authentication. When set, Fusion sends an Authorization Bearer header with ntfy requests. ",
"leaveBlankToKeepTheDefaultServerHttps": " Leave blank to keep the default server: https://ntfy.sh. Custom servers must use http:// or https://. No default — unset. ",
"leaveBlankToPublishWithoutAuthenticationWhenSet": " Leave blank to publish without authentication. When set, Fusion sends an Authorization Bearer header with ntfy requests. No default — unset. ",
"messageInbox": " Message inbox: ",
"mustBeAValidURLStartingWithHttp": " Must be a valid URL starting with http:// or https:// ",
"myTopicName": "my-topic-name",
@@ -6283,7 +6318,11 @@
"webhook": "Webhook",
"webhookNotifications": " Webhook notifications ",
"webhookURL": "Webhook URL",
"yourNtfyShTopicName164Alphanumeric": " Your ntfy.sh topic name (1–64 alphanumeric/hyphen/underscore characters)."
"yourNtfyShTopicName164Alphanumeric": " Your ntfy.sh topic name (1–64 alphanumeric/hyphen/underscore characters). No default — unset.",
"ntfyEnabledHint": "Default: disabled.",
"webhookEnabledHint": "Default: disabled.",
"webhookUrlHint": "No default — unset.",
"webhookFormatHint": "Default: generic."
},
"plugins": {
"fusionPlugins": " Fusion Plugins ",
@@ -6296,7 +6335,7 @@
"advancedWorkflowPolicy": " Advanced workflow policy ",
"aIMergeCommitSummaries": " AI merge commit summaries ",
"aITitleAndGitCommitMessageSummarization": " AI Title and Git Commit Message Summarization ",
"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.",
"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 — unset (no cap).",
"autoSelectPresetBasedOnTaskSize": " Auto-select preset based on task size ",
"autoSummarizeLongDescriptionsAsTitles": " Auto-summarize long descriptions as titles ",
"configuredPresets": "Configured presets",
@@ -6330,8 +6369,11 @@
"tokenCap": "Token Cap",
"useDefault": "Use default",
"useWorkflowDefault": "Use workflow default",
"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. ",
"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. "
"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. ",
"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. ",
"autoSelectModelPresetHint": "Default: disabled.",
"prTitlePromptInstructionsHelp": "Guides the AI-generated Create PR title. Leave blank to use the default PR metadata prompt. No default — unset.",
"prDescriptionPromptInstructionsHelp": "Guides the AI-generated Create PR summary, changes, and testing sections. Leave blank to use the default PR metadata prompt. No default — unset."
},
"remote": {
"acceptRoutes": " Accept routes ",
@@ -6340,7 +6382,7 @@
"authenticatedURL": "Authenticated URL:",
"authLinks": "Auth Links",
"authLinkTokenType": "Auth link token type",
"automaticallyRestoreTunnelOnStartupIfItWas": "Automatically restore tunnel on startup if it was running when last stopped.",
"automaticallyRestoreTunnelOnStartupIfItWas": "Automatically restore tunnel on startup if it was running when last stopped. Default: disabled.",
"cloudflare": "Cloudflare",
"cloudflaredInstalled": "cloudflared installed successfully",
"cloudflaredIsInstalled": "cloudflared is installed",
@@ -6398,14 +6440,17 @@
"uRLAndQRGenerationUseTheSelectedToken": " URL and QR generation use the selected token type. ",
"uRLNoHostnameOrPortConfigurationNeeded": " URL — no hostname or port configuration needed.",
"useExisting": "Use Existing",
"usingQuickTunnel": "Using Quick Tunnel — automatically creates a random trycloudflare.com URL, no account needed.",
"installationFailed": "Installation failed"
"usingQuickTunnel": "Using Quick Tunnel — automatically creates a random trycloudflare.com URL, no account needed. Default: enabled.",
"installationFailed": "Installation failed",
"acceptRoutesHint": "Default: disabled.",
"shortLivedEnabledHint": "Default: disabled.",
"shortLivedTtlMsHint": "Default: 900000 (15 minutes)."
},
"researchGlobal": {
"advancedExternalSearchProviders": "Advanced — external search providers",
"alwaysOn": "Always on",
"brave": "Brave",
"builtInUsesAgentWebTools": " Built-in (uses agent web tools) ",
"builtInUsesAgentWebTools": " Built-in (uses agent web tools) (default) ",
"configureBraveTavilyAndGoogleAPIKeysIn": " Configure Brave, Tavily, and Google API keys in Authentication. ",
"customSearchEngineId": "custom-search-engine-id",
"defaultMaxConcurrentRuns": "Default Max Concurrent Runs",
@@ -6423,12 +6468,21 @@
"openAuthenticationSettings": " Open Authentication Settings ",
"requestTimeoutMs": "Request Timeout (ms)",
"researchDefaults": "Research Defaults",
"searchesAndFetchesUseTheAgentsNativeWebSearch": " Searches and fetches use the agent's native WebSearch/WebFetch tools. No API key required. ",
"searchesAndFetchesUseTheAgentsNativeWebSearch": " Searches and fetches use the agent's native WebSearch/WebFetch tools. No API key required. Default: builtin. ",
"searchProvider": "Search Provider",
"searXNG": "SearXNG",
"searXNGURL": "SearXNG URL",
"tavily": "Tavily",
"webSearch": " Web Search "
"webSearch": " Web Search ",
"searXNGURLHint": "No default — unset.",
"googleSearchCXHint": "No default — unset.",
"maxConcurrentRunsHint": "Default: 3.",
"maxSourcesPerRunHint": "Default: 20.",
"defaultMaxDurationMsHint": "Default: 300000 (5 minutes).",
"requestTimeoutMsHint": "Default: 30000 (30 seconds).",
"maxSynthesisRoundsHint": "Default: 2.",
"gitHubSourceHint": " Default: disabled. ",
"localDocsSourceHint": " Default: enabled. "
},
"researchProject": {
"alwaysOn": "Always on",
@@ -6444,7 +6498,12 @@
"projectResearchSettings": "Project Research Settings",
"requestTimeoutMs": "Request Timeout (ms)",
"webSearch": " Web Search ",
"webSearchIsAlwaysEnabledConfigureTheSearch": " Web search is always enabled. Configure the search provider under Research Defaults. "
"webSearchIsAlwaysEnabledConfigureTheSearch": " Web search is always enabled. Configure the search provider under Research Defaults. ",
"enableResearchInThisProjectHint": "Default: enabled.",
"maxConcurrentRunsHint": "Default: 3.",
"maxSourcesPerRunHint": "Default: 20.",
"maxDurationMsHint": "Default: 300000 (5 minutes).",
"requestTimeoutMsHint": "Default: 30000 (30 seconds)."
},
"resolveAllLocal": "Resolve All: Keep Local",
"resolveAllRemote": "Resolve All: Keep Remote",
@@ -6465,11 +6524,16 @@
"followUpPolicy": "Follow-up Policy",
"gpt5": "gpt-5",
"intervalMs": "Interval (ms)",
"leaveProviderAndModelBlankToInheritThe": " Leave provider and model blank to inherit the project validator lane model settings. ",
"leaveProviderAndModelBlankToInheritThe": " Leave provider and model blank to inherit the project validator lane model settings. No default — unset. ",
"openai": "openai",
"retentionDays": "Retention (days)",
"scheduledEvals": "Scheduled Evals",
"suggestOnly": "Suggest only"
"suggestOnly": "Suggest only",
"enabledHint": "Default: disabled.",
"intervalMsHint": "Default: 86400000 (24 hours).",
"evaluatorProviderHint": "No default — unset (inherits the project validator lane provider).",
"followUpPolicyHint": "Default: suggest only.",
"retentionDaysHint": "Default: 30."
},
"scheduling": {
"addIgnoredPath": " Add ignored path ",
@@ -6478,9 +6542,9 @@
"backlogNoTaskAutoClaimIsExecutorOnly": "Backlog/no-task auto-claim is executor-only by default. Enable to let engineer-role agents auto-claim unowned backlog tasks; explicit routing and delegation are unchanged. Default: off.",
"browse": " Browse ",
"closeParenPeriod": ").",
"compactModeKeepsArchiveSizeLowWhilePreserving": "Compact mode keeps archive size low while preserving recent agent activity for context.",
"compactModeKeepsArchiveSizeLowWhilePreserving": "Compact mode keeps archive size low while preserving recent agent activity for context. Default: compact.",
"compactSummaryAndRecentEntries": "Compact summary and recent entries",
"completedTasksOlderThanTheThresholdAreMoved": "Completed tasks older than the threshold are moved out of the active task database.",
"completedTasksOlderThanTheThresholdAreMoved": "Completed tasks older than the threshold are moved out of the active task database. Default: enabled.",
"docs": "docs/",
"doNotArchiveAgentLogs": "Do not archive agent logs",
"enableAutomaticTaskArchiving": " Enable automatic task archiving ",
@@ -6489,21 +6553,21 @@
"fullAgentLog": "Full agent log",
"globalMaxConcurrent": "Global Max Concurrent",
"heartbeatScopeDiscipline": "Heartbeat Scope Discipline",
"ignoreHiddenDotPathsHelp": "When enabled, overlap checks ignore hidden path segments such as .fusion/, .changeset/, .github/, .env, and nested .cache/ directories. Uncheck to restore legacy counting for stricter serialization.",
"ignoreHiddenDotPathsHelp": "When enabled, overlap checks ignore hidden path segments such as .fusion/, .changeset/, .github/, .env, and nested .cache/ directories. Uncheck to restore legacy counting for stricter serialization. Default: enabled.",
"ignoreHiddenDotPathsInOverlapChecks": " Ignore hidden dot paths in overlap checks ",
"ignoredOverlapPaths": "Ignored overlap paths",
"letEngineerAgentsAutoClaimBacklogTasks": " Let engineer agents auto-claim backlog tasks ",
"lite": "Lite",
"maxConcurrentTasks": "Max Concurrent Tasks",
"maximumAgeInHoursBeforeAPlanIs": "Maximum age in hours before a plan is considered stale. Default: 6 hours.",
"maximumConcurrentAgentsAcrossAllProjects": "Maximum concurrent agents across all projects",
"maximumConcurrentPlanningAgents": "Maximum concurrent planning agents",
"maximumConcurrentAgentsAcrossAllProjects": "Maximum concurrent agents across all projects. Default: 4.",
"maximumConcurrentPlanningAgents": "Maximum concurrent planning agents. Default: 2.",
"maximumStuckDetectorRetriesBeforeATaskIs": "Maximum stuck-detector retries before a task is marked failed. Default: 6.",
"maxStuckRetries": "Max Stuck Retries",
"maxTriageConcurrent": "Max Triage Concurrent",
"numberOfDaysATaskCanStayIn": "Number of days a task can stay in Done before it is archived. Default: 2 days (48 hours).",
"off": "Off",
"optionalFileOrDirectoryPathsToIgnoreWhen": " Optional file or directory paths to ignore when overlap serialization is enabled. Paths are project-relative (for example ",
"optionalFileOrDirectoryPathsToIgnoreWhen": " No default — unset (empty). Optional file or directory paths to ignore when overlap serialization is enabled. Paths are project-relative (for example ",
"or": " or ",
"pollIntervalMs": "Poll Interval (ms)",
"preserveStepProgressOnStuckTaskRequeue": " Preserve step progress on stuck-task requeue ",
@@ -6518,12 +6582,14 @@
"strictCoordinationFocusedHigherPerTickTokensLite": "Strict — coordination-focused; higher per-tick tokens. Lite — pre-2026-05-11 behavior. Off — minimal procedure.",
"strictDefault": "Strict (default)",
"stuckTaskTimeoutMinutes": "Stuck Task Timeout (minutes)",
"timeoutInMinutesForDetectingStuckTasksWhen": "Timeout in minutes for detecting stuck tasks. When a task&apos;s agent session shows no activity for longer than this duration, the task is terminated and retried. Leave empty to disable. Suggested: 10.",
"whenEnabledTasksThatModifyTheSameFiles": "When enabled, tasks that modify the same files are queued serially to avoid merge conflicts",
"whenEnabledTasksWithStalePlansPROMPTMd": "When enabled, tasks with stale plans (PROMPT.md older than the threshold) are automatically sent back to planning for replanning",
"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. Default: 10 minutes (600000ms).",
"whenEnabledTasksThatModifyTheSameFiles": "When enabled, tasks that modify the same files are queued serially to avoid merge conflicts. Default: enabled.",
"whenEnabledTasksWithStalePlansPROMPTMd": "When enabled, tasks with stale plans (PROMPT.md older than the threshold) are automatically sent back to planning for replanning. Default: disabled.",
"whenTheStuckDetectorKillsAndReQueues": "When the stuck detector kills and re-queues a task, keep completed step statuses so the agent can resume from where it left off. Disable to reset every step to pending on each stuck retry. Default: enabled.",
"browseWorkspacePath": "Browse workspace path",
"overlapPickerNote": "Choose a file to ignore directly, or navigate into a folder and select the current directory."
"overlapPickerNote": "Choose a file to ignore directly, or navigate into a folder and select the current directory.",
"maxConcurrentTasksHint": "Default: 2.",
"pollIntervalMsHint": "Default: 15000 (15 seconds)."
},
"scope": {
"globalBanner": "These settings are shared across all your Fusion projects.",
@@ -6534,7 +6600,7 @@
"allowSilentSiblingBranchRenameDuringExecutorConflicts": " Allow silent sibling branch rename during executor conflicts ",
"alsoRebaseOntoLocalDefaultBranchHEAD": " Also rebase onto local default-branch HEAD ",
"and": " and ",
"andCanHidePriorCommitsFromTheDefault": " and can hide prior commits from the default recovery flow. ",
"andCanHidePriorCommitsFromTheDefault": " and can hide prior commits from the default recovery flow. Default: disabled. ",
"autoDetectFusionBinWorktrunkOrPATH": "auto-detect (~/.fusion/bin/worktrunk or $PATH)",
"awaitingApproval": "Awaiting approval — open Approvals to continue.",
"branchCollisionSilentlyForksWorkOntoSiblingBranches": " branch collision silently forks work onto sibling branches like ",
@@ -6555,7 +6621,7 @@
"installTheWorktrunkBinaryBelowToEnableThis": "Install the worktrunk binary below to enable this integration.",
"installWorktrunk": "Install worktrunk binary",
"keepsProgressMovingBySwitchingToFusionApos": " keeps progress moving by switching to Fusion&apos;s built-in worktree backend. ",
"limitsTotalGitWorktreesIncludingInReviewTasks": "Limits total git worktrees including in-review tasks",
"limitsTotalGitWorktreesIncludingInReviewTasks": "Limits total git worktrees including in-review tasks. Default: 4.",
"maxWorktrees": "Max Worktrees",
"offByDefaultOptInWhenEnabledCompleted": "Off by default (opt-in). When enabled, completed task worktrees are returned to an idle pool instead of being deleted, preserving build caches for faster startup",
"openApprovals": "Open Approvals",
@@ -6567,13 +6633,13 @@
"rebaseRemote": "Rebase Remote",
"recycleWorktrees": " Recycle worktrees ",
"selectWorktreesDir": "Select worktrees directory",
"shellCommandToRunInEachNewWorktree": "Shell command to run in each new worktree after creation",
"shellCommandToRunInEachNewWorktree": "Shell command to run in each new worktree after creation. No default — unset.",
"stopsOnWorktrunkErrorsForExplicitOperatorRecovery": " stops on worktrunk errors for explicit operator recovery; ",
"taskIDEGFN042": "Task ID (e.g., FN-042)",
"taskTitleEGFixLoginBug": "Task title (e.g., fix-login-bug)",
"tryAgain": "Try again",
"useGitDefault": "Use git default",
"whenEnabledTheMergerFetchesFromTheConfigured": "When enabled, the merger fetches from the configured remote and rebases the task branch onto the latest default-branch tip before merging — catching concurrent pushes from other collaborators or fusion workers. Any conflicts the rebase surfaces flow into the existing smart/AI resolve pipeline.",
"whenEnabledTheMergerFetchesFromTheConfigured": "When enabled, the merger fetches from the configured remote and rebases the task branch onto the latest default-branch tip before merging — catching concurrent pushes from other collaborators or fusion workers. Any conflicts the rebase surfaces flow into the existing smart/AI resolve pipeline. Default: enabled.",
"whenUnsetOnlyAffectsNewlyCreatedWorktrees": " when unset. Only affects newly-created worktrees. ",
"whichRemoteToFetchForThePreMerge": " Which remote to fetch for the pre-merge rebase. \"Use git default\" falls back to the remote configured for the default branch (typically ",
"worktreeInitCommand": "Worktree Init Command",
@@ -6584,7 +6650,11 @@
"worktrunkBinaryPath": "Worktrunk binary path",
"worktrunkFailureBehavior": "Worktrunk failure behavior",
"worktrunkIntegration": "Worktrunk integration",
"worktreesPickerNote": "Navigate to the folder where Fusion should create task worktrees, then select the current directory."
"worktreesPickerNote": "Navigate to the folder where Fusion should create task worktrees, then select the current directory.",
"showWorktreeGroupingHelp": "Off by default. When enabled, WIP and processing columns always group tasks by worktree and show worktree names, including workflow-mode processing columns.",
"copyFilesHelp": "Optional. Repository-root-relative regular files are copied into fresh or pooled task worktrees before init commands run. Missing files or directories are skipped without exposing contents. Default: empty (no files copied).",
"namingStyleNotApplicableWhenRecycling": "Naming style is not applicable when recycling worktrees — pooled worktrees retain their existing names",
"howToNameFreshWorktreeDirectories": "How to name fresh worktree directories. Only applies when recycling is off. Default: random."
},
"fileBrowser": {
"currentDirectory": "Current directory:",
@@ -6594,7 +6664,14 @@
"globalTitle": "Global MCP servers",
"projectTitle": "Project MCP servers",
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers."
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
"enabledHint": "Default: disabled, with no servers configured."
},
"prompts": {
"surfaceExplanation": "Use this section for agent role system prompt templates, role assignments, and global PromptKey segment overrides. Per-workflow step prompts for prompt and gate nodes are edited in the Workflow Editor. No default — unset (built-in role prompts apply until overridden)."
},
"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 — unset (no overrides)."
}
},
"setup": {