feat(tasks): simplify launch-step destination (#3466)

This commit is contained in:
Carlos Florêncio
2026-09-07 13:47:06 +01:00
committed by GitHub
parent 5cf74c427f
commit ce512d146a
15 changed files with 362 additions and 48 deletions
@@ -1,10 +1,19 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { TooltipProvider } from "@kandev/ui/tooltip";
import { WorkflowSelectorRow } from "./workflow-selector-row";
import type { TaskCreateLaunchPreview } from "./task-create-dialog-launch-preview";
afterEach(cleanup);
const touchState = vi.hoisted(() => ({ enabled: false }));
vi.mock("@/hooks/use-compact-task-chrome", () => ({
useTouchDrawer: () => touchState.enabled,
}));
afterEach(() => {
touchState.enabled = false;
cleanup();
});
const launchPreview: TaskCreateLaunchPreview = {
stepId: "step-1",
@@ -28,7 +37,8 @@ function renderSelector(preview: TaskCreateLaunchPreview | null = launchPreview)
}
describe("WorkflowSelectorRow launch destination", () => {
it("shows the launch controls immediately after the selector", () => {
// @covers AC-TASKS-TASK-CREATE-LAUNCH-PREVIEW-001.1
it("shows the launch destination arrow and explanation after the selector", async () => {
renderSelector();
const trigger = screen.getByTestId("workflow-selector-trigger");
@@ -40,15 +50,37 @@ describe("WorkflowSelectorRow launch destination", () => {
expect(row.contains(trigger)).toBe(true);
expect(row.contains(launchStep)).toBe(true);
expect(trigger.className).not.toContain("flex-1");
expect(launchStep.textContent).toBe("Start step: In Progress");
expect(launchStep.textContent).toBe("In Progress");
expect(launchStep.className).not.toContain("ml-auto");
const launchStepInfo = screen.getByTestId("task-create-launch-step-info");
expect(trigger.contains(launchStepInfo)).toBe(false);
expect(launchStepInfo.getAttribute("aria-label")).toBe("Learn about the task start step");
expect(screen.getByTestId("task-create-launch-step-arrow")).toBeTruthy();
expect(Array.from(row.children).indexOf(launchStepInfo)).toBeLessThan(
Array.from(row.children).indexOf(launchStep),
);
fireEvent.focus(launchStepInfo);
expect((await screen.findByRole("tooltip")).textContent).toBe(
"The task starts in this workflow step. With a task description, an auto-start step can take priority over the configured Start step.",
);
});
it("opens the explanation in a drawer for coarse pointers", () => {
touchState.enabled = true;
renderSelector();
const launchStepInfo = screen.getByTestId("task-create-launch-step-info");
expect(launchStepInfo.getAttribute("aria-haspopup")).toBe("dialog");
expect(launchStepInfo.getAttribute("aria-expanded")).toBe("false");
fireEvent.click(launchStepInfo);
expect(launchStepInfo.getAttribute("aria-expanded")).toBe("true");
expect(screen.getByTestId("task-create-launch-step-help-drawer").textContent).toContain(
"The task starts in this workflow step.",
);
});
it("does not show a destination when the selected workflow has no preview", () => {
+55 -16
View File
@@ -2,10 +2,24 @@
import { Fragment, memo, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { IconCheck, IconChevronDown, IconInfoCircle, IconLogicBuffer } from "@tabler/icons-react";
import {
IconArrowBigRightLines,
IconCheck,
IconChevronDown,
IconLogicBuffer,
} from "@tabler/icons-react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@kandev/ui/tooltip";
import { Popover, PopoverContent, PopoverTrigger } from "@kandev/ui/popover";
import { Button } from "@kandev/ui/button";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@kandev/ui/drawer";
import { useTouchDrawer } from "@/hooks/use-compact-task-chrome";
import type { WorkflowSnapshotData } from "@/lib/state/slices/kanban/types";
import type { AgentProfileOption } from "@/lib/state/slices";
import { AgentLogo } from "@/components/agent-logo";
@@ -122,35 +136,60 @@ function WorkflowSelectorTrigger({
function LaunchDestinationInfo() {
const { t } = useTranslation();
const usesTouchDrawer = useTouchDrawer();
const [drawerOpen, setDrawerOpen] = useState(false);
const label = t("task:launchDestinationHelpLabel");
const description = t("task:launchDestinationHelp");
const trigger = (
<Button
type="button"
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 cursor-pointer text-muted-foreground hover:text-foreground [@media(pointer:coarse)]:h-11 [@media(pointer:coarse)]:w-11"
aria-label={label}
aria-haspopup={usesTouchDrawer ? "dialog" : undefined}
aria-expanded={usesTouchDrawer ? drawerOpen : undefined}
data-testid="task-create-launch-step-info"
>
<IconArrowBigRightLines
className="h-3.5 w-3.5"
aria-hidden="true"
data-testid="task-create-launch-step-arrow"
/>
</Button>
);
if (usesTouchDrawer) {
return (
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent data-testid="task-create-launch-step-help-drawer">
<DrawerHeader>
<DrawerTitle>{label}</DrawerTitle>
<DrawerDescription>{description}</DrawerDescription>
</DrawerHeader>
</DrawerContent>
</Drawer>
);
}
return (
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 cursor-pointer text-muted-foreground hover:text-foreground [@media(pointer:coarse)]:h-11 [@media(pointer:coarse)]:w-11"
aria-label={t("task:launchDestinationHelpLabel")}
data-testid="task-create-launch-step-info"
>
<IconInfoCircle className="h-3.5 w-3.5" aria-hidden="true" />
</Button>
</TooltipTrigger>
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
<TooltipContent className="max-w-[320px] text-xs leading-relaxed">
{t("task:launchDestinationHelp")}
{description}
</TooltipContent>
</Tooltip>
);
}
function LaunchDestinationLabel({ stepName }: { stepName: string }) {
const { t } = useTranslation();
return (
<span
className="min-w-0 max-w-[45vw] shrink truncate text-xs text-muted-foreground"
data-testid="task-create-launch-step"
>
{t("task:launchDestination", { step: stepName })}
{stepName}
</span>
);
}
+2 -2
View File
@@ -245,7 +245,7 @@ test.describe("Task creation", () => {
await expect(workflowSelector).toContainText("Launch Preview Workflow");
await dialog.getByTestId("task-description-input").fill("");
const launchStep = dialog.getByTestId("task-create-launch-step");
await expect(launchStep).toHaveText("Start step: Backlog");
await expect(launchStep).toHaveText("Backlog");
await expect(workflowSelector).not.toContainText("Start step:");
const selectorBox = await workflowSelector.boundingBox();
const launchStepBox = await launchStep.boundingBox();
@@ -269,7 +269,7 @@ test.describe("Task creation", () => {
await dialog.getByTestId("task-title-input").fill("Preview the launch prompt");
const description = "Review the launch preview";
await dialog.getByTestId("task-description-input").fill(description);
await expect(launchStep).toHaveText("Start step: In Progress");
await expect(launchStep).toHaveText("In Progress");
const toggle = dialog.getByTestId("task-create-launch-preview-toggle");
await expect(toggle).toHaveAttribute(
"aria-label",
@@ -51,7 +51,7 @@ test.describe("mobile task-create launch prompt preview", () => {
.tap();
await dialog.getByTestId("task-description-input").fill("");
const launchStep = dialog.getByTestId("task-create-launch-step");
await expect(launchStep).toHaveText("Start step: Backlog");
await expect(launchStep).toHaveText("Backlog");
await expect(workflowSelector).not.toContainText("Start step:");
const selectorBox = await workflowSelector.boundingBox();
const launchStepBox = await launchStep.boundingBox();
@@ -66,11 +66,19 @@ test.describe("mobile task-create launch prompt preview", () => {
launchStepInfoBox.x + launchStepInfoBox.width - 1,
);
expect(launchStepInfoBox.width).toBeGreaterThanOrEqual(44);
await launchStepInfo.tap();
const launchStepHelpDrawer = testPage.getByTestId("task-create-launch-step-help-drawer");
await expect(launchStepHelpDrawer).toBeVisible();
await expect(launchStepHelpDrawer).toContainText(
"The task starts in this workflow step. With a task description, an auto-start step can take priority over the configured Start step.",
);
await launchStepHelpDrawer.press("Escape");
await expect(launchStepHelpDrawer).not.toBeVisible();
await dialog.getByTestId("task-title-input").fill("Mobile launch preview");
const description = "Review mobile launch preview";
await dialog.getByTestId("task-description-input").fill(description);
await expect(launchStep).toHaveText("Start step: In Progress");
await expect(launchStep).toHaveText("In Progress");
const toggle = dialog.getByTestId("task-create-launch-preview-toggle");
await expect(toggle).toHaveAttribute(
-1
View File
@@ -2003,7 +2003,6 @@
"workspaceRoot": "Workspace root",
"worktreeExecutorRequiresRepository": "Worktree executor requires a repository.",
"worktreeNumbered": "Worktree {{index}}",
"launchDestination": "Start step: {{step}}",
"launchDestinationHelpLabel": "Learn about the task start step",
"launchDestinationHelp": "The task starts in this workflow step. With a task description, an auto-start step can take priority over the configured Start step.",
"launchErrorBaseBranchMissing": "The task could not start because its base branch is unavailable.",
-1
View File
@@ -2003,7 +2003,6 @@
"workspaceRoot": "Ŵōŕķśƥàćē ŕōōţ",
"worktreeExecutorRequiresRepository": "Ŵōŕķţŕēē ēxēćũţōŕ ŕēqũĩŕēś à ŕēƥōśĩţōŕŷ.",
"worktreeNumbered": "Ŵōŕķţŕēē {{index}}",
"launchDestination": "Śţàŕţ śţēƥ: {{step}}",
"launchDestinationHelpLabel": "Ĺēàŕń àƀōũţ ţĥē ţàśķ śţàŕţ śţēƥ",
"launchDestinationHelp": "Ţĥē ţàśķ śţàŕţś ĩń ţĥĩś ŵōŕķƒĺōŵ śţēƥ. Ŵĩţĥ à ţàśķ ďēśćŕĩƥţĩōń, àń àũţō-śţàŕţ śţēƥ ćàń ţàķē ƥŕĩōŕĩţŷ ōvēŕ ţĥē ćōńƒĩĝũŕēď Śţàŕţ śţēƥ.",
"launchErrorBaseBranchMissing": "Ţĥē ţàśķ ćōũĺď ńōţ śţàŕţ ƀēćàũśē ĩţś ƀàśē ƀŕàńćĥ ĩś ũńàvàĩĺàƀĺē.",
-1
View File
@@ -2003,7 +2003,6 @@
"promptHistoryPromptLabel": "Prompt {{number}}",
"promptHistoryPromptLabelGeneric": "Prompt",
"promptHistoryEmpty": "Ainda sem prompts.",
"launchDestination": "Passo de início: {{step}}",
"launchDestinationHelpLabel": "Saiba mais sobre o passo de início",
"launchDestinationHelp": "A tarefa começa neste passo do fluxo. Com uma descrição da tarefa, um passo que inicia automaticamente um agente pode ter prioridade sobre o passo de início configurado.",
"launchErrorBaseBranchMissing": "Não foi possível iniciar a tarefa porque o branch base não está disponível.",
-1
View File
@@ -2003,7 +2003,6 @@
"promptHistoryPromptLabel": "提示 {{number}}",
"promptHistoryPromptLabelGeneric": "提示",
"promptHistoryEmpty": "暂无提示。",
"launchDestination": "开始步骤:{{step}}",
"launchDestinationHelpLabel": "了解任务开始步骤",
"launchDestinationHelp": "任务会在此工作流步骤开始。有任务描述时,自动启动智能体的步骤可能优先于配置的开始步骤。",
"launchErrorBaseBranchMissing": "任务无法启动,因为基础分支不可用。",
-1
View File
@@ -2003,7 +2003,6 @@
"promptHistoryPromptLabel": "提示 {{number}}",
"promptHistoryPromptLabelGeneric": "提示",
"promptHistoryEmpty": "暫無提示。",
"launchDestination": "開始步驟:{{step}}",
"launchDestinationHelpLabel": "了解任務開始步驟",
"launchDestinationHelp": "任務會在此工作流程步驟開始。有任務描述時,自動啓動代理程式的步驟可能優先於已設定的開始步驟。",
"launchErrorBaseBranchMissing": "任務無法啓動,因為基礎分支不可用。",
-1
View File
@@ -2003,7 +2003,6 @@
"promptHistoryPromptLabel": "提示 {{number}}",
"promptHistoryPromptLabelGeneric": "提示",
"promptHistoryEmpty": "暫無提示。",
"launchDestination": "開始步驟:{{step}}",
"launchDestinationHelpLabel": "瞭解任務開始步驟",
"launchDestinationHelp": "任務會在此工作流程步驟開始。有任務描述時,自動啟動代理程式的步驟可能優先於已設定的開始步驟。",
"launchErrorBaseBranchMissing": "任務無法啟動,因為基礎分支不可用。",
@@ -0,0 +1,116 @@
---
created: 2026-09-07
status: complete
requirements:
- REQ-TASKS-TASK-CREATE-LAUNCH-PREVIEW-001
system_design:
- ../../specs/tasks/system-design/task-create-launch-preview.md
legacy_specs: []
---
# Implementation Plan: Task Create Launch-Step Arrow
## Overview
Refine the existing task-create launch destination into a clearer visual flow.
Replace the generic information glyph and visible **Start step:** prefix with a
directional arrow help button followed by the destination step name. Preserve
the current explanation and prove that it remains available by pointer,
keyboard, and touch.
## Scope
### In scope
- Render `IconArrowBigRightLines` between the selected workflow and launch step.
- Show only the resolved step name as visible destination text.
- Preserve the localized explanation in a tooltip on hover and focus, and
expose it in a drawer with a 44-pixel trigger on coarse pointers.
- Preserve the localized accessible name for the help button.
- Remove the now-unused visible launch-destination label from all locale
catalogs and update the task-creation guide.
- Update focused component, desktop, and mobile coverage.
### Out of scope
- Changing launch-destination resolution or task submission behavior.
- Making the launch destination selectable.
- Changing the prompt-preview toggle or editor.
- Adding a new route or mobile composition.
## Technical approach
### Launch destination presentation
Update `WorkflowSelectorRow` so `LaunchDestinationInfo` renders
`IconArrowBigRightLines` in the existing ghost icon button. Keep the existing
localized accessible name, tooltip content, test ID, compact fine-pointer size,
and 44-pixel coarse-pointer hit area. Use `useTouchDrawer` to present the same
localized explanation in a drawer for coarse pointers. Render
`launchPreview.stepName` directly in `LaunchDestinationLabel`; workflow step
names are domain data and are not translated.
The arrow remains a help control rather than a navigation or submit action. Its
placement between the workflow trigger and step name communicates the flow
`workflow -> launch destination`, while the tooltip or touch drawer explains
the action-sensitive precedence.
### Localization and documentation
Remove `task:launchDestination` from the English, Portuguese, Simplified
Chinese, Traditional Chinese, and pseudo catalogs because the visible prefix is
removed. Retain `task:launchDestinationHelpLabel` and
`task:launchDestinationHelp`. Update the public task-creation procedure to name
the arrow help control instead of an information button.
### Responsive behavior
Desktop and mobile keep the current inline selector row and shared task-create
dialog. The nearest mobile exemplar remains the Kanban FAB to task-create dialog
flow documented by the launch-preview design. This content-only refinement adds
no scroll, safe-area, or navigation boundary. The existing mobile launch-preview
scenario will prove touch access, hit-area size, containment, and the absence of
horizontal overflow.
## Tests
- `AC-TASKS-TASK-CREATE-LAUNCH-PREVIEW-001.1` maps to
`apps/web/components/workflow-selector-row.test.tsx`, which verifies the arrow
glyph, destination-only text, accessible name, tooltip disclosure, and
coarse-pointer drawer.
## E2E tests
- Update `apps/web/e2e/tests/task/create-task.spec.ts` to verify the destination
step names and the existing pointer-hover explanation.
- Update
`apps/web/e2e/tests/task/mobile-create-task-launch-preview.spec.ts` to verify
the destination step names, touch-activated explanation, 44-pixel target, and
existing containment contract in `mobile-chrome`.
## Work orders
- [x] [Task 01: Refine the launch-step help control](task-01-refine-launch-step-help-control.md)
## Verification results
- Focused selector component tests passed (3 tests) after failing first on the
old **Start step:** label and then on the missing coarse-pointer drawer.
- Focused ESLint, TypeScript typecheck, i18n completeness, and i18n new-code
ratchet checks passed.
- Public documentation tests passed (61 tests), and all 46 published pages
validated.
- Desktop Chromium passed the launch-preview scenario with pointer-hover help.
- Mobile Chrome passed the launch-preview scenario with the touch drawer,
44-pixel target sizing, containment, and no horizontal overflow.
- Specification tests, full specification lint, and `git diff --check` passed.
## Risks
- A directional arrow can resemble a navigation action if it is separated from
the destination name. Keeping the arrow inline, muted, and immediately before
the name preserves the intended flow reading.
- Removing localized visible copy can leave stale catalog entries or public
guidance. The i18n and documentation validators cover both boundaries.
- Tooltip-only help would be unavailable on touch. The coarse-pointer drawer
and mobile interaction test preserve tap access.
@@ -0,0 +1,114 @@
---
id: "01-refine-launch-step-help-control"
title: "Refine the launch-step help control"
status: done
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-TASKS-TASK-CREATE-LAUNCH-PREVIEW-001
acceptance_criteria:
- AC-TASKS-TASK-CREATE-LAUNCH-PREVIEW-001.1
system_design:
- ../../specs/tasks/system-design/task-create-launch-preview.md
---
# Task 01: Refine the Launch-Step Help Control
## Summary
Replace the task-create launch destination's generic information glyph and
visible prefix with a directional arrow help control followed by the resolved
step name. Preserve accessible explanatory behavior across pointer, keyboard,
and touch input.
## In scope
- Render `IconArrowBigRightLines` in the existing launch-step help button.
- Render only the destination step name beside the button.
- Preserve localized tooltip content and accessible naming, and use the
standard drawer and responsive target sizing on coarse pointers.
- Remove the unused visible-label translations and update public guidance.
- Update focused component and desktop/mobile browser coverage.
## Out of scope
- Launch routing, workflow selection, task submission, and prompt-preview
behavior.
- New responsive composition or navigation.
## Acceptance
- The selected workflow is followed by the muted directional arrow button and
resolved destination step name, with no visible **Start step:** prefix.
- Hovering or focusing the arrow shows the localized launch-destination
explanation; activating it on a coarse pointer opens the same explanation in
a drawer.
- The button retains its localized accessible name, and its coarse-pointer hit
area remains at least 44 CSS pixels without enlarging fine-pointer desktop
density.
## Verification
```bash
cd apps/web
pnpm test -- --run components/workflow-selector-row.test.tsx
pnpm exec eslint --max-warnings 0 components/workflow-selector-row.tsx components/workflow-selector-row.test.tsx
pnpm run typecheck
pnpm run i18n:check
pnpm run i18n:ratchet
pnpm e2e:run tests/task/create-task.spec.ts -- --grep "launch prompt preview"
pnpm e2e:run --project mobile-chrome tests/task/mobile-create-task-launch-preview.spec.ts
cd ../..
node --test scripts/validate-public-docs.test.mjs
node scripts/validate-public-docs.mjs
git diff --check
```
## Files likely touched
- `apps/web/components/workflow-selector-row.tsx`
- `apps/web/components/workflow-selector-row.test.tsx`
- `apps/web/e2e/tests/task/create-task.spec.ts`
- `apps/web/e2e/tests/task/mobile-create-task-launch-preview.spec.ts`
- `apps/web/src/locales/en/task.json`
- `apps/web/src/locales/pt-pt/task.json`
- `apps/web/src/locales/zh-cn/task.json`
- `apps/web/src/locales/zh-hk/task.json`
- `apps/web/src/locales/zh-tw/task.json`
- `apps/web/src/locales/pseudo/task.json`
- `docs/public/tasks-and-workflows.md`
## Dependencies
None.
## Risks
- The arrow can look like navigation if spacing or emphasis separates it from
the destination label.
- Coarse-pointer detection must route the disclosure to the standard touch
drawer while fine pointers retain hover and keyboard focus behavior.
## Parallelism
`sequential`
## Inputs
- Launch destination disclosure in the task-create launch-preview requirement.
- Workflow selector and responsive behavior in the paired system design.
- Existing selector component and launch-preview Playwright coverage.
## Results
- Replaced the generic information glyph with `IconArrowBigRightLines` and
rendered the resolved workflow step name without a visible prefix.
- Preserved localized accessible naming and explanatory tooltip behavior for
pointer hover and keyboard focus; mobile coverage proves the touch drawer and
its 44-pixel coarse-pointer trigger.
- Removed the unused visible-label key from all locale catalogs and updated the
public task-creation how-to guide.
- The focused component test passed (3 tests), targeted desktop and mobile E2E
each passed (1 test), and all static, i18n, documentation, specification, and
diff checks listed in the plan passed.
+2 -2
View File
@@ -68,7 +68,7 @@ Use **New Task** in the sidebar. In an open task, the **Task** split button also
task titles** is enabled, the New Task dialog hides this field, requires a nonempty prompt, and uses
the prompt's first six words as a provisional title while the first eligible agent session chooses
the final title. The empty-description Plan Mode exception applies only when this setting is disabled.
2. Select the workspace and workflow when Kandev cannot infer them. A regular non-ephemeral task must belong to a workflow. When the workflow selector shows a selected workflow, **Start step** identifies the step used by the applicable immediate launch action. Select the information button immediately after the workflow selector for an explanation of the action-sensitive destination. With an empty description, **Start Plan Mode** uses the first positional step. After you enter a description, **Start task** and **Start task in plan mode** use the first positional step with **Auto-start agent**, then the configured **Start step**, then the first step.
2. Select the workspace and workflow when Kandev cannot infer them. A regular non-ephemeral task must belong to a workflow. The step name after the workflow selector identifies the destination used by the applicable immediate launch action. Select the arrow button between the workflow and step name for an explanation of the action-sensitive destination. With an empty description, **Start Plan Mode** uses the first positional step. After you enter a description, **Start task** and **Start task in plan mode** use the first positional step with **Auto-start agent**, then the configured **Start step**, then the first positional step.
3. Select a source:
| Source | Use it for | Important behavior |
@@ -78,7 +78,7 @@ Use **New Task** in the sidebar. In an open task, the **Task** split button also
| **None** | Planning, research, or work outside Git | Use a scratch workspace or an optional folder on the Kandev host. Git worktree execution and repository-aware Changes, branch, and pull-request features are unavailable. |
4. Select a compatible executor profile and agent profile. A workflow default agent profile locks the task-level agent selector. Executor and agent compatibility is validated before launch.
5. Enter the initial description. If the applicable launch step has a prompt template, use the eye button after **Enhance prompt with AI** to inspect the launch prompt with your description inserted. The preview leaves task IDs and saved-prompt references unresolved until the task exists. Toggle the button again to return to the unchanged description. In the **New Task** dialog, an empty description changes the primary action to **Start Plan Mode** and uses the first positional workflow step; the other dialog actions require a description. Agent-facing task MCP has different empty-description rules. When agent-generated task titles are enabled, every task and subtask action requires a nonempty prompt; the empty-description Plan Mode exception is disabled. A nonempty description exposes the standard split actions and updates **Start step** to the immediate auto-start destination.
5. Enter the initial description. If the applicable launch step has a prompt template, use the eye button after **Enhance prompt with AI** to inspect the launch prompt with your description inserted. The preview leaves task IDs and saved-prompt references unresolved until the task exists. Toggle the button again to return to the unchanged description. In the **New Task** dialog, an empty description changes the primary action to **Start Plan Mode** and uses the first positional workflow step; the other dialog actions require a description. Agent-facing task MCP has different empty-description rules. When agent-generated task titles are enabled, every task and subtask action requires a nonempty prompt; the empty-description Plan Mode exception is disabled. A nonempty description exposes the standard split actions and updates the displayed destination to the first positional step with **Auto-start agent**, or falls back to **Start step** and then the first positional step.
6. Choose the applicable action:
- **Start Plan Mode** is the primary empty-description action and creates the task through the plan-mode path.
- **Start task** requires a nonempty description, creates the task, and starts its agent. This path starts in the first positional step whose entry actions include **Auto-start agent**, falling back to **Start step** when the workflow automates no step.
@@ -45,10 +45,13 @@ that the workflow destination is not hidden.
- **AC-TASKS-TASK-CREATE-LAUNCH-PREVIEW-001.1:** When the workflow selector
shows a selected workflow, the system shall show the immediate launch
destination outside the selector, immediately to its right, with an
adjacent localized information control followed by the muted label
**Start step: {{step}}**. The information control shall provide help that
explains the displayed destination and its action-sensitive precedence.
destination outside the selector, immediately to its right. A directional
arrow help control shall connect the workflow to the muted destination step
name without a visible **Start step:** prefix. On pointer hover, keyboard
focus, or touch activation, the control shall provide localized help that
explains the displayed destination and its action-sensitive precedence. The
control shall retain a localized accessible name, and its coarse-pointer hit
area shall be at least 44 CSS pixels.
- **AC-TASKS-TASK-CREATE-LAUNCH-PREVIEW-001.2:** When the description is empty,
the displayed launch destination shall be the first positional step because
**Start Plan Mode** uses the plan-mode launch path. When the description is
@@ -107,12 +107,17 @@ context. Its purpose is to show how the launch step applies its own template.
The selected workflow trigger renders only the workflow name and existing
chevron. The launch destination renders as a sibling group outside the trigger,
immediately to its right. The group contains the information button first and
the muted localized **Start step: {{step}}** label second. The trigger keeps its
intrinsic selector width and does not claim the full row. The information
button exposes localized help on hover and focus. On coarse pointers, the
information button has a hit area of at least 44 CSS pixels. Truncation keeps
the selector row within its available width.
immediately to its right. The group contains an `IconArrowBigRightLines` help
button first and the muted destination step name second. The arrow expresses
the flow from the selected workflow into its launch destination, so the visible
**Start step:** prefix and the generic information glyph are removed. The
trigger keeps its intrinsic selector width and does not claim the full row.
The arrow button exposes the existing localized help in a tooltip on pointer
hover and keyboard focus. On coarse pointers, `useTouchDrawer` replaces the
tooltip with a drawer containing the same localized title and description. The
button retains its localized accessible name and has a hit area of at least 44
CSS pixels on coarse pointers. Truncation keeps the selector row within its
available width.
The existing selector visibility rules remain unchanged. A single implicit
workflow can still omit the selector when it has no override information.
@@ -143,7 +148,8 @@ pointers, its active hit area is at least 44 CSS pixels. The read-only text wrap
inside the composer and owns only the same bounded overflow as the textarea.
The nearest mobile exemplar is the current Kanban FAB to task-create dialog
flow. This change adds no drawer, route, fixed control, or safe-area boundary.
flow. The launch-destination explanation uses the standard coarse-pointer
drawer; this change adds no route, fixed control, or safe-area boundary.
## Failure and recovery
@@ -170,10 +176,12 @@ server-owned values.
- Unit tests cover action-sensitive launch routing, stale workflow filtering,
workflow-snapshot step-event synchronization, and prompt composition.
- Component tests cover selector text, toggle state, draft preservation, and
the no-prompt fallback.
- Desktop Playwright covers workflow switching and composed preview content.
- Mobile Playwright covers the same user value, the 44-pixel hit area, viewport
containment, and horizontal overflow.
the no-prompt fallback. Focused selector coverage also verifies the arrow
glyph and help disclosure.
- Desktop Playwright covers workflow switching, pointer-hover help, and composed
preview content.
- Mobile Playwright covers the same user value, the touch drawer, the 44-pixel
hit area, viewport containment, and horizontal overflow.
## Related decisions