FN-9111: Add global Quick Add Enter-save preference
Add an operator-level setting that controls whether plain Enter saves a Quick Add task while preserving Cmd/Ctrl+Enter submission. - add the global quickAddSubmitOnEnter setting, defaults, settings UI, and app context - update Quick Add keyboard handling with multiline and duplicate/in-flight safeguards - add focused settings and keyboard regression coverage, localization, documentation, and a release changeset Files changed: .changeset/fn-9111-quick-add-enter.md | 7 ++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 1 + .../core/src/__tests__/settings-defaults.test.ts | 10 ++ packages/core/src/config/settings-schema.ts | 5 + packages/core/src/types/settings/settings-scope.ts | 5 + packages/dashboard/app/App.tsx | 4 + .../dashboard/app/components/QuickEntryBox.tsx | 24 ++++- .../QuickEntryBox.submit-on-enter.test.tsx | 118 +++++++++++++++++++++ .../__tests__/SettingsModal.general.test.tsx | 18 ++++ .../app/components/settings/save-split.ts | 1 + .../sections/GlobalGeneralSection.search.ts | 9 ++ .../settings/sections/GlobalGeneralSection.tsx | 10 ++ .../settings-default-descriptions.test.tsx | 1 + .../__tests__/useQuickAddSubmitOnEnter.test.tsx | 24 +++++ packages/dashboard/app/hooks/useAppSettings.ts | 4 + .../app/hooks/useQuickAddSubmitOnEnter.ts | 21 ++++ packages/i18n/locales/en/app.json | 2 + packages/i18n/locales/es/app.json | 4 +- packages/i18n/locales/fr/app.json | 4 +- packages/i18n/locales/ko/app.json | 4 +- packages/i18n/locales/pt-BR/app.json | 4 +- packages/i18n/locales/zh-CN/app.json | 4 +- packages/i18n/locales/zh-TW/app.json | 4 +- packages/i18n/src/resources.d.ts | 4 +- 25 files changed, 284 insertions(+), 10 deletions(-) Fusion-Task-Id: FN-9111 Fusion-Task-Lineage: 246153a8-5127-48df-838c-d9b60920cd91 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-9111-quick-add-enter.md
Normal file
7
.changeset/fn-9111-quick-add-enter.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Add a global Quick Add preference for Enter-to-save behavior.
|
||||||
|
category: feature
|
||||||
|
dev: Configure `quickAddSubmitOnEnter` in Settings → Global → General.
|
||||||
@@ -643,6 +643,8 @@ Optional workflow steps declared by the active workflow are available from the q
|
|||||||
FNXC:PriorityColorCoding 2026-07-11-00:00: Priority glyphs share urgency colors across Quick Add, the New Task inline row, and task-card badges: low=info/blue, normal=muted, high=warning/amber, urgent=error/red. -->
|
FNXC:PriorityColorCoding 2026-07-11-00:00: Priority glyphs share urgency colors across Quick Add, the New Task inline row, and task-card badges: low=info/blue, normal=muted, high=warning/amber, urgent=error/red. -->
|
||||||
Quick Add and Inline Create model selection include Plan, Executor, Reviewer, and Merger lanes. Each lane can inherit its default or select a task-specific model; Plan, Reviewer, and Merger also provide independent thinking-level overrides.
|
Quick Add and Inline Create model selection include Plan, Executor, Reviewer, and Merger lanes. Each lane can inherit its default or select a task-specific model; Plan, Reviewer, and Merger also provide independent thinking-level overrides.
|
||||||
|
|
||||||
|
Quick Add saves with Enter by default. Operators who prefer multi-line descriptions can disable **Settings → Global → General → Press Enter to save a task in Quick Add**; plain Enter then inserts a newline, while Cmd/Ctrl+Enter saves.
|
||||||
|
|
||||||
<!-- FNXC:QuickAddStart 2026-07-22-17:45: Coding (Ideas) Start is an atomic Todo create, while ordinary Save and Enter remain create-only in Ideas.
|
<!-- FNXC:QuickAddStart 2026-07-22-17:45: Coding (Ideas) Start is an atomic Todo create, while ordinary Save and Enter remain create-only in Ideas.
|
||||||
FNXC:QuickAddStart 2026-07-24-11:20: Start is now a visible action-row button for eligible workflows instead of a hidden long-press/right-click menu on Save; eligibility, snapshotting, and fail-closed routing are unchanged. -->
|
FNXC:QuickAddStart 2026-07-24-11:20: Start is now a visible action-row button for eligible workflows instead of a hidden long-press/right-click menu on Save; eligibility, snapshotting, and fail-closed routing are unchanged. -->
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ Defaults from `DEFAULT_GLOBAL_SETTINGS`; key scope from `GLOBAL_SETTINGS_KEYS`.
|
|||||||
| `dashboardFontScalePct` | `number` | `100` | Dashboard font scale percentage used by Appearance settings. Valid range: `85` to `125`; applied pre-hydration via document root font-size so board typography (column headers/counts, task cards, and quick-entry text) scales with the setting from first paint. |
|
| `dashboardFontScalePct` | `number` | `100` | Dashboard font scale percentage used by Appearance settings. Valid range: `85` to `125`; applied pre-hydration via document root font-size so board typography (column headers/counts, task cards, and quick-entry text) scales with the setting from first paint. |
|
||||||
| `dismissModalsOnOutsideClick` | `boolean` | `false` | Global dashboard preference for closing fixed modal overlays by clicking/tapping the backdrop. Off by default to prevent accidental modal dismissal; explicit close, cancel, and Escape paths remain available. |
|
| `dismissModalsOnOutsideClick` | `boolean` | `false` | Global dashboard preference for closing fixed modal overlays by clicking/tapping the backdrop. Off by default to prevent accidental modal dismissal; explicit close, cancel, and Escape paths remain available. |
|
||||||
| `skipConfirmationDialogs` | `boolean` | `false` | Global-only operator preference that skips centralized confirmation dialogs for critical actions. When enabled, destructive actions such as deleting a task or resetting progress immediately take the dialog's primary/default action; project settings cannot enable it for shared-project collaborators. |
|
| `skipConfirmationDialogs` | `boolean` | `false` | Global-only operator preference that skips centralized confirmation dialogs for critical actions. When enabled, destructive actions such as deleting a task or resetting progress immediately take the dialog's primary/default action; project settings cannot enable it for shared-project collaborators. |
|
||||||
|
| `quickAddSubmitOnEnter` | `boolean` | `true` | Global-only operator keyboard preference for Quick Add. Enter saves by default; disabling it makes Enter insert a newline and reserves Cmd/Ctrl+Enter as the save accelerator. |
|
||||||
| `credentialInstanceId` | `string` | `undefined` | Optional named credential instance for a resolved model selection. A named instance takes precedence over the provider default for that session and is scoped to that provider only; fallback providers retain their own default credentials. A deleted/malformed named instance uses the provider default and records `credentialInstanceMissing` with requested/resolved ids on `session:runtime-resolved`; no provider default is a visible resolution error. |
|
| `credentialInstanceId` | `string` | `undefined` | Optional named credential instance for a resolved model selection. A named instance takes precedence over the provider default for that session and is scoped to that provider only; fallback providers retain their own default credentials. A deleted/malformed named instance uses the provider default and records `credentialInstanceMissing` with requested/resolved ids on `session:runtime-resolved`; no provider default is a visible resolution error. |
|
||||||
| `defaultProvider` | `string` | `undefined` | Default AI provider. Anthropic has three independent surfaces, all executing on the direct `anthropic` provider except the CLI: (1) **direct OAuth** — a Claude subscription/OAuth login drives `anthropic/*` selections; Fusion sends the OAuth token to `https://api.anthropic.com/v1` with Claude Code identity headers (the same path the Claude Code CLI uses), so a subscription needs no API key. Credentials live under the `anthropic-subscription` auth/status/usage/banner id but are resolved for the direct provider at runtime; they are never stored or resolved as raw `ANTHROPIC_API_KEY` material. (2) **raw API key** — `ANTHROPIC_API_KEY`, a `models.json` `apiKey`, or an `api_key` auth credential uses `x-api-key` on the same direct provider and takes precedence over OAuth by default (see [`anthropicAuthPreference`](#anthropicauthpreference) to invert this). (3) **Claude CLI** — the explicit `pi-claude-cli` model provider runs sessions through the local `claude` CLI. There is no runtime rerouting between these surfaces. |
|
| `defaultProvider` | `string` | `undefined` | Default AI provider. Anthropic has three independent surfaces, all executing on the direct `anthropic` provider except the CLI: (1) **direct OAuth** — a Claude subscription/OAuth login drives `anthropic/*` selections; Fusion sends the OAuth token to `https://api.anthropic.com/v1` with Claude Code identity headers (the same path the Claude Code CLI uses), so a subscription needs no API key. Credentials live under the `anthropic-subscription` auth/status/usage/banner id but are resolved for the direct provider at runtime; they are never stored or resolved as raw `ANTHROPIC_API_KEY` material. (2) **raw API key** — `ANTHROPIC_API_KEY`, a `models.json` `apiKey`, or an `api_key` auth credential uses `x-api-key` on the same direct provider and takes precedence over OAuth by default (see [`anthropicAuthPreference`](#anthropicauthpreference) to invert this). (3) **Claude CLI** — the explicit `pi-claude-cli` model provider runs sessions through the local `claude` CLI. There is no runtime rerouting between these surfaces. |
|
||||||
| `defaultModelId` | `string` | `undefined` | Default AI model ID. |
|
| `defaultModelId` | `string` | `undefined` | Default AI model ID. |
|
||||||
|
|||||||
@@ -291,6 +291,16 @@ describe("settings defaults invariants", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("quickAddSubmitOnEnter default", () => {
|
||||||
|
it("defaults Quick Add Enter submission on and global-scoped only", () => {
|
||||||
|
expect(DEFAULT_GLOBAL_SETTINGS.quickAddSubmitOnEnter).toBe(true);
|
||||||
|
expect(GLOBAL_SETTINGS_KEYS).toContain("quickAddSubmitOnEnter");
|
||||||
|
expect("quickAddSubmitOnEnter" in DEFAULT_PROJECT_SETTINGS).toBe(false);
|
||||||
|
expect(PROJECT_SETTINGS_KEYS).not.toContain("quickAddSubmitOnEnter");
|
||||||
|
expect(isGlobalOnlySettingsKey("quickAddSubmitOnEnter")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("mergeIntegrationWorktree default", () => {
|
describe("mergeIntegrationWorktree default", () => {
|
||||||
it("defaults project settings to reuse-task-worktree", () => {
|
it("defaults project settings to reuse-task-worktree", () => {
|
||||||
expect(DEFAULT_PROJECT_SETTINGS.mergeIntegrationWorktree).toBe("reuse-task-worktree");
|
expect(DEFAULT_PROJECT_SETTINGS.mergeIntegrationWorktree).toBe("reuse-task-worktree");
|
||||||
|
|||||||
@@ -145,6 +145,11 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
|||||||
Critical-action confirmation dialogs stay enabled by default. This global-only preference may opt an operator into primary/default auto-approval, but project settings cannot enable it for collaborators.
|
Critical-action confirmation dialogs stay enabled by default. This global-only preference may opt an operator into primary/default auto-approval, but project settings cannot enable it for collaborators.
|
||||||
*/
|
*/
|
||||||
skipConfirmationDialogs: false,
|
skipConfirmationDialogs: false,
|
||||||
|
/*
|
||||||
|
FNXC:QuickEntry 2026-08-16-03:15:
|
||||||
|
Quick Add keeps its historical Enter-to-submit behavior by default; only an operator's global preference may opt into newline-first entry.
|
||||||
|
*/
|
||||||
|
quickAddSubmitOnEnter: true,
|
||||||
language: undefined,
|
language: undefined,
|
||||||
defaultProvider: undefined,
|
defaultProvider: undefined,
|
||||||
defaultCredentialInstanceId: undefined,
|
defaultCredentialInstanceId: undefined,
|
||||||
|
|||||||
@@ -353,6 +353,11 @@ export interface GlobalSettings {
|
|||||||
* This global operator preference defaults to false. When enabled, the dashboard skips centralized critical-action confirmations and proceeds with their primary/default choice. It must never be project-scoped so shared projects cannot force destructive actions without a prompt.
|
* This global operator preference defaults to false. When enabled, the dashboard skips centralized critical-action confirmations and proceeds with their primary/default choice. It must never be project-scoped so shared projects cannot force destructive actions without a prompt.
|
||||||
*/
|
*/
|
||||||
skipConfirmationDialogs?: boolean;
|
skipConfirmationDialogs?: boolean;
|
||||||
|
/**
|
||||||
|
* FNXC:QuickEntry 2026-08-16-03:15:
|
||||||
|
* This global-only operator keyboard preference defaults to true to preserve Enter-submits behavior. When disabled, Enter inserts a newline and Cmd/Ctrl+Enter submits; shared projects must never change an operator's keyboard behavior.
|
||||||
|
*/
|
||||||
|
quickAddSubmitOnEnter?: boolean;
|
||||||
/** Active UI locale (e.g. `"en"`, `"zh-CN"`, `"fr"`). One of `SUPPORTED_LOCALES`.
|
/** Active UI locale (e.g. `"en"`, `"zh-CN"`, `"fr"`). One of `SUPPORTED_LOCALES`.
|
||||||
* When unset, each surface resolves the locale at runtime (browser/env
|
* When unset, each surface resolves the locale at runtime (browser/env
|
||||||
* detection) and falls back to `DEFAULT_LOCALE` ("en"). */
|
* detection) and falls back to `DEFAULT_LOCALE` ("en"). */
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import { useModalManager, type DetailTaskOrigin, type DetailTaskTab } from "./ho
|
|||||||
import { useAppSettings } from "./hooks/useAppSettings";
|
import { useAppSettings } from "./hooks/useAppSettings";
|
||||||
import { useDashboardKeyboardShortcuts } from "./hooks/useDashboardKeyboardShortcuts";
|
import { useDashboardKeyboardShortcuts } from "./hooks/useDashboardKeyboardShortcuts";
|
||||||
import { ModalDismissPreferenceProvider } from "./hooks/useOverlayDismiss";
|
import { ModalDismissPreferenceProvider } from "./hooks/useOverlayDismiss";
|
||||||
|
import { QuickAddSubmitOnEnterProvider } from "./hooks/useQuickAddSubmitOnEnter";
|
||||||
import { useDeepLink } from "./hooks/useDeepLink";
|
import { useDeepLink } from "./hooks/useDeepLink";
|
||||||
import { useFavorites } from "./hooks/useFavorites";
|
import { useFavorites } from "./hooks/useFavorites";
|
||||||
import { useAuthOnboarding } from "./hooks/useAuthOnboarding";
|
import { useAuthOnboarding } from "./hooks/useAuthOnboarding";
|
||||||
@@ -864,6 +865,7 @@ function AppInner() {
|
|||||||
quickChatCloseOnOutsideClick,
|
quickChatCloseOnOutsideClick,
|
||||||
dashboardKeyboardShortcuts,
|
dashboardKeyboardShortcuts,
|
||||||
dismissModalsOnOutsideClick,
|
dismissModalsOnOutsideClick,
|
||||||
|
quickAddSubmitOnEnter,
|
||||||
skipConfirmationDialogs,
|
skipConfirmationDialogs,
|
||||||
maxTotalRetriesBeforeFail,
|
maxTotalRetriesBeforeFail,
|
||||||
prAuthAvailable,
|
prAuthAvailable,
|
||||||
@@ -1861,6 +1863,7 @@ function AppInner() {
|
|||||||
return (
|
return (
|
||||||
<ConfirmDialogProvider skipConfirmations={skipConfirmationDialogs}>
|
<ConfirmDialogProvider skipConfirmations={skipConfirmationDialogs}>
|
||||||
<ModalDismissPreferenceProvider enabled={dismissModalsOnOutsideClick}>
|
<ModalDismissPreferenceProvider enabled={dismissModalsOnOutsideClick}>
|
||||||
|
<QuickAddSubmitOnEnterProvider enabled={quickAddSubmitOnEnter}>
|
||||||
<NavigationHistoryProvider value={{ pushNav, replaceCurrent, removeNav }}>
|
<NavigationHistoryProvider value={{ pushNav, replaceCurrent, removeNav }}>
|
||||||
<FileBrowserProvider openFile={openFileInBrowser}>
|
<FileBrowserProvider openFile={openFileInBrowser}>
|
||||||
<RetryWarningProvider value={maxTotalRetriesBeforeFail * RETRY_WARNING_RATIO}>
|
<RetryWarningProvider value={maxTotalRetriesBeforeFail * RETRY_WARNING_RATIO}>
|
||||||
@@ -2288,6 +2291,7 @@ function AppInner() {
|
|||||||
</RetryWarningProvider>
|
</RetryWarningProvider>
|
||||||
</FileBrowserProvider>
|
</FileBrowserProvider>
|
||||||
</NavigationHistoryProvider>
|
</NavigationHistoryProvider>
|
||||||
|
</QuickAddSubmitOnEnterProvider>
|
||||||
</ModalDismissPreferenceProvider>
|
</ModalDismissPreferenceProvider>
|
||||||
</ConfirmDialogProvider>
|
</ConfirmDialogProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { getPriorityColorVar, getPriorityIcon, getPriorityLabel } from "../utils
|
|||||||
import { validateQuickAddStartWorkflow, workflowSupportsQuickAddStart, resolveQuickAddStartInitialColumn, resolveQuickAddStartTargetColumn, type ValidatedQuickAddWorkflow } from "../utils/quickAddStart";
|
import { validateQuickAddStartWorkflow, workflowSupportsQuickAddStart, resolveQuickAddStartInitialColumn, resolveQuickAddStartTargetColumn, type ValidatedQuickAddWorkflow } from "../utils/quickAddStart";
|
||||||
import { computeFixedMenuPosition, getLayoutViewportSize } from "../utils/fixedMenuPosition";
|
import { computeFixedMenuPosition, getLayoutViewportSize } from "../utils/fixedMenuPosition";
|
||||||
import { isInsidePortaledModelMenu } from "../utils/portalSurfaces";
|
import { isInsidePortaledModelMenu } from "../utils/portalSurfaces";
|
||||||
|
import { useQuickAddSubmitOnEnter } from "../hooks/useQuickAddSubmitOnEnter";
|
||||||
|
|
||||||
const STORAGE_KEY = "kb-quick-entry-text";
|
const STORAGE_KEY = "kb-quick-entry-text";
|
||||||
const ALLOWED_TASK_ATTACHMENT_TYPES = new Set([
|
const ALLOWED_TASK_ATTACHMENT_TYPES = new Set([
|
||||||
@@ -89,6 +90,8 @@ interface QuickEntryBoxProps {
|
|||||||
List view renders quick-add as a COMPACT single-line input so the box isn't tall. When true, the textarea stays one line: isExpanded initializes false, focus does NOT auto-expand it, and auto-resize-to-scrollHeight is short-circuited (capped to the one-line min-height). Board/columns omit singleLine, preserving the tall 80px + auto-grow behavior. singleLine governs only textarea height, not the disclosure/controls panel (which List already collapses via defaultExpanded={false}).
|
List view renders quick-add as a COMPACT single-line input so the box isn't tall. When true, the textarea stays one line: isExpanded initializes false, focus does NOT auto-expand it, and auto-resize-to-scrollHeight is short-circuited (capped to the one-line min-height). Board/columns omit singleLine, preserving the tall 80px + auto-grow behavior. singleLine governs only textarea height, not the disclosure/controls panel (which List already collapses via defaultExpanded={false}).
|
||||||
*/
|
*/
|
||||||
singleLine?: boolean;
|
singleLine?: boolean;
|
||||||
|
/** Explicit override of the global Enter-submit preference. */
|
||||||
|
submitOnEnter?: boolean;
|
||||||
/**
|
/**
|
||||||
* Favorited provider IDs from shared app-level state.
|
* Favorited provider IDs from shared app-level state.
|
||||||
* When provided (alongside availableModels), the component uses these
|
* When provided (alongside availableModels), the component uses these
|
||||||
@@ -158,8 +161,10 @@ function hasMeaningfulNodeChoice(nodes: NodeInfo[]): boolean {
|
|||||||
return nodes.length > 1 || nodes.some((node) => node.type !== "local");
|
return nodes.length > 1 || nodes.some((node) => node.type !== "local");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function QuickEntryBox({ onCreate, onMoveTask, addToast, tasks = [], availableModels, onSubtaskBreakdown, workflowId, workflowOptions, defaultWorkflowId, projectId, autoExpand = true, defaultExpanded = true, singleLine = false, favoriteProviders: parentFavoriteProviders, favoriteModels: parentFavoriteModels, onToggleFavorite: parentToggleFavorite, onToggleModelFavorite: parentToggleModelFavorite, onOpenTask }: QuickEntryBoxProps) {
|
export function QuickEntryBox({ onCreate, onMoveTask, addToast, tasks = [], availableModels, onSubtaskBreakdown, workflowId, workflowOptions, defaultWorkflowId, projectId, autoExpand = true, defaultExpanded = true, singleLine = false, submitOnEnter, favoriteProviders: parentFavoriteProviders, favoriteModels: parentFavoriteModels, onToggleFavorite: parentToggleFavorite, onToggleModelFavorite: parentToggleModelFavorite, onOpenTask }: QuickEntryBoxProps) {
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
|
const contextSubmitOnEnter = useQuickAddSubmitOnEnter();
|
||||||
|
const enterSubmits = submitOnEnter ?? contextSubmitOnEnter;
|
||||||
const [description, setDescription] = useState(() => {
|
const [description, setDescription] = useState(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
return getScopedItem(STORAGE_KEY, projectId) || "";
|
return getScopedItem(STORAGE_KEY, projectId) || "";
|
||||||
@@ -1025,11 +1030,23 @@ export function QuickEntryBox({ onCreate, onMoveTask, addToast, tasks = [], avai
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Enter without Shift submits
|
|
||||||
e.preventDefault();
|
|
||||||
if (duplicateMatches || submitInFlightRef.current) {
|
if (duplicateMatches || submitInFlightRef.current) {
|
||||||
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
FNXC:QuickEntry 2026-08-16-03:15:
|
||||||
|
The global Quick Add option defaults on to preserve historical Enter submission. When disabled, plain Enter remains a browser newline while Cmd/Ctrl+Enter stays the explicit save accelerator.
|
||||||
|
*/
|
||||||
|
if (e.metaKey || e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!enterSubmits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
} else if (e.key === "Escape") {
|
} else if (e.key === "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1100,6 +1117,7 @@ export function QuickEntryBox({ onCreate, onMoveTask, addToast, tasks = [], avai
|
|||||||
projectId,
|
projectId,
|
||||||
setIsDisclosureExpanded,
|
setIsDisclosureExpanded,
|
||||||
duplicateMatches,
|
duplicateMatches,
|
||||||
|
enterSubmits,
|
||||||
singleLine,
|
singleLine,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { createEvent, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
|
import { QuickEntryBox } from "../QuickEntryBox";
|
||||||
|
import { QuickAddSubmitOnEnterProvider } from "../../hooks/useQuickAddSubmitOnEnter";
|
||||||
|
import { checkDuplicateTasks } from "../../api";
|
||||||
|
|
||||||
|
vi.mock("../../api", () => ({
|
||||||
|
checkDuplicateTasks: vi.fn().mockResolvedValue([]),
|
||||||
|
fetchModels: vi.fn().mockResolvedValue({ models: [], favoriteProviders: [], favoriteModels: [] }),
|
||||||
|
fetchSettings: vi.fn().mockResolvedValue({}),
|
||||||
|
fetchAuthStatus: vi.fn().mockResolvedValue({ providers: [] }),
|
||||||
|
fetchAgents: vi.fn().mockResolvedValue([]),
|
||||||
|
fetchWorkflowOptionalSteps: vi.fn().mockResolvedValue([]),
|
||||||
|
refineText: vi.fn(),
|
||||||
|
uploadAttachment: vi.fn().mockResolvedValue({}),
|
||||||
|
updateGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||||
|
}));
|
||||||
|
vi.mock("../../hooks/useComposerDictation", () => ({
|
||||||
|
useComposerDictation: () => ({ micProps: { enabled: false, supported: false, state: "idle", start: vi.fn(), stop: vi.fn() } }),
|
||||||
|
}));
|
||||||
|
vi.mock("../../hooks/useNodes", () => ({ useNodes: () => ({ nodes: [], loading: false, error: null }) }));
|
||||||
|
|
||||||
|
const duplicate = { id: "FN-existing", title: "Existing", description: "Existing task", column: "todo", score: 0.9 };
|
||||||
|
|
||||||
|
function renderEntry({ enabled, singleLine = false }: { enabled?: boolean; singleLine?: boolean } = {}) {
|
||||||
|
const onCreate = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const entry = <QuickEntryBox addToast={vi.fn()} onCreate={onCreate} tasks={[]} singleLine={singleLine} defaultExpanded={false} />;
|
||||||
|
render(enabled === undefined ? entry : <QuickAddSubmitOnEnterProvider enabled={enabled}>{entry}</QuickAddSubmitOnEnterProvider>);
|
||||||
|
const textarea = screen.getByPlaceholderText("Add a task...") as HTMLTextAreaElement;
|
||||||
|
fireEvent.change(textarea, { target: { value: "Task description" } });
|
||||||
|
return { onCreate, textarea };
|
||||||
|
}
|
||||||
|
|
||||||
|
function pressEnter(textarea: HTMLTextAreaElement, options: KeyboardEventInit = {}) {
|
||||||
|
const event = createEvent.keyDown(textarea, { key: "Enter", ...options });
|
||||||
|
fireEvent(textarea, event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.mocked(checkDuplicateTasks).mockReset();
|
||||||
|
vi.mocked(checkDuplicateTasks).mockResolvedValue([]);
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("QuickEntryBox submit-on-Enter preference", () => {
|
||||||
|
it.each([{ singleLine: false, host: "Board" }, { singleLine: true, host: "List" }])("submits on Enter by default in $host Quick Add", async ({ singleLine }) => {
|
||||||
|
const { onCreate, textarea } = renderEntry({ singleLine });
|
||||||
|
const event = pressEnter(textarea);
|
||||||
|
expect(event.defaultPrevented).toBe(true);
|
||||||
|
await waitFor(() => expect(onCreate).toHaveBeenCalledTimes(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the enabled provider in the Board host", async () => {
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled: true });
|
||||||
|
expect(pressEnter(textarea).defaultPrevented).toBe(true);
|
||||||
|
await waitFor(() => expect(onCreate).toHaveBeenCalledTimes(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([{ singleLine: false, host: "Board" }, { singleLine: true, host: "List" }])("keeps plain Enter as a browser newline when disabled in $host Quick Add", ({ singleLine }) => {
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled: false, singleLine });
|
||||||
|
const event = pressEnter(textarea);
|
||||||
|
expect(event.defaultPrevented).toBe(false);
|
||||||
|
// jsdom does not perform textarea's native default action, so model the browser newline after proving it was allowed.
|
||||||
|
fireEvent.change(textarea, { target: { value: `${textarea.value}\n` } });
|
||||||
|
expect(textarea).toHaveValue("Task description\n");
|
||||||
|
expect(onCreate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([{ metaKey: true, platform: "Cmd" }, { ctrlKey: true, platform: "Ctrl" }])("uses $platform+Enter as the save accelerator when disabled", async (modifier) => {
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled: false });
|
||||||
|
expect(pressEnter(textarea, modifier).defaultPrevented).toBe(true);
|
||||||
|
await waitFor(() => expect(onCreate).toHaveBeenCalledTimes(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([{ enabled: true }, { enabled: false }])("keeps Shift+Enter non-submitting and does not expand the single-line textarea when preference is $enabled", ({ enabled }) => {
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled, singleLine: true });
|
||||||
|
expect(textarea.closest(".quick-entry-box")?.className).not.toContain("expanded");
|
||||||
|
expect(pressEnter(textarea, { shiftKey: true }).defaultPrevented).toBe(false);
|
||||||
|
expect(onCreate).not.toHaveBeenCalled();
|
||||||
|
expect(textarea.closest(".quick-entry-box")?.className).not.toContain("expanded");
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(["", " \n "])("never creates an empty or whitespace-only task (%j)", async (description) => {
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled: true });
|
||||||
|
fireEvent.change(textarea, { target: { value: description } });
|
||||||
|
expect(pressEnter(textarea).defaultPrevented).toBe(true);
|
||||||
|
await waitFor(() => expect(onCreate).not.toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([{ enabled: true }, { enabled: false }])("keeps Enter a no-op while duplicate confirmation is open when preference is $enabled", async ({ enabled }) => {
|
||||||
|
vi.mocked(checkDuplicateTasks).mockResolvedValueOnce([duplicate]);
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled });
|
||||||
|
pressEnter(textarea, enabled ? {} : { ctrlKey: true });
|
||||||
|
await screen.findByText("Possible duplicates");
|
||||||
|
const event = pressEnter(textarea);
|
||||||
|
expect(event.defaultPrevented).toBe(true);
|
||||||
|
expect(onCreate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([{ enabled: true }, { enabled: false }])("keeps Enter a no-op while a submission is in flight when preference is $enabled", async ({ enabled }) => {
|
||||||
|
let resolveDuplicateCheck!: (matches: typeof duplicate[]) => void;
|
||||||
|
vi.mocked(checkDuplicateTasks).mockImplementationOnce(() => new Promise((resolve) => { resolveDuplicateCheck = resolve; }));
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled });
|
||||||
|
pressEnter(textarea, enabled ? {} : { ctrlKey: true });
|
||||||
|
const event = pressEnter(textarea);
|
||||||
|
expect(event.defaultPrevented).toBe(true);
|
||||||
|
resolveDuplicateCheck([]);
|
||||||
|
await waitFor(() => expect(onCreate).toHaveBeenCalledTimes(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the same disabled Enter behavior at the mobile viewport", () => {
|
||||||
|
vi.spyOn(window, "innerWidth", "get").mockReturnValue(375);
|
||||||
|
const { onCreate, textarea } = renderEntry({ enabled: false });
|
||||||
|
expect(pressEnter(textarea).defaultPrevented).toBe(false);
|
||||||
|
expect(onCreate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1194,6 +1194,24 @@ describe("SettingsModal", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("saves quickAddSubmitOnEnter only via global settings payload", async () => {
|
||||||
|
renderModal({ initialSection: "global-general" });
|
||||||
|
await waitForSettingsModalReady();
|
||||||
|
|
||||||
|
vi.useFakeTimers();
|
||||||
|
fireEvent.click(screen.getByRole("checkbox", { name: "Press Enter to save a task in Quick Add" }));
|
||||||
|
await act(async () => { await vi.advanceTimersByTimeAsync(500); });
|
||||||
|
expect(mockUpdateGlobalSettings).toHaveBeenCalled();
|
||||||
|
vi.useRealTimers();
|
||||||
|
|
||||||
|
const globalPayload = mockUpdateGlobalSettings.mock.calls[0]?.[0] as Record<string, unknown>;
|
||||||
|
expect(globalPayload.quickAddSubmitOnEnter).toBe(false);
|
||||||
|
if (mockUpdateSettings.mock.calls.length > 0) {
|
||||||
|
const projectPayload = mockUpdateSettings.mock.calls[0]?.[0] as Record<string, unknown>;
|
||||||
|
expect(projectPayload.quickAddSubmitOnEnter).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("saves persistAgentToolOutput only via global settings payload", async () => {
|
it("saves persistAgentToolOutput only via global settings payload", async () => {
|
||||||
renderModal({ initialSection: "global-general" });
|
renderModal({ initialSection: "global-general" });
|
||||||
await waitForSettingsModalReady();
|
await waitForSettingsModalReady();
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export const GLOBAL_SECTION_KEYS: Record<string, ReadonlySet<string>> = {
|
|||||||
"language",
|
"language",
|
||||||
"dismissModalsOnOutsideClick",
|
"dismissModalsOnOutsideClick",
|
||||||
"skipConfirmationDialogs",
|
"skipConfirmationDialogs",
|
||||||
|
"quickAddSubmitOnEnter",
|
||||||
"persistAgentToolOutput",
|
"persistAgentToolOutput",
|
||||||
"agentToolOutputMaxChars",
|
"agentToolOutputMaxChars",
|
||||||
"proactiveTaskChatEnabled",
|
"proactiveTaskChatEnabled",
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ export const globalGeneralSearchEntries: SettingsSearchEntry[] = [
|
|||||||
" When enabled, destructive actions such as deleting a task or resetting progress run immediately without a prompt. Default: disabled",
|
" When enabled, destructive actions such as deleting a task or resetting progress run immediately without a prompt. Default: disabled",
|
||||||
keywords: ["confirm", "critical action", "delete", "reset", "destructive"],
|
keywords: ["confirm", "critical action", "delete", "reset", "destructive"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
sectionId: "global-general",
|
||||||
|
key: "quickAddSubmitOnEnter",
|
||||||
|
labelKey: "settings.globalGeneral.quickAddSubmitOnEnter",
|
||||||
|
labelFallback: " Press Enter to save a task in Quick Add ",
|
||||||
|
helpKey: "settings.globalGeneral.quickAddSubmitOnEnterHint",
|
||||||
|
helpFallback: " Default: enabled. When disabled, Enter inserts a newline and Cmd/Ctrl+Enter saves. ",
|
||||||
|
keywords: ["enter", "keyboard", "quick add", "newline", "submit"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
sectionId: "global-general",
|
sectionId: "global-general",
|
||||||
key: "persistAgentToolOutput",
|
key: "persistAgentToolOutput",
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ export function GlobalGeneralSection({ form, setForm }: GlobalGeneralSectionProp
|
|||||||
value={form.skipConfirmationDialogs === true}
|
value={form.skipConfirmationDialogs === true}
|
||||||
onChange={(v) => setForm((f) => ({ ...f, skipConfirmationDialogs: v === true }))}
|
onChange={(v) => setForm((f) => ({ ...f, skipConfirmationDialogs: v === true }))}
|
||||||
/>
|
/>
|
||||||
|
<SettingsToggleRow
|
||||||
|
descriptor={{
|
||||||
|
key: "quickAddSubmitOnEnter",
|
||||||
|
label: t("settings.globalGeneral.quickAddSubmitOnEnter", " Press Enter to save a task in Quick Add "),
|
||||||
|
help: t("settings.globalGeneral.quickAddSubmitOnEnterHint", " Default: enabled. When disabled, Enter inserts a newline and Cmd/Ctrl+Enter saves. "),
|
||||||
|
scope: "global",
|
||||||
|
}}
|
||||||
|
value={form.quickAddSubmitOnEnter !== false}
|
||||||
|
onChange={(v) => setForm((f) => ({ ...f, quickAddSubmitOnEnter: v === true }))}
|
||||||
|
/>
|
||||||
<SettingsToggleRow
|
<SettingsToggleRow
|
||||||
descriptor={{
|
descriptor={{
|
||||||
key: "persistAgentToolOutput",
|
key: "persistAgentToolOutput",
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const SETTING_DESCRIPTION_KEYS: Record<string, string> = {
|
|||||||
gitlabAuthToken: "globalGeneral.gitLabAuthTokenHint",
|
gitlabAuthToken: "globalGeneral.gitLabAuthTokenHint",
|
||||||
dismissModalsOnOutsideClick: "globalGeneral.dismissModalsByClickingOutsideHint",
|
dismissModalsOnOutsideClick: "globalGeneral.dismissModalsByClickingOutsideHint",
|
||||||
skipConfirmationDialogs: "globalGeneral.skipConfirmationDialogsHint",
|
skipConfirmationDialogs: "globalGeneral.skipConfirmationDialogsHint",
|
||||||
|
quickAddSubmitOnEnter: "globalGeneral.quickAddSubmitOnEnterHint",
|
||||||
persistAgentToolOutput: "globalGeneral.whenDisabledToolRowsAreStillLoggedBut",
|
persistAgentToolOutput: "globalGeneral.whenDisabledToolRowsAreStillLoggedBut",
|
||||||
agentToolOutputMaxChars: "globalGeneral.agentToolOutputLimitHint",
|
agentToolOutputMaxChars: "globalGeneral.agentToolOutputLimitHint",
|
||||||
proactiveTaskChatEnabled: "globalGeneral.enableProactiveTaskChatHint",
|
proactiveTaskChatEnabled: "globalGeneral.enableProactiveTaskChatHint",
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { QuickAddSubmitOnEnterProvider, useQuickAddSubmitOnEnter } from "../useQuickAddSubmitOnEnter";
|
||||||
|
|
||||||
|
function PreferenceValue() {
|
||||||
|
return <output>{String(useQuickAddSubmitOnEnter())}</output>;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("useQuickAddSubmitOnEnter", () => {
|
||||||
|
it("defaults to enabled without a provider", () => {
|
||||||
|
render(<PreferenceValue />);
|
||||||
|
expect(screen.getByText("true")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reads a disabled provider value", () => {
|
||||||
|
render(<QuickAddSubmitOnEnterProvider enabled={false}><PreferenceValue /></QuickAddSubmitOnEnterProvider>);
|
||||||
|
expect(screen.getByText("false")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reads an enabled provider value", () => {
|
||||||
|
render(<QuickAddSubmitOnEnterProvider enabled><PreferenceValue /></QuickAddSubmitOnEnterProvider>);
|
||||||
|
expect(screen.getByText("true")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -39,6 +39,7 @@ export interface UseAppSettingsResult {
|
|||||||
quickChatCloseOnOutsideClick: boolean;
|
quickChatCloseOnOutsideClick: boolean;
|
||||||
dashboardKeyboardShortcuts: Required<DashboardKeyboardShortcutMap>;
|
dashboardKeyboardShortcuts: Required<DashboardKeyboardShortcutMap>;
|
||||||
dismissModalsOnOutsideClick: boolean;
|
dismissModalsOnOutsideClick: boolean;
|
||||||
|
quickAddSubmitOnEnter: boolean;
|
||||||
skipConfirmationDialogs: boolean;
|
skipConfirmationDialogs: boolean;
|
||||||
showQuickChatFAB: boolean;
|
showQuickChatFAB: boolean;
|
||||||
maxTotalRetriesBeforeFail: number;
|
maxTotalRetriesBeforeFail: number;
|
||||||
@@ -99,6 +100,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
|||||||
const [quickChatCloseOnOutsideClick, setQuickChatCloseOnOutsideClick] = useState(true);
|
const [quickChatCloseOnOutsideClick, setQuickChatCloseOnOutsideClick] = useState(true);
|
||||||
const [dashboardKeyboardShortcuts, setDashboardKeyboardShortcuts] = useState<Required<DashboardKeyboardShortcutMap>>(DEFAULT_DASHBOARD_KEYBOARD_SHORTCUTS);
|
const [dashboardKeyboardShortcuts, setDashboardKeyboardShortcuts] = useState<Required<DashboardKeyboardShortcutMap>>(DEFAULT_DASHBOARD_KEYBOARD_SHORTCUTS);
|
||||||
const [dismissModalsOnOutsideClick, setDismissModalsOnOutsideClick] = useState(false);
|
const [dismissModalsOnOutsideClick, setDismissModalsOnOutsideClick] = useState(false);
|
||||||
|
const [quickAddSubmitOnEnter, setQuickAddSubmitOnEnter] = useState(true);
|
||||||
const [skipConfirmationDialogs, setSkipConfirmationDialogs] = useState(false);
|
const [skipConfirmationDialogs, setSkipConfirmationDialogs] = useState(false);
|
||||||
const [showQuickChatFAB, setShowQuickChatFAB] = useState(false);
|
const [showQuickChatFAB, setShowQuickChatFAB] = useState(false);
|
||||||
const [maxTotalRetriesBeforeFail, setMaxTotalRetriesBeforeFail] = useState(25);
|
const [maxTotalRetriesBeforeFail, setMaxTotalRetriesBeforeFail] = useState(25);
|
||||||
@@ -167,6 +169,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
|||||||
setQuickChatCloseOnOutsideClick(settings.quickChatCloseOnOutsideClick !== false);
|
setQuickChatCloseOnOutsideClick(settings.quickChatCloseOnOutsideClick !== false);
|
||||||
setDashboardKeyboardShortcuts(resolveDashboardKeyboardShortcuts((settings as GlobalSettings).dashboardKeyboardShortcuts));
|
setDashboardKeyboardShortcuts(resolveDashboardKeyboardShortcuts((settings as GlobalSettings).dashboardKeyboardShortcuts));
|
||||||
setDismissModalsOnOutsideClick(settings.dismissModalsOnOutsideClick === true);
|
setDismissModalsOnOutsideClick(settings.dismissModalsOnOutsideClick === true);
|
||||||
|
setQuickAddSubmitOnEnter(settings.quickAddSubmitOnEnter !== false);
|
||||||
setSkipConfirmationDialogs(settings.skipConfirmationDialogs === true);
|
setSkipConfirmationDialogs(settings.skipConfirmationDialogs === true);
|
||||||
setShowQuickChatFAB(nextQuickChatButtonMode === "floating");
|
setShowQuickChatFAB(nextQuickChatButtonMode === "floating");
|
||||||
setMaxTotalRetriesBeforeFail(settings.maxTotalRetriesBeforeFail ?? 25);
|
setMaxTotalRetriesBeforeFail(settings.maxTotalRetriesBeforeFail ?? 25);
|
||||||
@@ -363,6 +366,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
|||||||
quickChatCloseOnOutsideClick,
|
quickChatCloseOnOutsideClick,
|
||||||
dashboardKeyboardShortcuts,
|
dashboardKeyboardShortcuts,
|
||||||
dismissModalsOnOutsideClick,
|
dismissModalsOnOutsideClick,
|
||||||
|
quickAddSubmitOnEnter,
|
||||||
skipConfirmationDialogs,
|
skipConfirmationDialogs,
|
||||||
showQuickChatFAB,
|
showQuickChatFAB,
|
||||||
maxTotalRetriesBeforeFail,
|
maxTotalRetriesBeforeFail,
|
||||||
|
|||||||
21
packages/dashboard/app/hooks/useQuickAddSubmitOnEnter.ts
Normal file
21
packages/dashboard/app/hooks/useQuickAddSubmitOnEnter.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createContext, createElement, useContext, type ReactNode } from "react";
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:QuickEntry 2026-08-16-03:15:
|
||||||
|
Quick Add renders from the deep Column and ListView hosts without a shared settings prop channel. This context mirrors ModalDismissPreferenceProvider so global keyboard preference reaches both hosts while the true default preserves unwrapped components and existing tests.
|
||||||
|
*/
|
||||||
|
const QuickAddSubmitOnEnterContext = createContext(true);
|
||||||
|
|
||||||
|
export function QuickAddSubmitOnEnterProvider({
|
||||||
|
enabled,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
enabled: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
return createElement(QuickAddSubmitOnEnterContext.Provider, { value: enabled }, children);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useQuickAddSubmitOnEnter(): boolean {
|
||||||
|
return useContext(QuickAddSubmitOnEnterContext);
|
||||||
|
}
|
||||||
@@ -6140,6 +6140,8 @@
|
|||||||
"dismissModalsByClickingOutsideHint": " When enabled, clicking or tapping a modal backdrop closes the modal. Default: disabled, to prevent accidental dismissal. ",
|
"dismissModalsByClickingOutsideHint": " When enabled, clicking or tapping a modal backdrop closes the modal. Default: disabled, to prevent accidental dismissal. ",
|
||||||
"skipConfirmationDialogs": " Skip confirmation dialogs for critical actions ",
|
"skipConfirmationDialogs": " Skip confirmation dialogs for critical actions ",
|
||||||
"skipConfirmationDialogsHint": " When enabled, destructive actions such as deleting a task or resetting progress run immediately without a prompt. Default: disabled",
|
"skipConfirmationDialogsHint": " When enabled, destructive actions such as deleting a task or resetting progress run immediately without a prompt. Default: disabled",
|
||||||
|
"quickAddSubmitOnEnter": " Press Enter to save a task in Quick Add ",
|
||||||
|
"quickAddSubmitOnEnterHint": " Default: enabled. When disabled, Enter inserts a newline and Cmd/Ctrl+Enter saves. ",
|
||||||
"releaseChannel": "Release channel",
|
"releaseChannel": "Release channel",
|
||||||
"releaseChannelHelp": "Stable follows official releases. Beta follows pre-releases cut from main and also picks up each stable release once it overtakes the beta. Switching back to Stable never downgrades. Default: stable.",
|
"releaseChannelHelp": "Stable follows official releases. Beta follows pre-releases cut from main and also picks up each stable release once it overtakes the beta. Switching back to Stable never downgrades. Default: stable.",
|
||||||
"autoUpdateAndRestart": " Auto-update and restart ",
|
"autoUpdateAndRestart": " Auto-update and restart ",
|
||||||
|
|||||||
@@ -6121,7 +6121,9 @@
|
|||||||
"agentToolOutputLimit": "",
|
"agentToolOutputLimit": "",
|
||||||
"agentToolOutputLimitHint": "",
|
"agentToolOutputLimitHint": "",
|
||||||
"noLimitOnAgentToolOutput": "",
|
"noLimitOnAgentToolOutput": "",
|
||||||
"noLimitOnAgentToolOutputHint": ""
|
"noLimitOnAgentToolOutputHint": "",
|
||||||
|
"quickAddSubmitOnEnter": "",
|
||||||
|
"quickAddSubmitOnEnterHint": ""
|
||||||
},
|
},
|
||||||
"globalModels": {
|
"globalModels": {
|
||||||
"allow": "",
|
"allow": "",
|
||||||
|
|||||||
@@ -6121,7 +6121,9 @@
|
|||||||
"agentToolOutputLimit": "",
|
"agentToolOutputLimit": "",
|
||||||
"agentToolOutputLimitHint": "",
|
"agentToolOutputLimitHint": "",
|
||||||
"noLimitOnAgentToolOutput": "",
|
"noLimitOnAgentToolOutput": "",
|
||||||
"noLimitOnAgentToolOutputHint": ""
|
"noLimitOnAgentToolOutputHint": "",
|
||||||
|
"quickAddSubmitOnEnter": "",
|
||||||
|
"quickAddSubmitOnEnterHint": ""
|
||||||
},
|
},
|
||||||
"globalModels": {
|
"globalModels": {
|
||||||
"allow": "",
|
"allow": "",
|
||||||
|
|||||||
@@ -6121,7 +6121,9 @@
|
|||||||
"agentToolOutputLimit": "",
|
"agentToolOutputLimit": "",
|
||||||
"agentToolOutputLimitHint": "",
|
"agentToolOutputLimitHint": "",
|
||||||
"noLimitOnAgentToolOutput": "",
|
"noLimitOnAgentToolOutput": "",
|
||||||
"noLimitOnAgentToolOutputHint": ""
|
"noLimitOnAgentToolOutputHint": "",
|
||||||
|
"quickAddSubmitOnEnter": "",
|
||||||
|
"quickAddSubmitOnEnterHint": ""
|
||||||
},
|
},
|
||||||
"globalModels": {
|
"globalModels": {
|
||||||
"allow": "",
|
"allow": "",
|
||||||
|
|||||||
@@ -6669,7 +6669,9 @@
|
|||||||
"autoUpdateAndRestart": " Atualizar e reiniciar automaticamente ",
|
"autoUpdateAndRestart": " Atualizar e reiniciar automaticamente ",
|
||||||
"autoUpdateAndRestartHelp": "Quando ativado, o Fusion instala automaticamente as atualizações disponíveis no canal de lançamento selecionado e reinicia para aplicá-las. Requer um processo supervisor pai (o padrão para `fn dashboard`); hosts iniciados com --no-supervise pulam a instalação. Padrão: desativado.",
|
"autoUpdateAndRestartHelp": "Quando ativado, o Fusion instala automaticamente as atualizações disponíveis no canal de lançamento selecionado e reinicia para aplicá-las. Requer um processo supervisor pai (o padrão para `fn dashboard`); hosts iniciados com --no-supervise pulam a instalação. Padrão: desativado.",
|
||||||
"channelStable": "Stable (recomendado)",
|
"channelStable": "Stable (recomendado)",
|
||||||
"channelBeta": "Beta — builds iniciais da main"
|
"channelBeta": "Beta — builds iniciais da main",
|
||||||
|
"quickAddSubmitOnEnter": "",
|
||||||
|
"quickAddSubmitOnEnterHint": ""
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"accessTokenOptional": "Token de acesso (opcional)",
|
"accessTokenOptional": "Token de acesso (opcional)",
|
||||||
|
|||||||
@@ -6121,7 +6121,9 @@
|
|||||||
"agentToolOutputLimit": "",
|
"agentToolOutputLimit": "",
|
||||||
"agentToolOutputLimitHint": "",
|
"agentToolOutputLimitHint": "",
|
||||||
"noLimitOnAgentToolOutput": "",
|
"noLimitOnAgentToolOutput": "",
|
||||||
"noLimitOnAgentToolOutputHint": ""
|
"noLimitOnAgentToolOutputHint": "",
|
||||||
|
"quickAddSubmitOnEnter": "",
|
||||||
|
"quickAddSubmitOnEnterHint": ""
|
||||||
},
|
},
|
||||||
"globalModels": {
|
"globalModels": {
|
||||||
"allow": "",
|
"allow": "",
|
||||||
|
|||||||
@@ -6121,7 +6121,9 @@
|
|||||||
"agentToolOutputLimit": "",
|
"agentToolOutputLimit": "",
|
||||||
"agentToolOutputLimitHint": "",
|
"agentToolOutputLimitHint": "",
|
||||||
"noLimitOnAgentToolOutput": "",
|
"noLimitOnAgentToolOutput": "",
|
||||||
"noLimitOnAgentToolOutputHint": ""
|
"noLimitOnAgentToolOutputHint": "",
|
||||||
|
"quickAddSubmitOnEnter": "",
|
||||||
|
"quickAddSubmitOnEnterHint": ""
|
||||||
},
|
},
|
||||||
"globalModels": {
|
"globalModels": {
|
||||||
"allow": "",
|
"allow": "",
|
||||||
|
|||||||
4
packages/i18n/src/resources.d.ts
vendored
4
packages/i18n/src/resources.d.ts
vendored
@@ -6114,7 +6114,7 @@ export default interface Resources {
|
|||||||
"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. ",
|
"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. ",
|
||||||
"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.",
|
"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.",
|
||||||
"workflowsOrChangelogModeWhenContributorsShouldUpdate": " workflows, or changelog mode when contributors should update an existing changelog file. Default: off. ",
|
"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)."
|
"workspaceModeHint": "Enabling detects git sub-repositories and requires at least one; it creates .fusion/workspace.json. Disabling removes .fusion/workspace.json and returns to single-repo mode. No default — unset (disabled)."
|
||||||
},
|
},
|
||||||
"globalGeneral": {
|
"globalGeneral": {
|
||||||
"agentToolOutputLimit": " Agent tool-output limit ",
|
"agentToolOutputLimit": " Agent tool-output limit ",
|
||||||
@@ -6150,6 +6150,8 @@ export default interface Resources {
|
|||||||
"onStartupOncePerServerLaunch": "On startup — once per server launch",
|
"onStartupOncePerServerLaunch": "On startup — once per server launch",
|
||||||
"ownerRepo": "owner/repo",
|
"ownerRepo": "owner/repo",
|
||||||
"projectsInheritThisValueWhenTheyDoNot": "Projects inherit this value when they do not set a project default tracking repo. No default — unset.",
|
"projectsInheritThisValueWhenTheyDoNot": "Projects inherit this value when they do not set a project default tracking repo. No default — unset.",
|
||||||
|
"quickAddSubmitOnEnter": " Press Enter to save a task in Quick Add ",
|
||||||
|
"quickAddSubmitOnEnterHint": " Default: enabled. When disabled, Enter inserts a newline and Cmd/Ctrl+Enter saves. ",
|
||||||
"releaseChannel": "Release channel",
|
"releaseChannel": "Release channel",
|
||||||
"releaseChannelHelp": "Stable follows official releases. Beta follows pre-releases cut from main and also picks up each stable release once it overtakes the beta. Switching back to Stable never downgrades. Default: stable.",
|
"releaseChannelHelp": "Stable follows official releases. Beta follows pre-releases cut from main and also picks up each stable release once it overtakes the beta. Switching back to Stable never downgrades. Default: stable.",
|
||||||
"reportRoadmapDedupeEnabled": "Global public-roadmap deduplication",
|
"reportRoadmapDedupeEnabled": "Global public-roadmap deduplication",
|
||||||
|
|||||||
Reference in New Issue
Block a user