mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
W9-5: Tooltip sweep — HelpTip helper + per-view decode (#533)
* [W9-5a] Tooltip sweep foundation: HelpTip helper + shared cryptic badges HelpTip: DRY wrapper over the verbose 3-element Radix Tooltip pattern so a broad sweep stays a one-line wrap per site (falsy label short-circuits to the bare child). Unit-tested (3 cases). TaskStatusBadge + AgentStateBadge: the panel's most cryptic, most-frequent elements (15 task lifecycle states, 11 agent states) had no explanation anywhere. Add a per-state tooltip via HelpTip, with the canonical text in one description map and exported as taskStatusDescription / agentStateDescription so the per-view inline renderers (kanban, task header) reuse it instead of re-declaring. This is part 1 of the W9-5 tooltip sweep; the per-view inline surfaces follow in subsequent PRs. * [W9-5b] Per-view tooltip sweep: decode cryptic badges, icon-only buttons, status dots 35 HelpTip additions across 22 panel components, reusing the W9-5a helper plus taskStatusDescription/agentStateDescription. Tipped: task-id/commit-hash/branch/PR badges, severity/origin/status badges, priority (P0-P3), migration/shared flags, MegaTask umbrella badge, Review Gate / For Resumption / Confidential note badges, icon-only view/delete/edit/clear/show-hide buttons, semver bump + gate-state badges, ahead-not-pushed badge. Skipped self-explanatory labeled buttons and elements already carrying title=. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Plus, Trash2, Edit3, Check, X } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import { CollapsibleSection } from "./collapsible-section";
|
||||
|
||||
interface AcceptanceCriteriaProps {
|
||||
@@ -257,22 +258,26 @@ export function AcceptanceCriteria({ task }: AcceptanceCriteriaProps) {
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => startEditing(idx)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<Edit3 className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleDeleteCriterion(idx)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
<HelpTip label="Edit this criterion">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => startEditing(idx)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<Edit3 className="h-3 w-3" />
|
||||
</Button>
|
||||
</HelpTip>
|
||||
<HelpTip label="Delete this criterion">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleDeleteCriterion(idx)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@@ -9,6 +9,8 @@ import { ListTree, ExternalLink, Plus } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getAgentDisplayName } from "@/lib/agent-utils";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import { taskStatusDescription } from "@/components/tasks/task-status-badge";
|
||||
|
||||
interface SubtasksListProps {
|
||||
task: Task;
|
||||
@@ -128,12 +130,14 @@ export function SubtasksList({ task }: SubtasksListProps) {
|
||||
>
|
||||
<div className="flex items-center justify-between p-3 rounded-lg border hover:bg-muted/50 transition-colors">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`text-xs shrink-0 ${STATUS_COLORS[subtask.status]}`}
|
||||
>
|
||||
{subtask.status.replace(/_/g, " ")}
|
||||
</Badge>
|
||||
<HelpTip label={taskStatusDescription(subtask.status)}>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`text-xs shrink-0 ${STATUS_COLORS[subtask.status]}`}
|
||||
>
|
||||
{subtask.status.replace(/_/g, " ")}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
<span className="text-sm truncate">{subtask.title}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { getAgentDisplayName } from "@/lib/agent-utils";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
interface TabCommitsProps {
|
||||
task: Task;
|
||||
@@ -122,24 +123,28 @@ export function TabCommits({ task }: TabCommitsProps) {
|
||||
</CardTitle>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{task.branch_name && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="gap-1.5 py-1 px-2.5 font-mono text-sm"
|
||||
>
|
||||
<GitBranch className="h-4 w-4" />
|
||||
{task.branch_name}
|
||||
</Badge>
|
||||
<HelpTip label="Git branch this task is being worked on">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="gap-1.5 py-1 px-2.5 font-mono text-sm"
|
||||
>
|
||||
<GitBranch className="h-4 w-4" />
|
||||
{task.branch_name}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
{task.pr_url && (
|
||||
<a href={task.pr_url} target="_blank" rel="noopener noreferrer">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="gap-1.5 py-1 px-2.5 hover:bg-secondary/80"
|
||||
>
|
||||
PR #{task.pr_number}
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</Badge>
|
||||
</a>
|
||||
<HelpTip label="Pull Request — click to open on GitHub">
|
||||
<a href={task.pr_url} target="_blank" rel="noopener noreferrer">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="gap-1.5 py-1 px-2.5 hover:bg-secondary/80"
|
||||
>
|
||||
PR #{task.pr_number}
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</Badge>
|
||||
</a>
|
||||
</HelpTip>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
@@ -243,12 +248,14 @@ export function TabCommits({ task }: TabCommitsProps) {
|
||||
{/* Meta info */}
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
{/* Hash */}
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="font-mono text-xs"
|
||||
>
|
||||
{commit.hash.slice(0, 7)}
|
||||
</Badge>
|
||||
<HelpTip label="Short git commit hash (first 7 characters)">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="font-mono text-xs"
|
||||
>
|
||||
{commit.hash.slice(0, 7)}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
|
||||
{/* Author */}
|
||||
{commit.author_agent_id && (
|
||||
@@ -266,14 +273,16 @@ export function TabCommits({ task }: TabCommitsProps) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleDelete(commit.hash)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
<HelpTip label="Unlink this commit from the task">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleDelete(commit.hash)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { toast } from "sonner";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
interface TabDependenciesProps {
|
||||
task: Task;
|
||||
@@ -155,27 +156,31 @@ function DependencyList({
|
||||
className={`flex items-center gap-2 p-3 rounded-lg ${itemBorderClass} ${itemBgClass} transition-colors`}
|
||||
>
|
||||
<Link2 className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<Link
|
||||
href={`/tasks/${depId}`}
|
||||
className="flex-1"
|
||||
prefetch={false}
|
||||
>
|
||||
<span className="font-mono text-sm hover:underline">
|
||||
{depId.slice(0, 8)}...
|
||||
</span>
|
||||
</Link>
|
||||
<HelpTip label="Task ID (first 8 characters) — click to open">
|
||||
<Link
|
||||
href={`/tasks/${depId}`}
|
||||
className="flex-1"
|
||||
prefetch={false}
|
||||
>
|
||||
<span className="font-mono text-sm hover:underline">
|
||||
{depId.slice(0, 8)}...
|
||||
</span>
|
||||
</Link>
|
||||
</HelpTip>
|
||||
<Badge variant="outline" className={badgeClass}>
|
||||
{badgeLabel}
|
||||
</Badge>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemove(depId)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity text-destructive hover:text-destructive"
|
||||
disabled={updateTask.isPending}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
<HelpTip label="Remove this dependency">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemove(depId)}
|
||||
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity text-destructive hover:text-destructive"
|
||||
disabled={updateTask.isPending}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
Plus,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
interface TabNotesProps {
|
||||
task: Task;
|
||||
@@ -47,9 +48,11 @@ function prReviewBadge(task: Task): React.ReactNode {
|
||||
)?.pr_review?.verdict;
|
||||
if (!verdict) {
|
||||
return (
|
||||
<Badge variant="outline" className="ml-2 text-teal-600 border-teal-300">
|
||||
Review Gate
|
||||
</Badge>
|
||||
<HelpTip label="PR review has not been submitted yet">
|
||||
<Badge variant="outline" className="ml-2 text-teal-600 border-teal-300">
|
||||
Review Gate
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
);
|
||||
}
|
||||
const map: Record<string, { label: string; cls: string }> = {
|
||||
@@ -345,9 +348,11 @@ export function TabNotes({ task }: TabNotesProps) {
|
||||
title="Quick Context"
|
||||
icon={<FileText className="h-5 w-5" />}
|
||||
badge={
|
||||
<Badge variant="outline" className="ml-2">
|
||||
For Resumption
|
||||
</Badge>
|
||||
<HelpTip label="Short context notes for quickly resuming work after an interruption">
|
||||
<Badge variant="outline" className="ml-2">
|
||||
For Resumption
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
}
|
||||
bgClass="bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800"
|
||||
/>
|
||||
@@ -420,12 +425,14 @@ export function TabNotes({ task }: TabNotesProps) {
|
||||
title="Auditor Notes"
|
||||
icon={<Shield className="h-5 w-5" />}
|
||||
badge={
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="ml-2 text-purple-600 border-purple-300"
|
||||
>
|
||||
Confidential
|
||||
</Badge>
|
||||
<HelpTip label="Visible only to the Auditor and CEO">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="ml-2 text-purple-600 border-purple-300"
|
||||
>
|
||||
Confidential
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
}
|
||||
bgClass="bg-purple-50 dark:bg-purple-950 border border-purple-200 dark:border-purple-800"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TaskStatus } from "@/types";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
const statusColors: Record<TaskStatus, string> = {
|
||||
[TaskStatus.BACKLOG]: "bg-slate-500",
|
||||
@@ -19,14 +20,44 @@ const statusColors: Record<TaskStatus, string> = {
|
||||
[TaskStatus.CANCELLED]: "bg-gray-400",
|
||||
};
|
||||
|
||||
const statusDescriptions: Record<TaskStatus, string> = {
|
||||
[TaskStatus.BACKLOG]: "PM setup phase — dependencies or session not ready yet.",
|
||||
[TaskStatus.PENDING]: "Ready for work — the orchestrator can spawn an agent.",
|
||||
[TaskStatus.CLAIMED]: "An agent has locked this task.",
|
||||
[TaskStatus.IN_PROGRESS]: "Active development in progress.",
|
||||
[TaskStatus.BLOCKED]: "An external dependency is blocking progress.",
|
||||
[TaskStatus.PAUSED]: "Temporarily stopped; can resume.",
|
||||
[TaskStatus.VERIFYING]: "The developer is self-verifying their work.",
|
||||
[TaskStatus.NEEDS_REVISION]:
|
||||
"QA / PR-review / PM / CEO requested changes — back with the dev.",
|
||||
[TaskStatus.AWAITING_QA]: "Submitted for QA review (PR already open).",
|
||||
[TaskStatus.AWAITING_DOCUMENTATION]:
|
||||
"Documentation phase — the documenter is writing docs.",
|
||||
[TaskStatus.AWAITING_PR_REVIEW]:
|
||||
"PR-review gate: a reviewer checks the assembled PR before the PM merges.",
|
||||
[TaskStatus.AWAITING_PM_REVIEW]: "Docs complete; the PM is reviewing and merging.",
|
||||
[TaskStatus.AWAITING_CEO_APPROVAL]: "Escalated for the CEO's final approval.",
|
||||
[TaskStatus.COMPLETED]: "Terminal — work done and merged.",
|
||||
[TaskStatus.CANCELLED]: "Terminal — work cancelled.",
|
||||
};
|
||||
|
||||
interface TaskStatusBadgeProps {
|
||||
status: TaskStatus;
|
||||
}
|
||||
|
||||
/** Plain-language explanation for a task lifecycle state. Reused by the
|
||||
* shared badge and by inline status renderers (kanban, task header) so the
|
||||
* canonical text lives in one place. Empty for an unknown status. */
|
||||
export function taskStatusDescription(status: TaskStatus): string {
|
||||
return statusDescriptions[status] ?? "";
|
||||
}
|
||||
|
||||
export function TaskStatusBadge({ status }: TaskStatusBadgeProps) {
|
||||
return (
|
||||
<Badge className={`${statusColors[status] ?? "bg-slate-600"} text-white`}>
|
||||
{status.replace(/_/g, " ")}
|
||||
</Badge>
|
||||
<HelpTip label={statusDescriptions[status]}>
|
||||
<Badge className={`${statusColors[status] ?? "bg-slate-600"} text-white`}>
|
||||
{status.replace(/_/g, " ")}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
ResponsiveTableCardEmpty,
|
||||
} from "@/components/ui/responsive-table";
|
||||
import { TaskStatusBadge } from "./task-status-badge";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import { TaskActions } from "./task-actions";
|
||||
import { GitStatusBadge } from "./git-status-badge";
|
||||
import Link from "next/link";
|
||||
@@ -614,12 +615,14 @@ export function TaskTable({
|
||||
{task.title}
|
||||
</span>
|
||||
{task.batch_id && !task.parent_task_id && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-xs shrink-0 border-primary/50 text-primary"
|
||||
>
|
||||
MegaTask
|
||||
</Badge>
|
||||
<HelpTip label="A multi-task batch — this is the umbrella task for a set of related tasks">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-xs shrink-0 border-primary/50 text-primary"
|
||||
>
|
||||
MegaTask
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
{childCount > 0 && (
|
||||
<Badge
|
||||
@@ -744,12 +747,14 @@ export function TaskTable({
|
||||
childCount > 0 ? (
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{task.batch_id && !task.parent_task_id && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-primary/50 text-xs text-primary"
|
||||
>
|
||||
MegaTask
|
||||
</Badge>
|
||||
<HelpTip label="A multi-task batch — this is the umbrella task for a set of related tasks">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-primary/50 text-xs text-primary"
|
||||
>
|
||||
MegaTask
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
{childCount > 0 && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
|
||||
Reference in New Issue
Block a user