FN-083: position auto-summarize task titles toggle
Align the auto-summarize task titles setting with task-language controls and preserve language-aware title generation behavior. - Place the project toggle directly after the AI-authored task language selector. - Verify toggle placement, state updates, and resolved title-output language instructions. - Update settings documentation for language snapshotting and bounded fallback behavior. Files changed: docs/settings-reference.md | 4 +-- packages/core/src/__tests__/ai-summarize.test.ts | 18 +++++++++++ .../app/__tests__/settings-sections.test.tsx | 23 ++++++++++++-- .../settings/sections/ProjectModelsSection.tsx | 37 ++++++++++------------ 4 files changed, 58 insertions(+), 24 deletions(-) Fusion-Task-Id: FN-083 Fusion-Task-Lineage: 4a018f7e-3e8d-4478-8d9d-266fd04ada4c Co-authored-by: Fusion <noreply@runfusion.ai>
This commit is contained in:
@@ -779,7 +779,7 @@ Database backups work with both external PostgreSQL and Fusion's default embedde
|
||||
| `memoryBackupRetention` | `number` | `14` | Number of memory backups to retain. |
|
||||
| `memoryBackupDir` | `string` | `".fusion/backups/memory"` | Relative memory backup directory path. |
|
||||
| `memoryBackupScope` | `"project" \| "agents" \| "all"` | `"all"` | Backup scope: project memory, agent memory, or both. |
|
||||
| `autoSummarizeTitles` | `boolean` | `false` | Project-scoped automatic policy for every non-empty untitled task description across dashboard/API, direct store, agent, scheduled, signal, and CLI-backed creates. Enabled creates attempt an AI title regardless of description length; disabled creates use the deterministic planner fallback. Explicit titles are preserved, `summarize:true` and Task Detail/manual requests remain explicit force paths, and the setting is snapshotted per create rather than applied retroactively. |
|
||||
| `autoSummarizeTitles` | `boolean` | `false` | Project-scoped automatic policy for every non-empty untitled task description across dashboard/API, direct store, agent, scheduled, signal, and CLI-backed creates. In Settings → Project Models it sits immediately after AI-authored task language. Enabled creates attempt an AI title regardless of description length and snapshot the selected output language before deferred generation; disabled or unavailable generation leaves the title blank for bounded display-only fallback. Explicit titles are preserved, `summarize:true` and Task Detail/manual requests remain explicit force paths. |
|
||||
| `taskDefinitionInInputLanguage` | `boolean` | `false` | When enabled, generated task-definition (`PROMPT.md`) prose uses a confidently detected supported input language: Spanish (`es`), French (`fr`), Korean (`ko`), or Chinese (`zh-CN`). Only planner-authored prose is localized; headings, markers, the verbatim Original Description, code, paths, tool names, and commit conventions stay canonical English for deterministic parsing. Chinese always authors as `zh-CN`; Traditional Chinese is not variant-detected. English, short/uncertain, and unsupported input such as Japanese fall back to English. Configure in **Settings → Project Models**. |
|
||||
| `useAiMergeCommitSummary` | `boolean` | `true` | Use AI-generated merge commit summaries (subject + bullet body + diff-stat) instead of raw step-commit subject lists. |
|
||||
| `titleSummarizerProvider` | `string` | `undefined` | Provider for title summarization. |
|
||||
@@ -2000,7 +2000,7 @@ When adding a picker provider, add a census entry with `autoDerive`, `guardNotAp
|
||||
|
||||
### AI-authored task output language
|
||||
|
||||
`taskOutputLanguage` is a project setting with `"english"` (default), `"input"`, and `"interface"` modes. English explicitly targets English; input follows the original task text (with detection only as a hint); interface follows the global Fusion UI `language`, falling back to English when unset. Existing `taskDefinitionInInputLanguage: true` projects resolve as input until an explicit new mode is saved; explicit modes clear that legacy flag atomically. The setting affects future planning, triage, title, execution, workflow-summary, and recommendation generation sessions only, never existing task text. Human prose is targeted while headings, JSON keys, markers, code, paths, tool names, and schemas remain canonical.
|
||||
`taskOutputLanguage` is a project setting with `"english"` (default), `"input"`, and `"interface"` modes. English explicitly targets English; input follows the original task text (with detection only as a hint); interface follows the global Fusion UI `language`, falling back to English when unset. Existing `taskDefinitionInInputLanguage: true` projects resolve as input until an explicit new mode is saved; explicit modes clear that legacy flag atomically. The setting affects future planning, triage, title, execution, workflow-summary, and recommendation generation sessions only, never existing task text. The immediately following **Auto-summarize task titles** project toggle snapshots this selected language at task creation for every non-empty untitled description when enabled; no description-length threshold controls the policy. When disabled or generation is unavailable, titleless Board and List cards use bounded presentation-only fallback without persisting a description-derived title. Human prose is targeted while headings, JSON keys, markers, code, paths, tool names, and schemas remain canonical.
|
||||
|
||||
### JIRA branch naming
|
||||
|
||||
|
||||
@@ -173,6 +173,24 @@ describe("ai-summarize", () => {
|
||||
expect(getFnAgentMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["English", { mode: "english", locale: "en", instruction: "English" }, "Write the title in English."],
|
||||
["interface", { mode: "interface", locale: "fr", instruction: "French" }, "Write the title in Français (fr)."],
|
||||
["input", { mode: "input", locale: "es", instruction: "input language" }, "SAME language as the task description"],
|
||||
] as const)("honors the resolved %s output target", async (_mode, target, expectedInstruction) => {
|
||||
const prompt = vi.fn().mockResolvedValue(undefined);
|
||||
getFnAgentMock.mockResolvedValue(() => Promise.resolve({
|
||||
session: {
|
||||
prompt,
|
||||
dispose: vi.fn(),
|
||||
state: { messages: [{ role: "assistant", content: "Generated task title" }] },
|
||||
},
|
||||
}));
|
||||
|
||||
await expect(summarizeTitle("Necesitamos mejorar la creación de tareas.", "/tmp", undefined, undefined, target)).resolves.toBe("Generated task title");
|
||||
expect(prompt.mock.calls[0][0]).toContain(expectedInstruction);
|
||||
});
|
||||
|
||||
it.each(["", " ", "\n\t"]) ("rejects invalid %j before creating an agent", async (description) => {
|
||||
await expect(summarizeTitle(description, "/tmp")).rejects.toThrow(ValidationError);
|
||||
expect(getFnAgentMock).not.toHaveBeenCalled();
|
||||
|
||||
@@ -626,6 +626,26 @@ describe("ProjectModelsSection", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("places the sole title toggle directly after task language and updates project form state", () => {
|
||||
const setForm = vi.fn();
|
||||
render(
|
||||
<ProjectModelsSection
|
||||
form={{ autoSummarizeTitles: false } as SettingsFormState}
|
||||
setForm={setForm}
|
||||
models={models}
|
||||
addToast={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const language = screen.getByRole("combobox", { name: "AI-authored task language" });
|
||||
const toggle = screen.getByRole("checkbox", { name: "Auto-summarize task titles" });
|
||||
expect(screen.getAllByRole("checkbox", { name: "Auto-summarize task titles" })).toHaveLength(1);
|
||||
expect(language.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
fireEvent.click(toggle);
|
||||
const update = setForm.mock.calls[0]?.[0] as (form: SettingsFormState) => SettingsFormState;
|
||||
expect(update({ autoSummarizeTitles: false } as SettingsFormState)).toMatchObject({ autoSummarizeTitles: true });
|
||||
});
|
||||
|
||||
it("colocates summarization model controls with AI summarization settings", () => {
|
||||
render(
|
||||
<ProjectModelsSection
|
||||
@@ -646,14 +666,13 @@ describe("ProjectModelsSection", () => {
|
||||
|
||||
const summarizationSection = screen.getByTestId("project-models-ai-summarization");
|
||||
const heading = screen.getByRole("heading", { name: "AI Title and Git Commit Message Summarization" });
|
||||
const autoSummarizeTitles = screen.getByRole("checkbox", { name: "Auto-summarize task titles" });
|
||||
const summarizationDropdown = screen.getByTestId("mock-model-dropdown-summarizationModel");
|
||||
const fallbackDropdown = screen.getByTestId("mock-model-dropdown-titleSummarizerFallbackModel");
|
||||
const defaultDropdown = screen.getByTestId("mock-model-dropdown-defaultModel");
|
||||
const mergerDropdown = screen.getByTestId("mock-model-dropdown-mergerModel");
|
||||
|
||||
expect(summarizationSection).toContainElement(heading);
|
||||
expect(summarizationSection).toContainElement(autoSummarizeTitles);
|
||||
expect(summarizationSection).not.toContainElement(screen.getByRole("checkbox", { name: "Auto-summarize task titles" }));
|
||||
expect(summarizationSection).toContainElement(summarizationDropdown);
|
||||
expect(summarizationSection).toContainElement(fallbackDropdown);
|
||||
expect(defaultDropdown.compareDocumentPosition(heading) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
|
||||
@@ -867,6 +867,23 @@ export function ProjectModelsSection({ form, setForm, models, projectId, onOpenW
|
||||
}))}
|
||||
/>
|
||||
|
||||
{/*
|
||||
FNXC:TitleSummarization 2026-08-20-20:19:
|
||||
The automatic title policy belongs immediately beside its task-language selector so operators
|
||||
can see that every enabled create snapshots this language for title generation. Keep this
|
||||
project form row outside the model guard: availability of a model must not hide the policy.
|
||||
*/}
|
||||
<SettingsToggleRow
|
||||
descriptor={{
|
||||
key: "autoSummarizeTitles",
|
||||
label: t("settings.projectModels.autoSummarizeLongDescriptionsAsTitles", " Auto-summarize task titles "),
|
||||
help: t("settings.projectModels.whenEnabledTasksCreatedWithoutATitleBut", " When enabled, every non-empty task description created without a title receives an AI-generated title (max 60 characters). Explicit titles are preserved, and manual or explicit force requests remain available when this is disabled. The same model is also used for merge commit summaries and GitHub tracking issue titles. Default: disabled. "),
|
||||
scope: "project",
|
||||
}}
|
||||
value={form.autoSummarizeTitles || false}
|
||||
onChange={(v) => setForm((f) => ({ ...f, autoSummarizeTitles: v === true }))}
|
||||
/>
|
||||
|
||||
{/* --- AI Title and Git Commit Message Summarization --- */}
|
||||
<section data-testid="project-models-ai-summarization">
|
||||
{/*
|
||||
@@ -905,26 +922,6 @@ export function ProjectModelsSection({ form, setForm, models, projectId, onOpenW
|
||||
</div>
|
||||
</div>
|
||||
</>)}
|
||||
{/*
|
||||
FNXC:SettingsSearch 2026-07-15-17:35:
|
||||
This is the row operators searched "summarize" for and could not find (FN-7907, patched again 2026-07-14). It is now indexed by descriptor key from ProjectModelsSection.search.ts, so the word in its own label is what search matches — no hand-maintained keyword list to fall behind again.
|
||||
*/}
|
||||
<SettingsToggleRow
|
||||
descriptor={{
|
||||
key: "autoSummarizeTitles",
|
||||
/*
|
||||
FNXC:TitleSummarization 2026-08-19-13:43:
|
||||
The project toggle describes the all-length automatic policy. Keep explicit titles and
|
||||
manual/force requests as separate precedence paths rather than implying this toggle is a permission gate.
|
||||
*/
|
||||
label: t("settings.projectModels.autoSummarizeLongDescriptionsAsTitles", " Auto-summarize task titles "),
|
||||
help: t("settings.projectModels.whenEnabledTasksCreatedWithoutATitleBut", " When enabled, every non-empty task description created without a title receives an AI-generated title (max 60 characters). Explicit titles are preserved, and manual or explicit force requests remain available when this is disabled. The same model is also used for merge commit summaries and GitHub tracking issue titles. Default: disabled. "),
|
||||
scope: "project",
|
||||
}}
|
||||
value={form.autoSummarizeTitles || false}
|
||||
onChange={(v) => setForm((f) => ({ ...f, autoSummarizeTitles: v === true }))}
|
||||
/>
|
||||
|
||||
<SettingsToggleRow
|
||||
descriptor={{
|
||||
key: "useAiMergeCommitSummary",
|
||||
|
||||
Reference in New Issue
Block a user