From ebc89310bcb9c2119c8cd6cbf77ef891969ab8ab Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 28 Jul 2026 22:19:42 -0700 Subject: [PATCH] U12 part 4: derive the move menu's "Back to" label from workflow traits (plus two legacy reads I did NOT delete, with measurements) (#2521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## U12 part 4 — the move menu's "Back to" label followed hardcoded column ids `getTaskMoveTransitions` is shared by Board cards, List rows and Task Detail. It labelled a backwards move with: ```ts column === "in-progress" && task.column === "in-review" ? t("taskDetail.move.backToInProgress", "Back to In Progress") ``` Two hardcoded lifecycle ids **and** a hardcoded English column name. On a workflow that renames those lanes the condition never matched, so the affordance silently vanished — and had it matched, it would have announced "In Progress", a column absent from that board. Same legacy-vocabulary class U10 removed from Board and U12 removed from ListView, surviving in the context menu all three surfaces render. Now keyed on the traits it was approximating: the **current** column carries `mergeBlocker`, the **target** carries `countsTowardWip`, and the label interpolates the column's own name through a new `taskDetail.move.backTo` key (added to all six locales). ### Scope I deliberately held back **The set of moves labelled "Back to" is unchanged.** For `builtin:coding` the traits resolve to exactly `in-review` and `in-progress`. I first generalised this to "any target earlier in the workflow's declared order" — arguably nicer, and I had it working. Then I measured it: it relabels moves this change never set out to touch. **18 assertion sites across three suites** flip from "Move to" to "Back to" (e.g. a card in In progress gets "Back to Todo", "Back to Planning"). Same-set-different-derivation is the honest scope here; widening which moves read as backwards is a separate, visible product decision, not a side effect of a vocabulary fix. ### Two things I chose not to delete, and why Both are still-live `VALID_TRANSITIONS` reads in this file. Neither is removable today, and the reason is the same missing wire field — documented at both sites rather than left as a puzzle. **1. The default-column-set shortcut.** `TaskContextMenuColumnMetadata` carries id/label/flags but **no adjacency**, so the workflow branch can only guess targets from a column's neighbours in declared order. Measured against the real graph that is a strict loss: | current | `VALID_TRANSITIONS` | neighbour-derived | |---|---|---| | `in-progress` | in-review, todo, triage, done (4) | todo, in-review (2) | | `todo` | in-progress, triage, archived (3) | triage, in-progress (2) | | `done` | todo, triage, archived (3) | in-review, archived (2) | Deleting that read is not a cleanup — it drops real operator moves (archive from Todo, straight-to-Done from In progress). Note the guard keys on the column **id set**, so a workflow that merely renames the six built-ins still takes this path and still gets correct targets; only reordering or replacing them falls through to the weaker logic. **2. The no-metadata fallback.** I removed it first, on principle, and measured the result: `workflowMoveColumns` is optional at both call sites (`workflowMoveMetadata?.moveColumns`, `taskMoveColumns`) and genuinely undefined until board-workflows resolves, so dropping it left Task Detail with **no move options during load**. That is a live surface degraded to satisfy a purity rule, so it is not shipped. Unlike Board and ListView — where the legacy path was provably unreachable — this one is reachable and useful. Both retire the same way: put each column's allowed targets on the board-workflows payload so the load window has real data instead of a guess. That is a server + wire + client change and belongs in its own slice. ### Revert-proof The renamed-workflow fixture declares `signoff` (mergeBlocker) and `building` (countsTowardWip). Restore the id literals and the new case fails — `Move to Building` instead of `Back to Building` — which no relabelling of the old hardcoded string could satisfy, since that string names a column absent from the board. The same case asserts the forward move keeps "Move to Shipped", so the rule stays a distinction rather than a blanket relabel. ### Verification `pnpm test:gate` (309 + 10 + 71), `pnpm lint`, `pnpm verify:fast`, dashboard typecheck green. **No new test failures**, established properly: the three suites this touches report 30 failures both with and without the change, and I diffed the failing test *names* against a stashed clean tree rather than comparing counts — the sets are identical. (An earlier count-only comparison had me chasing two failures that turned out to be my own new assertions.) Also regenerates `packages/i18n/src/resources.d.ts` via `pnpm i18n:types`. That picks up **~45 lines of pre-existing drift** from earlier merges that did not regenerate it; the file is generated, and leaving it stale would omit the new key from the types. Flagged so the extra lines are not mistaken for scope creep. Note `packages/dashboard/app/locales/` is gitignored (copied from `packages/i18n/locales/`), so only the canonical locales are committed. --------- Co-authored-by: Claude Opus 5 (1M context) --- .changeset/u12-context-menu-back-to-label.md | 7 ++ .../app/components/TaskContextMenu.tsx | 93 ++++++++++++++++++- .../components/__tests__/ListView.test.tsx | 12 ++- .../workflow-resolved-columns.test.tsx | 62 +++++++++++++ packages/i18n/locales/en/app.json | 1 + packages/i18n/locales/es/app.json | 1 + packages/i18n/locales/fr/app.json | 1 + packages/i18n/locales/ko/app.json | 1 + packages/i18n/locales/zh-CN/app.json | 1 + packages/i18n/locales/zh-TW/app.json | 1 + packages/i18n/src/resources.d.ts | 56 ++++++++++- 11 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 .changeset/u12-context-menu-back-to-label.md diff --git a/.changeset/u12-context-menu-back-to-label.md b/.changeset/u12-context-menu-back-to-label.md new file mode 100644 index 0000000000..d22da4ea97 --- /dev/null +++ b/.changeset/u12-context-menu-back-to-label.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: The "Back to" move-menu label now uses your workflow's own review and work column names. +category: fix +dev: `getTaskMoveTransitions` derived the "Back to In Progress" label from the hardcoded ids `in-review`/`in-progress` plus a hardcoded English string, so a workflow renaming those lanes either lost the label or named a column not on the board. Now keyed on the `mergeBlocker` (current) and `countsTowardWip` (target) traits with the column's own label via a new `taskDetail.move.backTo` key. The labelled set is unchanged for built-in workflows. diff --git a/packages/dashboard/app/components/TaskContextMenu.tsx b/packages/dashboard/app/components/TaskContextMenu.tsx index 50b979b920..43ff8ba350 100644 --- a/packages/dashboard/app/components/TaskContextMenu.tsx +++ b/packages/dashboard/app/components/TaskContextMenu.tsx @@ -158,6 +158,32 @@ Manual pull-request review has two separate operator intents: Start PR Review op */ function getWorkflowMoveTargets(task: Task | TaskDetail, columns: readonly TaskContextMenuColumnMetadata[]): ColumnId[] { const visibleColumns = columns.filter((column) => column.flags?.hiddenFromBoard !== true); + /* + FNXC:TaskContextMenu 2026-07-29-00:00 (U12 — R8, KNOWN REMAINING GAP): + This `VALID_TRANSITIONS` shortcut is the LAST legacy-vocabulary read in this file and + it is deliberately KEPT, because removing it today would silently SHRINK the move + menu for every default-workflow project rather than fix anything. + + The reason is a missing wire field, not a missing idea. `TaskContextMenuColumnMetadata` + carries id/label/flags but NO adjacency, so the workflow branch below can only guess + targets from a column's neighbours in the declared order — [previous, next]. Measured + against the real graph that is a strict loss: + + in-progress VALID_TRANSITIONS: in-review, todo, triage, done (4) + neighbour-derived: todo, in-review (2) + todo VALID_TRANSITIONS: in-progress, triage, archived (3) + neighbour-derived: triage, in-progress (2) + done VALID_TRANSITIONS: todo, triage, archived (3) + neighbour-derived: in-review, archived (2) + + So "delete the legacy read" here is not a cleanup — it is a UI regression that drops + real operator moves (archive from Todo, straight-to-Done from In progress). The + correct fix is to put each column's allowed targets on the board-workflows payload + and read THOSE, which changes the server, the wire shape and this file, and is its own + slice. Note the guard is keyed on the COLUMN ID SET, so a workflow that merely renames + the six built-in columns still takes this path and still gets correct targets; only a + workflow that reorders or replaces them falls through to the weaker neighbour logic. + */ if (isDefaultWorkflowColumnSet(visibleColumns) && isColumn(task.column)) { return task.column === "in-review" ? ["todo", "in-progress"] : [...VALID_TRANSITIONS[task.column]]; } @@ -195,19 +221,80 @@ export function getTaskMoveTransitions( columnLabel: (column: ColumnId) => string, workflowMoveColumns?: readonly TaskContextMenuColumnMetadata[], ): TaskMoveActionDescriptor[] { + /* + FNXC:TaskContextMenu 2026-07-29-00:00 (U12 — R8, decision recorded): + The no-metadata `VALID_TRANSITIONS` fallback is KEPT. I removed it first, on the R8 + principle, and measured the result: `workflowMoveColumns` is optional at both call + sites (`workflowMoveMetadata?.moveColumns`, `taskMoveColumns`) and is genuinely + undefined until board-workflows resolves, so dropping it left Task Detail with NO + move options during load — a live surface degraded to satisfy a purity rule. That is + a regression, not a cleanup, so it is not shipped. + + Unlike Board and ListView, where the legacy path was provably unreachable, this one + is reachable and useful. It is retired the same way the shortcut above is: by putting + each column's allowed targets on the board-workflows payload so the load window has + real data instead of a guess. + */ const moveTransitions: ColumnId[] = workflowMoveColumns ? getWorkflowMoveTargets(task, workflowMoveColumns) : isColumn(task.column) ? (task.column === "in-review" ? ["todo", "in-progress"] : [...VALID_TRANSITIONS[task.column]]) : []; - const workflowLabelById = new Map((workflowMoveColumns ?? []).map((column) => [column.id, column.label])); + const visibleOrdered = (workflowMoveColumns ?? []).filter((column) => column.flags?.hiddenFromBoard !== true); + const workflowLabelById = new Map(visibleOrdered.map((column) => [column.id, column.label])); + /* + FNXC:TaskContextMenu 2026-07-29-00:00 (U12 — R8): + "Back to X" is derived from COLUMN TRAITS, not from the literals `in-review` and + `in-progress`. The old condition (`column === "in-progress" && task.column === + "in-review"`) hardcoded two lifecycle ids AND a hardcoded English label ("Back to In + Progress"), so on a workflow that renames those columns it either failed to fire or + announced a column name that is not on the board. + + The rule it was expressing is "leaving the review lane backwards into the work lane", + which the traits already say: the CURRENT column carries `mergeBlocker`, the TARGET + carries `countsTowardWip`. For builtin:coding those are exactly in-review and + in-progress, so the labelled set is unchanged — deliberately. I first generalised + this to "any target earlier in the workflow order", which is arguably nicer but + relabels moves this change never set out to touch (18 assertion sites across three + suites would have flipped from "Move to" to "Back to"). Same-set-different-derivation + is the honest scope here; widening which moves read as backwards is a separate, + visible product decision. + + Load window (no metadata): fall back to the legacy id pair, matching the fallback + already kept for the targets themselves a few lines below. + */ + const flagsById = new Map(visibleOrdered.map((column) => [column.id, column.flags])); + const orderById = new Map(visibleOrdered.map((column, index) => [column.id, index])); + const currentFlags = flagsById.get(task.column); + const currentOrder = orderById.get(task.column); + const isBackwardsLabel = (target: ColumnId): boolean => { + if (visibleOrdered.length === 0) { + return target === "in-progress" && task.column === "in-review"; + } + /* + FNXC:TaskContextMenu 2026-07-29-00:00 (PR #2521 review — greptile): + DIRECTION as well as traits. The traits alone say "review lane -> work lane", but a + workflow may declare a `countsTowardWip` column AFTER its `mergeBlocker` one (a + rework or hotfix lane placed downstream of review). Labelling that "Back to" would + call a FORWARD move backwards — the same class of wrongness as the hardcoded ids + this predicate replaced, just arrived at differently. + + Requiring the target to sit EARLIER in the workflow's declared order keeps the + builtin:coding set unchanged (in-progress precedes in-review) while making the + label mean what it says on any column layout. + */ + if (currentOrder === undefined) return false; + const targetOrder = orderById.get(target); + if (targetOrder === undefined || targetOrder >= currentOrder) return false; + return currentFlags?.mergeBlocker === true && flagsById.get(target)?.countsTowardWip === true; + }; return moveTransitions.map((column) => { const label = workflowLabelById.get(column) ?? columnLabel(column); return { column, - label: column === "in-progress" && task.column === "in-review" - ? t("taskDetail.move.backToInProgress", "Back to In Progress") + label: isBackwardsLabel(column) + ? t("taskDetail.move.backTo", "Back to {{column}}", { column: label }) : t("taskDetail.move.moveTo", "Move to {{column}}", { column: label }), primaryLabel: t("taskDetail.move.moveTo", "Move to {{column}}", { column: label }), }; diff --git a/packages/dashboard/app/components/__tests__/ListView.test.tsx b/packages/dashboard/app/components/__tests__/ListView.test.tsx index 76cae80dd8..676769dc24 100644 --- a/packages/dashboard/app/components/__tests__/ListView.test.tsx +++ b/packages/dashboard/app/components/__tests__/ListView.test.tsx @@ -922,7 +922,17 @@ describe("ListView", () => { fireEvent.contextMenu(document.querySelector('.list-row[data-id="FN-003"]') as HTMLElement, { clientX: 40, clientY: 50 }); expect(screen.getByRole("menuitem", { name: "Merge & Close" })).toBeInTheDocument(); expect(screen.getByRole("menuitem", { name: "Refine" })).toBeInTheDocument(); - expect(screen.getByRole("menuitem", { name: "Back to In Progress" })).toBeInTheDocument(); + /* + FNXC:TaskContextMenu 2026-07-29-00:00 (U12 — R8): + "In progress", not "In Progress". The label is now interpolated from the WORKFLOW's + own column name (`BUILTIN_CODING_WORKFLOW_IR` declares "In progress") instead of the + hardcoded English string `taskDetail.move.backToInProgress`. This assertion is the + visible proof that the label follows the workflow: rename that column and the menu + renames with it. Task Detail cases that render before board-workflows resolves still + read "Back to In Progress" — they go through the no-metadata fallback, which uses + the legacy column label map. + */ + expect(screen.getByRole("menuitem", { name: "Back to In progress" })).toBeInTheDocument(); fireEvent.contextMenu(document.querySelector('.list-row[data-id="FN-006"]') as HTMLElement, { clientX: 40, clientY: 50 }); expect(screen.getByRole("menuitem", { name: "Merge & Close" })).toBeInTheDocument(); diff --git a/packages/dashboard/app/components/__tests__/workflow-resolved-columns.test.tsx b/packages/dashboard/app/components/__tests__/workflow-resolved-columns.test.tsx index 1297b0d8d6..a16561adbb 100644 --- a/packages/dashboard/app/components/__tests__/workflow-resolved-columns.test.tsx +++ b/packages/dashboard/app/components/__tests__/workflow-resolved-columns.test.tsx @@ -442,6 +442,68 @@ describe("U10 — surfaces render workflow-resolved columns", () => { ]); }); + /* + FNXC:TaskContextMenu 2026-07-29-00:00 (U12 — R8): + The "Back to X" label must follow the workflow's TRAITS, not the hardcoded ids + `in-review`/`in-progress`. This workflow renames both lanes — `signoff` carries + `mergeBlocker`, `building` carries `countsTowardWip` — so leaving review backwards + into the work lane must read "Back to Building" and name the column the operator + actually sees. + + REVERT CHECK: restore `column === "in-progress" && task.column === "in-review"` and + this fails twice over — the ids never match, so the label falls through to "Move to + Building"; and the old hardcoded string would have said "Back to In Progress", a + column name absent from this board entirely. + */ + it("labels a backwards move using the workflow's own review/work traits and label", () => { + const transitions = getTaskMoveTransitions( + mkTask({ id: "FN-15", column: "signoff" as Task["column"] }), + t, + columnLabel, + renamedMoveColumns, + ); + const building = transitions.find((transition) => transition.column === "building"); + expect(building).toBeDefined(); + expect(building!.label).toBe("Back to Building"); + // The forward move keeps "Move to", so the rule stays a distinction and not a + // blanket relabel. + const shipped = transitions.find((transition) => transition.column === "shipped"); + expect(shipped?.label).toBe("Move to Shipped"); + }); + + /* + FNXC:TaskContextMenu 2026-07-29-00:00 (PR #2521 review — greptile): + A workflow may place a `countsTowardWip` lane AFTER its `mergeBlocker` one (rework + or hotfix downstream of review). The traits alone would label that FORWARD move + "Back to"; direction has to be part of the predicate. + + REVERT CHECK: drop the order comparison and this fails — `hotfix` is labelled + "Back to Hotfix" despite sitting after `signoff` in the declared order. + */ + it("does not call a forward move backwards when a WIP lane follows review", () => { + // Placed immediately AFTER signoff so it is a neighbour (and therefore offered), + // while still sitting later in the declared order. + const signoffIndex = renamedMoveColumns.findIndex((column) => column.id === "signoff"); + const downstreamWip: TaskContextMenuColumnMetadata[] = [ + ...renamedMoveColumns.slice(0, signoffIndex + 1), + { id: "hotfix" as ColumnId, label: "Hotfix", flags: { countsTowardWip: true } }, + ...renamedMoveColumns.slice(signoffIndex + 1), + ]; + const transitions = getTaskMoveTransitions( + mkTask({ id: "FN-18", column: "signoff" as Task["column"] }), + t, + columnLabel, + downstreamWip, + ); + const hotfix = transitions.find((transition) => transition.column === "hotfix"); + expect(hotfix).toBeDefined(); + expect(hotfix!.label).toBe("Move to Hotfix"); + // The genuinely-backwards move keeps its label, so the fix narrows rather than + // disables the rule. + const building = transitions.find((transition) => transition.column === "building"); + expect(building?.label).toBe("Back to Building"); + }); + it("never offers a column the workflow does not declare", () => { const transitions = getTaskMoveTransitions( mkTask({ id: "FN-13", column: "in-review" }), diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json index 18526ad639..c7a2acb8ff 100644 --- a/packages/i18n/locales/en/app.json +++ b/packages/i18n/locales/en/app.json @@ -7828,6 +7828,7 @@ }, "move": { "backToInProgress": "Back to In Progress", + "backTo": "Back to {{column}}", "cancelMove": "Cancel Move", "keepProgress": "Keep Progress", "movedTo": "Moved to {{column}}", diff --git a/packages/i18n/locales/es/app.json b/packages/i18n/locales/es/app.json index 147331cc11..da04ee214f 100644 --- a/packages/i18n/locales/es/app.json +++ b/packages/i18n/locales/es/app.json @@ -7791,6 +7791,7 @@ }, "move": { "backToInProgress": "Volver a En progreso", + "backTo": "Volver a {{column}}", "cancelMove": "Cancelar movimiento", "keepProgress": "Conservar progreso", "movedTo": "Movido a {{column}}", diff --git a/packages/i18n/locales/fr/app.json b/packages/i18n/locales/fr/app.json index 308c8bf994..fa48f44f75 100644 --- a/packages/i18n/locales/fr/app.json +++ b/packages/i18n/locales/fr/app.json @@ -7791,6 +7791,7 @@ }, "move": { "backToInProgress": "Retour à En cours", + "backTo": "Retour à {{column}}", "cancelMove": "Annuler le déplacement", "keepProgress": "Conserver la progression", "movedTo": "Déplacé vers {{column}}", diff --git a/packages/i18n/locales/ko/app.json b/packages/i18n/locales/ko/app.json index 9634f705bc..5236d1a57c 100644 --- a/packages/i18n/locales/ko/app.json +++ b/packages/i18n/locales/ko/app.json @@ -7791,6 +7791,7 @@ }, "move": { "backToInProgress": "진행 중으로 돌아가기", + "backTo": "{{column}}(으)로 돌아가기", "cancelMove": "이동 취소", "keepProgress": "진행 상태 유지", "movedTo": "{{column}}으로 이동됨", diff --git a/packages/i18n/locales/zh-CN/app.json b/packages/i18n/locales/zh-CN/app.json index 1347a8c3c6..187bf2fbb4 100644 --- a/packages/i18n/locales/zh-CN/app.json +++ b/packages/i18n/locales/zh-CN/app.json @@ -7791,6 +7791,7 @@ }, "move": { "backToInProgress": "返回进行中", + "backTo": "返回{{column}}", "cancelMove": "取消移动", "keepProgress": "保留进度", "movedTo": "已移至 {{column}}", diff --git a/packages/i18n/locales/zh-TW/app.json b/packages/i18n/locales/zh-TW/app.json index c246403a1d..b53a2fa918 100644 --- a/packages/i18n/locales/zh-TW/app.json +++ b/packages/i18n/locales/zh-TW/app.json @@ -7791,6 +7791,7 @@ }, "move": { "backToInProgress": "返回進行中", + "backTo": "返回{{column}}", "cancelMove": "取消移動", "keepProgress": "保留進度", "movedTo": "已移至 {{column}}", diff --git a/packages/i18n/src/resources.d.ts b/packages/i18n/src/resources.d.ts index 336ebeefc8..804f7734f4 100644 --- a/packages/i18n/src/resources.d.ts +++ b/packages/i18n/src/resources.d.ts @@ -1197,6 +1197,7 @@ export default interface Resources { "mergeBlocked": "This task is blocked from completing until its merge step finishes.", "promoteRejected": "This card could not be promoted.", "unknownColumn": "That column doesn't exist in this task's workflow.", + "unplannedForExecution": "This task isn't ready for execution yet — planning or plan review is still outstanding.", "workflowMismatch": "Drag can't move a card between workflows. Use the workflow switcher instead." }, "todo": "To Do", @@ -1507,6 +1508,10 @@ export default interface Resources { "preserveProgressMessage": "This task has completed steps. Keep progress before moving?", "preserveProgressMoveTodoMessage": "Some tasks have completed steps. Keep progress before moving to Todo?", "preserveProgressTitle": "Preserve Progress?", + "promoteUnplannedCancel": "Keep Waiting", + "promoteUnplannedConfirm": "Start Anyway", + "promoteUnplannedMessage": "{{taskId}} is still waiting on planning or plan review. Promoting now starts execution with the current plan and cancels the pending replan.", + "promoteUnplannedTitle": "Start execution anyway?", "replanAll": "Replan All", "replanAllHint_one": "Move {{count}} task{{plural}} to Planning", "replanAllHint_other": "Move {{count}} task{{plural}} to Planning", @@ -5745,6 +5750,10 @@ export default interface Resources { "title": "Appearance" }, "auth": { + "anthropicPreferenceApiKey": "API key", + "anthropicPreferenceHint": "You have both an Anthropic API key and a Claude subscription connected. Choose which one Fusion sends when a lane calls Anthropic directly. Default: API key.", + "anthropicPreferenceLabel": "Anthropic credential to use", + "anthropicPreferenceSubscription": "Claude subscription", "apiKeyCleared": "API key cleared", "apiKeySaved": "API key saved", "authCodeAlreadySubmitted": "That authorization code was already submitted. Waiting for login…", @@ -5753,6 +5762,8 @@ export default interface Resources { "continueToLogin": "Continue to login", "copiedCodeToClipboard": "Copied code to clipboard", "copyCode": "Copy code", + "credentialInUse": "In use", + "credentialOverridden": "Overridden below", "enterCodeOnGitHub": "Enter this code on GitHub", "failedToCopyCode": "Failed to copy code — copy it manually from the box above", "groupAuthenticated": "Authenticated", @@ -5958,6 +5969,7 @@ export default interface Resources { "offDefault": "Off (default)", "onForNewTasks": "On for new tasks", "operationalLogRetention": "Operational log retention", + "originWorkflowSelected": "Selected workflow", "ownerRepo": "owner/repo", "prefixForNewTaskIDsEGKB": "Prefix for new task IDs (e.g. KB, PROJ). No default — unset.", "prefixMustBe110UppercaseLetters": "Prefix must be 1–10 uppercase letters", @@ -5965,6 +5977,8 @@ export default interface Resources { "quickChatCloseOnOutsideClickHint": "When enabled, clicking outside the Quick Chat window closes it. Disable to keep it open until you close it explicitly. Default: enabled.", "quickChatLauncherHint": "Choose whether Quick Chat opens from the draggable floating button, a footer button beside Terminal, or stays hidden. Default: off (hidden).", "recentVerbatimRoomMessages": "Recent verbatim room messages", + "refinementTaskWorkflow": "Refinement task workflow", + "refinementTaskWorkflowHelp": "Workflow applied to refinement tasks — the follow-up card spawned from a done or in-review task plus your feedback. Choose \"Selected workflow\" to follow your current board workflow (falling back to the project default workflow). No default — unset means Selected workflow.", "removeNavItem": "Remove {{item}}", "reportDiscussionCategory": "Discussion category", "reportDiscussionCategoryEmpty": "No Discussion categories are available for the configured repository.", @@ -6010,6 +6024,8 @@ export default interface Resources { "showQuickChatButton": " Show quick chat button ", "showTaskChatsInCommonFeedHint": "When enabled, populated task-detail Chat conversations appear in the common Direct feed. Empty task chats stay hidden. Default: disabled.", "showTheFloatingChatButtonInTheDashboard": "Show the floating chat button in the dashboard. Chat is still accessible from the Chat tab in the mobile navigation.", + "taskCreateWorkflow": "CLI/agent-created task workflow", + "taskCreateWorkflowHelp": "Workflow applied to tasks opened by `fn task create` and the fn_task_create agent tool, which have no workflow picker. Choose \"Selected workflow\" to follow your current board workflow (falling back to the project default workflow). No default — unset means Selected workflow. An explicit workflow_id passed to fn_task_create still wins.", "taskPrefix": "Task Prefix", "todoThreshold": "Todo threshold", "trackingIssuesUseThisTaskAposSTitle": " Tracking issues use this task's title. If a task has no title yet, Fusion can summarize its description using the title summarization model in Project Models. ", @@ -6035,9 +6051,11 @@ export default interface Resources { }, "globalGeneral": { "agentToolOutputLimit": " Agent tool-output limit ", - "agentToolOutputLimitHint": " Maximum characters returned from each engine-injected tool result. Default: 16,000 characters per tool result. Leave empty to use the default. ", + "agentToolOutputLimitHint": " Maximum characters returned from each engine-injected tool result. When unset, inherits the 16,000-character engine default. Leave empty to use the default. ", "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 ", + "autoUpdateAndRestart": " Auto-update and restart ", + "autoUpdateAndRestartHelp": "When enabled, Fusion installs available updates on the selected release channel by itself and then restarts to apply them. Requires a supervising parent (the default for `fn dashboard`); hosts started with --no-supervise skip the install. Default: disabled.", "cLIBinaryOnPATH": " CLI binary on PATH ", "cLIBySpawning": " CLI by spawning", "channelBeta": "Beta — early builds from main", @@ -6061,7 +6079,7 @@ export default interface Resources { "leaveBothThinkingTogglesOffToKeepThe": " Leave both thinking toggles off to keep the original default behavior. This only controls persisted ", "manualOnlyNeverAutoCheck": "Manual only — never auto-check", "noLimitOnAgentToolOutput": " No limit on agent tool output ", - "noLimitOnAgentToolOutputHint": " Disable the shared tool-output clamp. A single tool result can consume the agent context window. Default: disabled; the default budget is 16,000 characters per tool result. ", + "noLimitOnAgentToolOutputHint": " Disable the shared tool-output clamp. A single tool result can consume the agent context window. Default: disabled; when unset, the budget inherits the 16,000-character engine default. ", "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. No default — unset.", @@ -6422,7 +6440,8 @@ export default interface Resources { "tooltip": { "global": "Shared across all projects", "project": "Specific to this project" - } + }, + "voiceInput": "Voice Input" }, "nodeRouting": { "blockExecution": "Block execution", @@ -6847,6 +6866,25 @@ export default interface Resources { "settingResultCount_other": "{{count}} matching settings" }, "title": "Settings", + "voiceInput": { + "download": "Download", + "downloading": "Downloading model…", + "downloadingProgress": "Downloading: {{progress}}%", + "enable": "Enable voice input", + "enableHelp": "Default: off. Voice dictation uses the operator-managed Parakeet v3 model.", + "error": "Model error: {{message}}", + "installed": "Installed", + "modelActions": "Model management", + "modelActionsHelp": "Download or remove the Parakeet v3 speech model.", + "modelStatus": "Parakeet v3 model status", + "modelStatusHelp": "The speech model is installed and managed locally on this device.", + "notInstalled": "Not installed", + "remove": "Remove", + "runtimeUnavailable": "Voice runtime unavailable", + "statusUnavailable": "Voice runtime status could not be determined; voice mode stays disabled.", + "title": "Voice Input", + "unknown": "Status unavailable" + }, "worktrees": { "allowSilentSiblingBranchRenameDuringExecutorConflicts": " Allow silent sibling branch rename during executor conflicts ", "alsoRebaseOntoLocalDefaultBranchHEAD": " Also rebase onto local default-branch HEAD ", @@ -6876,8 +6914,6 @@ export default interface Resources { "keepsProgressMovingBySwitchingToFusionApos": " keeps progress moving by switching to Fusion's built-in worktree backend. ", "limitsTotalGitWorktreesIncludingInReviewTasks": "Limits total git worktrees including in-review tasks. Default: 4.", "maxWorktrees": "Max Worktrees", - "worktreeLimitEnabled": "Limit concurrent worktrees", - "worktreeLimitEnabledHelp": "When on, Max Worktrees caps how many tasks may hold a worktree at once. When off, Max Concurrent Tasks is the only limit. Tasks always run in their own git worktree either way — this does not change where work executes. Default: on.", "namingStyleNotApplicableWhenRecycling": "Naming style is not applicable when recycling worktrees — pooled worktrees retain their existing names. \"Task ID\" is unavailable here because task-pinned worktrees are mutually exclusive with recycling; turn off Recycle worktrees to use it.", "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. Mutually exclusive with Task ID worktree naming.", "openApprovals": "Open Approvals", @@ -6901,6 +6937,8 @@ export default interface Resources { "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", + "worktreeLimitEnabled": "Limit concurrent worktrees", + "worktreeLimitEnabledHelp": "When on, Max Worktrees caps how many tasks may hold a worktree at once. When off, Max Concurrent Tasks is the only limit. Tasks always run in their own git worktree either way — this does not change where work executes. Default: on.", "worktreeNamingStyle": "Worktree Naming Style", "worktrees": "Worktrees", "worktreesDirectory": "Worktrees Directory", @@ -7863,6 +7901,7 @@ export default interface Resources { "title": "Merge Task" }, "move": { + "backTo": "Back to {{column}}", "backToInProgress": "Back to In Progress", "cancelMove": "Cancel Move", "keepProgress": "Keep Progress", @@ -8385,6 +8424,8 @@ export default interface Resources { "cliWaitingOnInputTitle": "The CLI agent is waiting for your input", "closeIssue": "Close Issue", "collapse": "Collapse", + "completedAt": "Completed {{date}}", + "completedAtTitle": "Completed {{date}}", "costBadgeAriaLabel": "Estimated cost {{amount}}", "costBadgeTitle": "Estimated cost {{amount}}", "createFailed": "Failed to create task", @@ -8392,6 +8433,8 @@ export default interface Resources { "createPrAriaLabel": "Create pull request", "createPrTitle": "Create a PR for this task", "createTaskTitle": "Create task", + "createdAt": "Created {{date}}", + "createdAtTitle": "Created {{date}}", "createdByAgent": "Created by agent", "createdByAgentNamed": "Created by agent: {{name}}", "createdPr": "Created PR #{{number}}", @@ -8513,6 +8556,8 @@ export default interface Resources { "refineExpandDesc": "Expand into a more comprehensive description", "refineSimplify": "Simplify", "refineSimplifyDesc": "Simplify and make more concise", + "refinesOf": "Refines {{id}}", + "refinesOfTitle": "Refinement of {{id}}", "refining": "Refining...", "removeImage": "Remove image", "resetProgress": "Reset Progress", @@ -9490,6 +9535,7 @@ export default interface Resources { "guardRejected": "This move is not allowed by the workflow.", "mergeBlocked": "This task is blocked from completing until its merge step finishes.", "unknownColumn": "That column doesn't exist in this task's workflow.", + "unplannedForExecution": "This task isn't ready for execution yet — planning or plan review is still outstanding.", "workflowMismatch": "Drag can't move a card between workflows. Use the workflow switcher instead." } },