From 525ecc9f25e457e280b66debbeb8ed313ee68287 Mon Sep 17 00:00:00 2001 From: Fusion Agent Date: Thu, 20 Aug 2026 20:38:13 +0000 Subject: [PATCH] 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 --- docs/settings-reference.md | 4 +- .../core/src/__tests__/ai-summarize.test.ts | 18 +++++++++ .../app/__tests__/settings-sections.test.tsx | 23 +++++++++++- .../sections/ProjectModelsSection.tsx | 37 +++++++++---------- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/docs/settings-reference.md b/docs/settings-reference.md index 9a75581a37..57ac4c730c 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -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 diff --git a/packages/core/src/__tests__/ai-summarize.test.ts b/packages/core/src/__tests__/ai-summarize.test.ts index 761c21176b..65a820d164 100644 --- a/packages/core/src/__tests__/ai-summarize.test.ts +++ b/packages/core/src/__tests__/ai-summarize.test.ts @@ -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(); diff --git a/packages/dashboard/app/__tests__/settings-sections.test.tsx b/packages/dashboard/app/__tests__/settings-sections.test.tsx index 9ddb4e15f3..2b3d413a32 100644 --- a/packages/dashboard/app/__tests__/settings-sections.test.tsx +++ b/packages/dashboard/app/__tests__/settings-sections.test.tsx @@ -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( + , + ); + + 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( { 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(); diff --git a/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx b/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx index eee75ed7b2..3c87bd4d58 100644 --- a/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx +++ b/packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx @@ -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. + */} + setForm((f) => ({ ...f, autoSummarizeTitles: v === true }))} + /> + {/* --- AI Title and Git Commit Message Summarization --- */}
{/* @@ -905,26 +922,6 @@ export function ProjectModelsSection({ form, setForm, models, projectId, onOpenW )} - {/* - 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. - */} - setForm((f) => ({ ...f, autoSummarizeTitles: v === true }))} - /> -