feat(tasks): simplify launch-step destination (#3466)
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -2003,7 +2003,6 @@
|
||||
"workspaceRoot": "Ŵōŕķśƥàćē ŕōōţ",
|
||||
"worktreeExecutorRequiresRepository": "Ŵōŕķţŕēē ēxēćũţōŕ ŕēqũĩŕēś à ŕēƥōśĩţōŕŷ.",
|
||||
"worktreeNumbered": "Ŵōŕķţŕēē {{index}}",
|
||||
"launchDestination": "Śţàŕţ śţēƥ: {{step}}",
|
||||
"launchDestinationHelpLabel": "Ĺēàŕń àƀōũţ ţĥē ţàśķ śţàŕţ śţēƥ",
|
||||
"launchDestinationHelp": "Ţĥē ţàśķ śţàŕţś ĩń ţĥĩś ŵōŕķƒĺōŵ śţēƥ. Ŵĩţĥ à ţàśķ ďēśćŕĩƥţĩōń, àń àũţō-śţàŕţ śţēƥ ćàń ţàķē ƥŕĩōŕĩţŷ ōvēŕ ţĥē ćōńƒĩĝũŕēď Śţàŕţ śţēƥ.",
|
||||
"launchErrorBaseBranchMissing": "Ţĥē ţàśķ ćōũĺď ńōţ śţàŕţ ƀēćàũśē ĩţś ƀàśē ƀŕàńćĥ ĩś ũńàvàĩĺàƀĺē.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -2003,7 +2003,6 @@
|
||||
"promptHistoryPromptLabel": "提示 {{number}}",
|
||||
"promptHistoryPromptLabelGeneric": "提示",
|
||||
"promptHistoryEmpty": "暂无提示。",
|
||||
"launchDestination": "开始步骤:{{step}}",
|
||||
"launchDestinationHelpLabel": "了解任务开始步骤",
|
||||
"launchDestinationHelp": "任务会在此工作流步骤开始。有任务描述时,自动启动智能体的步骤可能优先于配置的开始步骤。",
|
||||
"launchErrorBaseBranchMissing": "任务无法启动,因为基础分支不可用。",
|
||||
|
||||
@@ -2003,7 +2003,6 @@
|
||||
"promptHistoryPromptLabel": "提示 {{number}}",
|
||||
"promptHistoryPromptLabelGeneric": "提示",
|
||||
"promptHistoryEmpty": "暫無提示。",
|
||||
"launchDestination": "開始步驟:{{step}}",
|
||||
"launchDestinationHelpLabel": "了解任務開始步驟",
|
||||
"launchDestinationHelp": "任務會在此工作流程步驟開始。有任務描述時,自動啓動代理程式的步驟可能優先於已設定的開始步驟。",
|
||||
"launchErrorBaseBranchMissing": "任務無法啓動,因為基礎分支不可用。",
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user