From d1b02e3747d9c1e42f6573d0d87f03cc704e30e9 Mon Sep 17 00:00:00 2001 From: Renn F Date: Wed, 15 Jul 2026 16:30:26 +0200 Subject: [PATCH] =?UTF-8?q?feat(panel):=20tooltip=20sweep=20=E2=80=94=20ta?= =?UTF-8?q?sks,=20kanban,=20task=20assistant,=20git,=20work=20sessions=20(?= =?UTF-8?q?58=20tips,=2022=20files)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full import-graph walk per page: filter-chip and plan-section icon buttons gain their first accessible names, per-state description maps for git/docs/finding/priority badges, truncation-gated full-value tips, disabled buttons explain their gate. Stateful Radix triggers use the task-tabs data-state re-assertion pattern. --- panel/src/app/(dashboard)/kanban/page.tsx | 85 +++- panel/src/components/git/git-diff-viewer.tsx | 43 ++- panel/src/components/git/git-status-panel.tsx | 54 ++- .../kanban/shared/blocked-badge.tsx | 13 +- .../kanban/shared/priority-indicator.tsx | 16 +- .../components/prompter/batch-review-card.tsx | 80 ++-- .../prompter/board-review-sent-card.tsx | 25 +- .../prompter/draft-proposal-card.tsx | 63 +-- panel/src/components/prompter/intake-form.tsx | 58 ++- .../src/components/prompter/success-card.tsx | 9 +- .../__tests__/create-task-dialog.test.tsx | 17 + .../tasks/approve-and-start-button.tsx | 9 +- .../components/tasks/create-task-dialog.tsx | 20 +- .../components/tasks/docs-status-badge.tsx | 67 ++-- .../src/components/tasks/edit-task-dialog.tsx | 20 +- .../src/components/tasks/git-status-badge.tsx | 88 +++-- .../src/components/tasks/markdown-editor.tsx | 5 +- .../__tests__/tab-findings.test.tsx | 39 ++ .../__tests__/task-description.test.tsx | 15 + .../tasks/task-detail/commit-card.tsx | 20 +- .../tasks/task-detail/tab-collision.tsx | 62 ++- .../tasks/task-detail/tab-findings.tsx | 41 +- .../tasks/task-detail/tab-overview.tsx | 17 +- .../components/tasks/task-detail/tab-plan.tsx | 365 +++++++++++------- .../tasks/task-detail/task-action-dialogs.tsx | 34 +- .../tasks/task-detail/task-description.tsx | 20 +- .../tasks/task-detail/task-metadata.tsx | 31 +- .../tasks/task-detail/work-session-card.tsx | 117 ++++-- panel/src/components/tasks/task-filters.tsx | 56 ++- panel/src/components/tasks/task-selector.tsx | 133 ++++--- 30 files changed, 1079 insertions(+), 543 deletions(-) diff --git a/panel/src/app/(dashboard)/kanban/page.tsx b/panel/src/app/(dashboard)/kanban/page.tsx index 1b7226cc..923c9cf0 100644 --- a/panel/src/app/(dashboard)/kanban/page.tsx +++ b/panel/src/app/(dashboard)/kanban/page.tsx @@ -10,6 +10,11 @@ import { } from "@/components/kanban"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Skeleton } from "@/components/ui/skeleton"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { pickTab } from "@/lib/tabs"; import { Code, TestTube, GitPullRequest, ClipboardList } from "lucide-react"; @@ -35,22 +40,70 @@ function KanbanPageContent() {
- - - Developer - - - - QA - - - - PR Review - - - - PM - + {/* TooltipTrigger's asChild Slot merge clobbers TabsTrigger's own + data-state with the tooltip's — re-assert the real selection + state explicitly so data-[state=active] styling survives + (same fix as task-detail/task-tabs.tsx). */} + + + + + Developer + + + + Tasks claimed and worked by developers — backlog through + completion + + + + + + + QA + + + Quality assurance review workflow + + + + + + PR Review + + + + In-path PR-review gate for assembled PRs, before the PM merges + + + + + + + PM + + + + Project management overview — every lifecycle state, including + recovery states + + diff --git a/panel/src/components/git/git-diff-viewer.tsx b/panel/src/components/git/git-diff-viewer.tsx index 8dfecd28..a5add9b6 100644 --- a/panel/src/components/git/git-diff-viewer.tsx +++ b/panel/src/components/git/git-diff-viewer.tsx @@ -10,6 +10,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; import { FileCode, FileDiff, WrapText } from "lucide-react"; +import { HelpTip } from "@/components/ui/help-tip"; interface GitDiffViewerProps { stagedDiff: GitDiffResponse | undefined; @@ -124,21 +125,39 @@ export function GitDiffViewer({
+ {/* HelpTip wraps an inner span, never the TabsTrigger itself — + TooltipTrigger's asChild would clobber the trigger's own + data-state and break the active-tab highlight (see + task-tabs.tsx for the fuller writeup of this bug class). */} - Working Directory - {unstagedCount > 0 && ( - - {unstagedCount} - - )} + + + Working Directory + {unstagedCount > 0 && ( + + {unstagedCount} + + )} + + - Staged - {stagedCount > 0 && ( - - {stagedCount} - - )} + + + Staged + {stagedCount > 0 && ( + + {stagedCount} + + )} + +
diff --git a/panel/src/components/git/git-status-panel.tsx b/panel/src/components/git/git-status-panel.tsx index 429d9320..5a7f3cd0 100644 --- a/panel/src/components/git/git-status-panel.tsx +++ b/panel/src/components/git/git-status-panel.tsx @@ -14,12 +14,22 @@ import { ArrowDown, CheckCircle, } from "lucide-react"; +import { HelpTip } from "@/components/ui/help-tip"; interface GitStatusPanelProps { status: GitStatusResponse | undefined; isLoading: boolean; } +// Plain-language explanation per file bucket, mirroring the task-status-badge +// per-state description map so non-git-fluent readers (CEO, PM) know what +// each section means without having to already know git jargon. +const FILE_SECTION_DESCRIPTIONS = { + staged: "Changes staged and ready to be included in the next commit.", + unstaged: "Tracked files with changes not yet staged for commit.", + untracked: "New files git isn't tracking yet.", +} as const; + export function GitStatusPanel({ status, isLoading }: GitStatusPanelProps) { if (isLoading) { return ( @@ -77,16 +87,20 @@ export function GitStatusPanel({ status, isLoading }: GitStatusPanelProps) { {(status.ahead > 0 || status.behind > 0) && (
{status.ahead > 0 && ( - - - {status.ahead} ahead - + + + + {status.ahead} ahead + + )} {status.behind > 0 && ( - - - {status.behind} behind - + + + + {status.behind} behind + + )}
)} @@ -99,9 +113,11 @@ export function GitStatusPanel({ status, isLoading }: GitStatusPanelProps) { {/* Staged Files */} {status.staged_files.length > 0 && (
-

- Staged ({status.staged_files.length}) -

+ +

+ Staged ({status.staged_files.length}) +

+
{status.staged_files.map((file) => (
0 && (
-

- Modified ({status.unstaged_files.length}) -

+ +

+ Modified ({status.unstaged_files.length}) +

+
{status.unstaged_files.map((file) => (
0 && (
-

- Untracked ({status.untracked_files.length}) -

+ +

+ Untracked ({status.untracked_files.length}) +

+
{status.untracked_files.map((file) => (
- - Blocked - + + + + Blocked + + ); } diff --git a/panel/src/components/kanban/shared/priority-indicator.tsx b/panel/src/components/kanban/shared/priority-indicator.tsx index 3149ede0..a9924326 100644 --- a/panel/src/components/kanban/shared/priority-indicator.tsx +++ b/panel/src/components/kanban/shared/priority-indicator.tsx @@ -1,6 +1,7 @@ "use client"; import { Badge } from "@/components/ui/badge"; +import { HelpTip } from "@/components/ui/help-tip"; interface PriorityIndicatorProps { priority: number; @@ -20,10 +21,19 @@ const priorityLabels: Record = { 3: "P3 - Low", }; +const priorityDescriptions: Record = { + 0: "Highest urgency — work this before anything else.", + 1: "High urgency — prioritize over P2/P3 work.", + 2: "Standard priority — the default for most tasks.", + 3: "Low urgency — fine to defer behind higher-priority work.", +}; + export function PriorityIndicator({ priority }: PriorityIndicatorProps) { return ( - - {priorityLabels[priority] ?? "P2 - Medium"} - + + + {priorityLabels[priority] ?? "P2 - Medium"} + + ); } diff --git a/panel/src/components/prompter/batch-review-card.tsx b/panel/src/components/prompter/batch-review-card.tsx index 06b4242e..7f9221df 100644 --- a/panel/src/components/prompter/batch-review-card.tsx +++ b/panel/src/components/prompter/batch-review-card.tsx @@ -124,11 +124,16 @@ export function BatchReviewCard({
- MegaTask: {batch.title || "Untitled"} + + MegaTask + + : {batch.title || "Untitled"} - - {batch.drafts.length} tasks - + + + {batch.drafts.length} tasks + +

One batch, sequenced into conflict-free waves. Each task keeps its own @@ -191,7 +196,10 @@ export function BatchReviewCard({ : "text-muted-foreground" }`} > - Projects {selected.length === 0 && "— pick at least one"} + + Projects + {" "} + {selected.length === 0 && "— pick at least one"}

{CELL_TEAMS.map((cell) => { const repos = scopedByCell(cell); @@ -231,9 +239,11 @@ export function BatchReviewCard({ {/* Wave plan — how the batch will be sequenced */} {waves && waves.length > 0 && (
-

- Wave plan ({waves.length} wave{waves.length === 1 ? "" : "s"}) -

+ +

+ Wave plan ({waves.length} wave{waves.length === 1 ? "" : "s"}) +

+
    {waves.map((wave, w) => (
  1. {/* Board review & Start → the Board reviews the whole batch first */} - + + + {/* Approve & Start → straight to the Main PM, waves dispatch at once */} - + + +
diff --git a/panel/src/components/prompter/board-review-sent-card.tsx b/panel/src/components/prompter/board-review-sent-card.tsx index f585de79..013a3ec1 100644 --- a/panel/src/components/prompter/board-review-sent-card.tsx +++ b/panel/src/components/prompter/board-review-sent-card.tsx @@ -11,6 +11,7 @@ import { CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; +import { HelpTip } from "@/components/ui/help-tip"; interface BoardReviewSentCardProps { /** The umbrella task id — the single board-review / CEO-approve unit. */ @@ -52,15 +53,21 @@ export function BoardReviewSentCard({

{taskTitle}

- - {rootSubtaskCount} task{rootSubtaskCount === 1 ? "" : "s"} - - - {waveCount} wave{waveCount === 1 ? "" : "s"} - - - ID: {taskId.slice(0, 8)}… - + + + {rootSubtaskCount} task{rootSubtaskCount === 1 ? "" : "s"} + + + + + {waveCount} wave{waveCount === 1 ? "" : "s"} + + + + + ID: {taskId.slice(0, 8)}… + +

The Product Owner and Head of Marketing are reviewing this MegaTask. diff --git a/panel/src/components/prompter/draft-proposal-card.tsx b/panel/src/components/prompter/draft-proposal-card.tsx index d1577f3d..454f6bf1 100644 --- a/panel/src/components/prompter/draft-proposal-card.tsx +++ b/panel/src/components/prompter/draft-proposal-card.tsx @@ -11,6 +11,7 @@ import { } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { CopyButton } from "@/components/ui/copy-button"; +import { HelpTip } from "@/components/ui/help-tip"; import type { DraftProposal } from "@/lib/api/prompter"; import type { StartRoute } from "@/hooks/use-prompter"; @@ -116,7 +117,13 @@ export function DraftProposalCard({ {distinctTeams.length > 0 && (

- {distinctTeams.length > 1 ? "Board-led across" : "Cell:"} + {distinctTeams.length > 1 ? ( + + Board-led across + + ) : ( + "Cell:" + )} {distinctTeams.map((team) => ( @@ -164,32 +171,36 @@ export function DraftProposalCard({ Keep chatting {/* Board review & Start → PENDING, assigned to PO + HoM for review */} - + + + {/* Approve & Start → PENDING, straight to Main PM (skip the board) */} - + + + ); diff --git a/panel/src/components/prompter/intake-form.tsx b/panel/src/components/prompter/intake-form.tsx index ba6e0f88..28fe1361 100644 --- a/panel/src/components/prompter/intake-form.tsx +++ b/panel/src/components/prompter/intake-form.tsx @@ -17,6 +17,7 @@ import { import { useProjects } from "@/hooks/use-projects"; import { useProducts } from "@/hooks/use-products"; import type { TargetKind } from "@/hooks/use-prompter"; +import { HelpTip } from "@/components/ui/help-tip"; interface IntakeFormProps { targetKind: TargetKind; @@ -111,14 +112,23 @@ export function IntakeForm({ onValueChange={(v) => onTargetKind(v as TargetKind)} > + {/* Tooltip goes on the inner span, not TabsTrigger itself — + TooltipTrigger's asChild merge would clobber the trigger's + own data-state and break the active-tab highlight. */} - Single cell + + Single cell + - Board-led + + Board-led + - MegaTask + + MegaTask + @@ -229,20 +239,36 @@ export function IntakeForm({ />
- + + + + {isPreparing && (
diff --git a/panel/src/components/prompter/success-card.tsx b/panel/src/components/prompter/success-card.tsx index d1a0926a..6e9b77d0 100644 --- a/panel/src/components/prompter/success-card.tsx +++ b/panel/src/components/prompter/success-card.tsx @@ -11,6 +11,7 @@ import { CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; +import { HelpTip } from "@/components/ui/help-tip"; import type { Team } from "@/types"; interface SuccessCardProps { @@ -43,9 +44,11 @@ export function SuccessCard({ {team.replace("_", " ")} - - ID: {taskId.slice(0, 8)}… - + + + ID: {taskId.slice(0, 8)}… + +
diff --git a/panel/src/components/tasks/__tests__/create-task-dialog.test.tsx b/panel/src/components/tasks/__tests__/create-task-dialog.test.tsx index 577bb855..273b756b 100644 --- a/panel/src/components/tasks/__tests__/create-task-dialog.test.tsx +++ b/panel/src/components/tasks/__tests__/create-task-dialog.test.tsx @@ -1,5 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; const { mutateAsync } = vi.hoisted(() => ({ mutateAsync: vi.fn().mockResolvedValue(undefined), @@ -204,3 +205,19 @@ describe("CreateTaskDialog — project/product mutual exclusivity (F085)", () => expect(payload.project_id).toBeUndefined(); }); }); + +describe("CreateTaskDialog — Task Type tooltip (W9-5 follow-up)", () => { + it("explains what the selected task type produces on hover", async () => { + const user = userEvent.setup(); + render(); + fireEvent.click(screen.getByRole("button", { name: /New Task/i })); + + // Task Type defaults to CODE; the Collapsible mock renders Advanced + // Options open, so the field is reachable without a pointer toggle. + await user.hover(screen.getByText("Task Type")); + + expect(await screen.findByRole("tooltip")).toHaveTextContent( + /source code changes/i, + ); + }); +}); diff --git a/panel/src/components/tasks/approve-and-start-button.tsx b/panel/src/components/tasks/approve-and-start-button.tsx index 3503b077..ed24edd8 100644 --- a/panel/src/components/tasks/approve-and-start-button.tsx +++ b/panel/src/components/tasks/approve-and-start-button.tsx @@ -19,6 +19,7 @@ import { Label } from "@/components/ui/label"; import { Rocket } from "lucide-react"; import type { Task } from "@/types"; import { toast } from "sonner"; +import { HelpTip } from "@/components/ui/help-tip"; interface ApproveAndStartButtonProps { task: Task; @@ -124,9 +125,11 @@ export function ApproveAndStartButton({ task }: ApproveAndStartButtonProps) {
- + + +