mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(panel): dense tooltip pass — org surfaces + layout chrome (253 tips)
Every sidebar destination, metric derivation, sortable column, queue action, and dialog control explains itself; mobile tab bar shares the sidebar's descriptions so the two nav surfaces can't drift.
This commit is contained in:
@@ -44,12 +44,16 @@ export function ActiveBlockersPanel({
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-red-500" />
|
||||
Active Blockers
|
||||
</CardTitle>
|
||||
<HelpTip label="Tasks in BLOCKED status, oldest first">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-red-500" />
|
||||
Active Blockers
|
||||
</CardTitle>
|
||||
</HelpTip>
|
||||
{blockedTasks.length > 0 && (
|
||||
<Badge variant="destructive">{blockedTasks.length}</Badge>
|
||||
<HelpTip label="Shown below — capped at 5, there may be more">
|
||||
<Badge variant="destructive">{blockedTasks.length}</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -77,16 +81,20 @@ export function ActiveBlockersPanel({
|
||||
Task #{task.id.slice(0, 8)}
|
||||
</span>
|
||||
</HelpTip>
|
||||
<Badge variant="outline" className="text-xs capitalize">
|
||||
{task.team.replace(/_/g, " ")}
|
||||
</Badge>
|
||||
<HelpTip label="Team currently responsible for this task">
|
||||
<Badge variant="outline" className="text-xs capitalize">
|
||||
{task.team.replace(/_/g, " ")}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
</div>
|
||||
<p className="text-sm truncate">{task.title}</p>
|
||||
<div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
|
||||
<Clock className="h-3 w-3" />
|
||||
Blocked for{" "}
|
||||
{formatDuration(task.updated_at ?? task.created_at)}
|
||||
</div>
|
||||
<HelpTip label="Time since this task's last status update (or creation, if never updated)">
|
||||
<div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
|
||||
<Clock className="h-3 w-3" />
|
||||
Blocked for{" "}
|
||||
{formatDuration(task.updated_at ?? task.created_at)}
|
||||
</div>
|
||||
</HelpTip>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -95,10 +103,12 @@ export function ActiveBlockersPanel({
|
||||
)}
|
||||
<div className="mt-4 pt-3 border-t">
|
||||
<Link href="/tasks?status=blocked" prefetch={false}>
|
||||
<Button variant="ghost" size="sm" className="w-full">
|
||||
View All Blocked
|
||||
<ArrowRight className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
<HelpTip label="The complete blocked-task list — not capped to 5">
|
||||
<Button variant="ghost" size="sm" className="w-full">
|
||||
View All Blocked
|
||||
<ArrowRight className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Clock,
|
||||
} from "lucide-react";
|
||||
import { getAgentDisplayName } from "@/lib/agent-utils";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
export interface Activity {
|
||||
id: string;
|
||||
@@ -66,9 +67,11 @@ export function ActivityItem({ activity }: ActivityItemProps) {
|
||||
<div className="mt-0.5">{icon}</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm">
|
||||
<span className="font-medium">
|
||||
{getAgentDisplayName(activity.agent_id)}
|
||||
</span>{" "}
|
||||
<HelpTip label={`Agent slug: ${activity.agent_id}`}>
|
||||
<span className="font-medium">
|
||||
{getAgentDisplayName(activity.agent_id)}
|
||||
</span>
|
||||
</HelpTip>{" "}
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
{activity.task_title && (
|
||||
<>
|
||||
@@ -77,9 +80,17 @@ export function ActivityItem({ activity }: ActivityItemProps) {
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{activity.timestamp ? formatTime(activity.timestamp) : "N/A"}
|
||||
</span>
|
||||
<HelpTip
|
||||
label={
|
||||
activity.timestamp
|
||||
? `Exact time: ${new Date(activity.timestamp).toLocaleString()}`
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{activity.timestamp ? formatTime(activity.timestamp) : "N/A"}
|
||||
</span>
|
||||
</HelpTip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Shield, ArrowRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
interface AuditorAlertsPanelProps {
|
||||
alerts: AuditorFlag[] | undefined;
|
||||
@@ -49,12 +50,16 @@ export function AuditorAlertsPanel({
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Shield className="h-5 w-5" />
|
||||
Auditor Alerts
|
||||
</CardTitle>
|
||||
<HelpTip label="Unresolved flags raised by the silent Auditor role, most severe first">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Shield className="h-5 w-5" />
|
||||
Auditor Alerts
|
||||
</CardTitle>
|
||||
</HelpTip>
|
||||
{unresolvedAlerts.length > 0 && (
|
||||
<Badge variant="destructive">{unresolvedAlerts.length}</Badge>
|
||||
<HelpTip label="Shown below — capped at 5, there may be more">
|
||||
<Badge variant="destructive">{unresolvedAlerts.length}</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -77,17 +82,33 @@ export function AuditorAlertsPanel({
|
||||
key={alert.id}
|
||||
className="flex items-start gap-3 p-3 rounded-lg border bg-muted/30"
|
||||
>
|
||||
<span className="text-lg">{severityEmoji[alert.severity]}</span>
|
||||
<HelpTip
|
||||
label={`Severity: ${alert.severity}${alert.category ? ` — ${alert.category}` : ""}`}
|
||||
>
|
||||
<span className="text-lg">
|
||||
{severityEmoji[alert.severity]}
|
||||
</span>
|
||||
</HelpTip>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-medium text-sm truncate">
|
||||
{alert.title}
|
||||
</span>
|
||||
<Badge
|
||||
className={severityColors[alert.severity] + " text-xs"}
|
||||
<HelpTip
|
||||
label={
|
||||
alert.severity === "urgent"
|
||||
? "Urgent — needs attention now"
|
||||
: alert.severity === "warning"
|
||||
? "Warning — worth a look soon"
|
||||
: "Info — no action required"
|
||||
}
|
||||
>
|
||||
{alert.severity}
|
||||
</Badge>
|
||||
<Badge
|
||||
className={severityColors[alert.severity] + " text-xs"}
|
||||
>
|
||||
{alert.severity}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground line-clamp-1">
|
||||
{alert.description}
|
||||
@@ -99,10 +120,12 @@ export function AuditorAlertsPanel({
|
||||
)}
|
||||
<div className="mt-4 pt-3 border-t">
|
||||
<Link href="/auditor" prefetch={false}>
|
||||
<Button variant="ghost" size="sm" className="w-full">
|
||||
View All Flags
|
||||
<ArrowRight className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
<HelpTip label="Full Auditor report, including resolved flags">
|
||||
<Button variant="ghost" size="sm" className="w-full">
|
||||
View All Flags
|
||||
<ArrowRight className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -225,7 +225,9 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) {
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{getPriorityBadge(task.priority)}
|
||||
<Badge variant="outline">{task.team}</Badge>
|
||||
<HelpTip label="Team this task belongs to">
|
||||
<Badge variant="outline">{task.team}</Badge>
|
||||
</HelpTip>
|
||||
</div>
|
||||
<Link
|
||||
prefetch={false}
|
||||
@@ -250,33 +252,39 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) {
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => openDialog(task, "reject")}
|
||||
>
|
||||
<XCircle className="h-4 w-4 mr-1" />
|
||||
Reject
|
||||
</Button>
|
||||
<HelpTip label="Sends this task back to needs_revision with your reason">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => openDialog(task, "reject")}
|
||||
>
|
||||
<XCircle className="h-4 w-4 mr-1" />
|
||||
Reject
|
||||
</Button>
|
||||
</HelpTip>
|
||||
{kind === "start" ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
onClick={() => openDialog(task, "start")}
|
||||
>
|
||||
<Rocket className="h-4 w-4 mr-1" />
|
||||
Approve & Start
|
||||
</Button>
|
||||
<HelpTip label="Hands the task to the Main PM to delegate to the cells">
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
onClick={() => openDialog(task, "start")}
|
||||
>
|
||||
<Rocket className="h-4 w-4 mr-1" />
|
||||
Approve & Start
|
||||
</Button>
|
||||
</HelpTip>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
onClick={() => openDialog(task, "approve")}
|
||||
>
|
||||
<CheckCircle2 className="h-4 w-4 mr-1" />
|
||||
Approve
|
||||
</Button>
|
||||
<HelpTip label="Merges the PR and marks the task completed">
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
onClick={() => openDialog(task, "approve")}
|
||||
>
|
||||
<CheckCircle2 className="h-4 w-4 mr-1" />
|
||||
Approve
|
||||
</Button>
|
||||
</HelpTip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -290,9 +298,11 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) {
|
||||
<Clock className="h-5 w-5" />
|
||||
CEO Approval Queue
|
||||
{totalCount > 0 && (
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{totalCount}
|
||||
</Badge>
|
||||
<HelpTip label="Ready-to-start plus final-approval tasks combined">
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{totalCount}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
</CardTitle>
|
||||
<CardDescription>Tasks waiting on your decision</CardDescription>
|
||||
@@ -307,17 +317,21 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) {
|
||||
<div className="space-y-5">
|
||||
{readyToStart.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Ready to start · board reviewed
|
||||
</p>
|
||||
<HelpTip label="Board finished reviewing; still pending until you approve & start">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Ready to start · board reviewed
|
||||
</p>
|
||||
</HelpTip>
|
||||
{readyToStart.map((task) => renderRow(task, "start"))}
|
||||
</div>
|
||||
)}
|
||||
{pendingTasks.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Final approval · work complete
|
||||
</p>
|
||||
<HelpTip label="Delivery work is done; this is the last gate before merge">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Final approval · work complete
|
||||
</p>
|
||||
</HelpTip>
|
||||
{pendingTasks.map((task) => renderRow(task, "approve"))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Settings, AlertCircle } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
const SETTINGS_LABEL = "Open settings";
|
||||
|
||||
@@ -137,7 +138,11 @@ export function CommandCenter() {
|
||||
|
||||
{/* Section 1: Quick Actions + the four key cards */}
|
||||
<section>
|
||||
<h2 className="text-lg font-semibold mb-4">Quick Actions</h2>
|
||||
<HelpTip label="One-click shortcuts to the pages you visit most often">
|
||||
<h2 className="text-lg font-semibold mb-4 inline-block">
|
||||
Quick Actions
|
||||
</h2>
|
||||
</HelpTip>
|
||||
<QuickActionsBar />
|
||||
</section>
|
||||
|
||||
@@ -153,7 +158,11 @@ export function CommandCenter() {
|
||||
|
||||
{/* Section 2: Team Health (team cards + Task Intake + Secretary) */}
|
||||
<section>
|
||||
<h2 className="text-lg font-semibold mb-4">Team Health</h2>
|
||||
<HelpTip label="Per-team blocked ratio and throughput, plus the on-demand agents">
|
||||
<h2 className="text-lg font-semibold mb-4 inline-block">
|
||||
Team Health
|
||||
</h2>
|
||||
</HelpTip>
|
||||
<TeamHealthCards
|
||||
teams={overview?.health_status}
|
||||
isLoading={loadingOverview}
|
||||
|
||||
@@ -61,7 +61,9 @@ export function KeyMetricsPanel({ metrics, isLoading }: KeyMetricsProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg">Key Metrics</CardTitle>
|
||||
<HelpTip label="Org-wide 7-day delivery snapshot from DashboardService">
|
||||
<CardTitle className="text-lg">Key Metrics</CardTitle>
|
||||
</HelpTip>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { BookOpen, CheckCircle2, XCircle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
|
||||
const _MIN_REASON = 4;
|
||||
|
||||
@@ -89,9 +90,15 @@ export function PlaybookReviewQueue({ className }: { className?: string }) {
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BookOpen className="h-5 w-5" />
|
||||
Playbook Review
|
||||
<Badge variant="secondary">{drafts.length}</Badge>
|
||||
<HelpTip label="Procedures agents drafted from real task runs, awaiting your curation call">
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<BookOpen className="h-5 w-5" />
|
||||
Playbook Review
|
||||
</span>
|
||||
</HelpTip>
|
||||
<HelpTip label="Drafts awaiting a decision">
|
||||
<Badge variant="secondary">{drafts.length}</Badge>
|
||||
</HelpTip>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Drafted playbooks awaiting your approval — approved ones are indexed
|
||||
@@ -106,11 +113,17 @@ export function PlaybookReviewQueue({ className }: { className?: string }) {
|
||||
>
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<span className="font-medium">{pb.title}</span>
|
||||
{pb.team && <Badge variant="outline">{pb.team}</Badge>}
|
||||
{pb.team && (
|
||||
<HelpTip label="Team that authored this playbook">
|
||||
<Badge variant="outline">{pb.team}</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
{pb.tags.map((t) => (
|
||||
<Badge key={t} variant="secondary" className="text-xs">
|
||||
{t}
|
||||
</Badge>
|
||||
<HelpTip key={t} label="Search/filter tag for this playbook">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{t}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -120,27 +133,31 @@ export function PlaybookReviewQueue({ className }: { className?: string }) {
|
||||
{pb.procedure}
|
||||
</pre>
|
||||
<div className="mt-3 flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => setRejecting(pb)}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
disabled={
|
||||
approveMutation.isPending &&
|
||||
approveMutation.variables === pb.id
|
||||
}
|
||||
onClick={() => approveMutation.mutate(pb.id)}
|
||||
>
|
||||
<CheckCircle2 className="mr-1 h-4 w-4" />
|
||||
Approve
|
||||
</Button>
|
||||
<HelpTip label="Archives this draft — it is never indexed">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => setRejecting(pb)}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
</HelpTip>
|
||||
<HelpTip label="Indexes this playbook so it's auto-suggested to agents">
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
disabled={
|
||||
approveMutation.isPending &&
|
||||
approveMutation.variables === pb.id
|
||||
}
|
||||
onClick={() => approveMutation.mutate(pb.id)}
|
||||
>
|
||||
<CheckCircle2 className="mr-1 h-4 w-4" />
|
||||
Approve
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import {
|
||||
GitPullRequest,
|
||||
ExternalLink,
|
||||
@@ -139,9 +140,11 @@ export function PrReviewQueue({ className }: PrReviewQueueProps) {
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<GitPullRequest className="h-5 w-5" />
|
||||
PR Reviews
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{items.length}
|
||||
</Badge>
|
||||
<HelpTip label="External PRs in review or awaiting your call">
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{items.length}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
External PRs the org is reviewing or has reviewed — the reviewer
|
||||
@@ -175,14 +178,18 @@ export function PrReviewQueue({ className }: PrReviewQueueProps) {
|
||||
{task.title}
|
||||
</Link>
|
||||
{awaiting ? (
|
||||
<Badge variant="secondary" className="shrink-0">
|
||||
Awaiting your call
|
||||
</Badge>
|
||||
<HelpTip label="The reviewer finished — pick Supersede or Dismiss below">
|
||||
<Badge variant="secondary" className="shrink-0">
|
||||
Awaiting your call
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
) : (
|
||||
<Badge variant="outline" className="shrink-0 gap-1">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
Reviewing
|
||||
</Badge>
|
||||
<HelpTip label="The PR reviewer task hasn't reached completed yet">
|
||||
<Badge variant="outline" className="shrink-0 gap-1">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
Reviewing
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
</div>
|
||||
{task.description && (
|
||||
@@ -224,23 +231,27 @@ export function PrReviewQueue({ className }: PrReviewQueueProps) {
|
||||
</TooltipProvider>
|
||||
{awaiting && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => open(task, "dismiss")}
|
||||
>
|
||||
<XCircle className="h-4 w-4 mr-1" />
|
||||
Dismiss
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
onClick={() => open(task, "supersede")}
|
||||
>
|
||||
<Rocket className="h-4 w-4 mr-1" />
|
||||
Supersede
|
||||
</Button>
|
||||
<HelpTip label="Drops this from the queue — the review stays on the GitHub PR">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => open(task, "dismiss")}
|
||||
>
|
||||
<XCircle className="h-4 w-4 mr-1" />
|
||||
Dismiss
|
||||
</Button>
|
||||
</HelpTip>
|
||||
<HelpTip label="The org takes the contribution over and finishes it">
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
onClick={() => open(task, "supersede")}
|
||||
>
|
||||
<Rocket className="h-4 w-4 mr-1" />
|
||||
Supersede
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CreateTaskDialog } from "@/components/tasks/create-task-dialog";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import { Users, BookOpen, Shield, Sparkles, Bot } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -11,38 +12,48 @@ export function QuickActionsBar() {
|
||||
<CreateTaskDialog />
|
||||
|
||||
<Link href="/agents" prefetch={false}>
|
||||
<Button variant="outline">
|
||||
<Users className="h-4 w-4 mr-2" />
|
||||
Spawn Agent
|
||||
</Button>
|
||||
<HelpTip label="Agents page — view the roster and spawn a new agent run">
|
||||
<Button variant="outline">
|
||||
<Users className="h-4 w-4 mr-2" />
|
||||
Spawn Agent
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
|
||||
<Link href="/prompter" prefetch={false}>
|
||||
<Button variant="outline">
|
||||
<Sparkles className="h-4 w-4 mr-2" />
|
||||
Task Intake
|
||||
</Button>
|
||||
<HelpTip label="Chat-based interview to draft and submit a new task">
|
||||
<Button variant="outline">
|
||||
<Sparkles className="h-4 w-4 mr-2" />
|
||||
Task Intake
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
|
||||
<Link href="/business?tab=secretary" prefetch={false}>
|
||||
<Button variant="outline">
|
||||
<Bot className="h-4 w-4 mr-2" />
|
||||
Secretary
|
||||
</Button>
|
||||
<HelpTip label="Chat with the Secretary — company state and gated CEO directives">
|
||||
<Button variant="outline">
|
||||
<Bot className="h-4 w-4 mr-2" />
|
||||
Secretary
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
|
||||
<Link href="/journals" prefetch={false}>
|
||||
<Button variant="outline">
|
||||
<BookOpen className="h-4 w-4 mr-2" />
|
||||
View Journals
|
||||
</Button>
|
||||
<HelpTip label="Browse agent journal entries and learnings">
|
||||
<Button variant="outline">
|
||||
<BookOpen className="h-4 w-4 mr-2" />
|
||||
View Journals
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
|
||||
<Link href="/auditor" prefetch={false}>
|
||||
<Button variant="outline">
|
||||
<Shield className="h-4 w-4 mr-2" />
|
||||
Auditor Report
|
||||
</Button>
|
||||
<HelpTip label="The Auditor's flagged issues and reports">
|
||||
<Button variant="outline">
|
||||
<Shield className="h-4 w-4 mr-2" />
|
||||
Auditor Report
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import { Activity as ActivityIcon, ArrowRight } from "lucide-react";
|
||||
import { ActivityItem, Activity } from "./activity-item";
|
||||
import Link from "next/link";
|
||||
@@ -20,10 +21,12 @@ export function RecentActivityFeed({
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<ActivityIcon className="h-5 w-5" />
|
||||
Recent Activity
|
||||
</CardTitle>
|
||||
<HelpTip label="The 10 most recent agent actions from the last 24 hours">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<ActivityIcon className="h-5 w-5" />
|
||||
Recent Activity
|
||||
</CardTitle>
|
||||
</HelpTip>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
@@ -48,10 +51,12 @@ export function RecentActivityFeed({
|
||||
)}
|
||||
<div className="mt-4 pt-3 border-t">
|
||||
<Link href="/notifications" prefetch={false}>
|
||||
<Button variant="ghost" size="sm" className="w-full">
|
||||
View Full Activity
|
||||
<ArrowRight className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
<HelpTip label="Opens Notifications — the complete activity history">
|
||||
<Button variant="ghost" size="sm" className="w-full">
|
||||
View Full Activity
|
||||
<ArrowRight className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -172,7 +172,9 @@ export function ReleaseProposalCard({ className }: { className?: string }) {
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Rocket className="h-5 w-5" />
|
||||
Release Proposal
|
||||
<Badge variant="outline">v{report.proposed_version}</Badge>
|
||||
<HelpTip label="Next version number, computed from the commit classification below">
|
||||
<Badge variant="outline">v{report.proposed_version}</Badge>
|
||||
</HelpTip>
|
||||
<HelpTip label="Semver bump type — how the version number increases (major, minor, or patch)">
|
||||
<Badge variant="secondary">{report.bump_kind}</Badge>
|
||||
</HelpTip>
|
||||
@@ -208,18 +210,22 @@ export function ReleaseProposalCard({ className }: { className?: string }) {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Drafted CHANGELOG
|
||||
</p>
|
||||
<HelpTip label="Written by the release manager from conventional-commit messages since the last tag">
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Drafted CHANGELOG
|
||||
</p>
|
||||
</HelpTip>
|
||||
<pre className="max-h-60 overflow-auto rounded-md bg-muted p-3 text-xs whitespace-pre-wrap">
|
||||
{report.drafted_changelog}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Version bump plan ({report.version_bump_plan.length} files)
|
||||
</p>
|
||||
<HelpTip label="Files the executor will rewrite with the new version number on approve">
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Version bump plan ({report.version_bump_plan.length} files)
|
||||
</p>
|
||||
</HelpTip>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{report.version_bump_plan.join(", ")}
|
||||
</p>
|
||||
@@ -227,9 +233,11 @@ export function ReleaseProposalCard({ className }: { className?: string }) {
|
||||
|
||||
{report.migration_notes.length > 0 && (
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Migrations
|
||||
</p>
|
||||
<HelpTip label="Alembic migrations included in this release — check for a single head">
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Migrations
|
||||
</p>
|
||||
</HelpTip>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
{report.migration_notes.map((note, i) => (
|
||||
<li key={i}>{note}</li>
|
||||
@@ -272,25 +280,47 @@ export function ReleaseProposalCard({ className }: { className?: string }) {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col-reverse gap-2 pt-1 sm:flex-row sm:items-center sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => setAction("reject")}
|
||||
disabled={executeInFlight}
|
||||
<HelpTip
|
||||
label={
|
||||
executeInFlight
|
||||
? "Disabled while the execute is running"
|
||||
: "Cancels the proposal — a fresh assessment runs next cycle"
|
||||
}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject with changes
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
onClick={() => setAction("approve")}
|
||||
disabled={executeInFlight}
|
||||
<span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => setAction("reject")}
|
||||
disabled={executeInFlight}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject with changes
|
||||
</Button>
|
||||
</span>
|
||||
</HelpTip>
|
||||
<HelpTip
|
||||
label={
|
||||
executeInFlight
|
||||
? "Disabled while the execute is running"
|
||||
: "Runs the fail-closed executor: bump + CHANGELOG + gate + CI + publish"
|
||||
}
|
||||
>
|
||||
<CheckCircle2 className="mr-1 h-4 w-4" />
|
||||
{executeFailed ? "Retry approve & publish" : "Approve & publish"}
|
||||
</Button>
|
||||
<span>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
onClick={() => setAction("approve")}
|
||||
disabled={executeInFlight}
|
||||
>
|
||||
<CheckCircle2 className="mr-1 h-4 w-4" />
|
||||
{executeFailed
|
||||
? "Retry approve & publish"
|
||||
: "Approve & publish"}
|
||||
</Button>
|
||||
</span>
|
||||
</HelpTip>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -37,13 +37,19 @@ interface RejectTarget {
|
||||
function itemStatusBadge(item: RoadmapItem) {
|
||||
if (item.status === "approved") {
|
||||
return (
|
||||
<Badge variant="secondary" className="bg-green-600/10 text-green-700">
|
||||
Approved
|
||||
</Badge>
|
||||
<HelpTip label="Materialized as a BACKLOG task">
|
||||
<Badge variant="secondary" className="bg-green-600/10 text-green-700">
|
||||
Approved
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
);
|
||||
}
|
||||
if (item.status === "rejected") {
|
||||
return <Badge variant="outline">Rejected</Badge>;
|
||||
return (
|
||||
<HelpTip label="Recorded, not added to the backlog">
|
||||
<Badge variant="outline">Rejected</Badge>
|
||||
</HelpTip>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -95,24 +101,28 @@ function RoadmapItemRow({
|
||||
)}
|
||||
{isProposed && (
|
||||
<div className="mt-3 flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => onReject({ taskId, item })}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
disabled={approving}
|
||||
onClick={() => onApprove(taskId, item.id)}
|
||||
>
|
||||
<CheckCircle2 className="mr-1 h-4 w-4" />
|
||||
Approve
|
||||
</Button>
|
||||
<HelpTip label="Records your reason and feeds the next cycle's prompt — not added to the backlog">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => onReject({ taskId, item })}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
</HelpTip>
|
||||
<HelpTip label="Materializes this item as a BACKLOG task — needs normal PM activation to start">
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
disabled={approving}
|
||||
onClick={() => onApprove(taskId, item.id)}
|
||||
>
|
||||
<CheckCircle2 className="mr-1 h-4 w-4" />
|
||||
Approve
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -138,7 +148,9 @@ function RoadmapCycleCard({
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Map className="h-5 w-5" />
|
||||
Roadmap Cycle
|
||||
<Badge variant="secondary">{pending} pending</Badge>
|
||||
<HelpTip label="Items still awaiting your approve/reject decision">
|
||||
<Badge variant="secondary">{pending} pending</Badge>
|
||||
</HelpTip>
|
||||
</CardTitle>
|
||||
<CardDescription>{cycle.goal}</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
@@ -54,17 +54,23 @@ export function ScorecardOverviewPanel() {
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Trophy className="h-5 w-5" />
|
||||
Performance
|
||||
</CardTitle>
|
||||
<HelpTip label="Org-wide performance rollup for the last 30 days">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<Trophy className="h-5 w-5" />
|
||||
Performance
|
||||
</CardTitle>
|
||||
</HelpTip>
|
||||
<Link
|
||||
prefetch={false}
|
||||
href="/metrics?tab=scorecards"
|
||||
className="text-muted-foreground hover:text-foreground flex items-center gap-1 text-xs"
|
||||
>
|
||||
Scorecards
|
||||
<ArrowRight className="h-3 w-3" />
|
||||
<HelpTip label="Per-agent and per-team scorecards, full detail">
|
||||
<span className="flex items-center gap-1">
|
||||
Scorecards
|
||||
<ArrowRight className="h-3 w-3" />
|
||||
</span>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import { Share2 } from "lucide-react";
|
||||
|
||||
// Compact stand-in for the full X/video queues on the command center — the
|
||||
@@ -41,7 +42,11 @@ export function SocialSummaryCard({ className }: { className?: string }) {
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Share2 className="h-5 w-5" />
|
||||
Social
|
||||
{total > 0 && <Badge variant="secondary">{total}</Badge>}
|
||||
{total > 0 && (
|
||||
<HelpTip label="X + video drafts combined">
|
||||
<Badge variant="secondary">{total}</Badge>
|
||||
</HelpTip>
|
||||
)}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Held X and video drafts awaiting your approval.
|
||||
@@ -49,17 +54,23 @@ export function SocialSummaryCard({ className }: { className?: string }) {
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex gap-4 text-sm text-muted-foreground">
|
||||
<span>
|
||||
{xCount} X draft{xCount === 1 ? "" : "s"}
|
||||
</span>
|
||||
<span>
|
||||
{videoCount} video draft{videoCount === 1 ? "" : "s"}
|
||||
</span>
|
||||
<HelpTip label="Drafted release posts, feature spotlights, and mention replies awaiting review">
|
||||
<span>
|
||||
{xCount} X draft{xCount === 1 ? "" : "s"}
|
||||
</span>
|
||||
</HelpTip>
|
||||
<HelpTip label="Rendered video clips awaiting review">
|
||||
<span>
|
||||
{videoCount} video draft{videoCount === 1 ? "" : "s"}
|
||||
</span>
|
||||
</HelpTip>
|
||||
</div>
|
||||
<Link href="/social" prefetch={false}>
|
||||
<Button variant="outline" size="sm">
|
||||
Open Social
|
||||
</Button>
|
||||
<HelpTip label="Opens the full X and video queues plus posting history">
|
||||
<Button variant="outline" size="sm">
|
||||
Open Social
|
||||
</Button>
|
||||
</HelpTip>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { HelpTip } from "@/components/ui/help-tip";
|
||||
import { TrendingUp } from "lucide-react";
|
||||
|
||||
interface StrategySignalsPanelProps {
|
||||
@@ -29,10 +30,12 @@ export function StrategySignalsPanel({ className }: StrategySignalsPanelProps) {
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
Strategy Signals
|
||||
</CardTitle>
|
||||
<HelpTip label="Notable patterns the strategy engine detected across delivery data">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
Strategy Signals
|
||||
</CardTitle>
|
||||
</HelpTip>
|
||||
<CardDescription>Live signals from the strategy engine</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -55,9 +58,11 @@ export function StrategySignalsPanel({ className }: StrategySignalsPanelProps) {
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{signal.kind}
|
||||
</Badge>
|
||||
<HelpTip label="Signal category from the strategy engine">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{signal.kind}
|
||||
</Badge>
|
||||
</HelpTip>
|
||||
</div>
|
||||
<p className="font-medium text-sm">{signal.summary}</p>
|
||||
{signal.detail && (
|
||||
|
||||
@@ -34,35 +34,41 @@ export function TeamHealthCard({ health }: TeamHealthCardProps) {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{/* Active Tasks */}
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Users className="h-4 w-4" />
|
||||
Active
|
||||
<HelpTip label="Tasks this team currently has claimed or in progress">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Users className="h-4 w-4" />
|
||||
Active
|
||||
</div>
|
||||
<span className="font-medium">{health.active_tasks}</span>
|
||||
</div>
|
||||
<span className="font-medium">{health.active_tasks}</span>
|
||||
</div>
|
||||
</HelpTip>
|
||||
|
||||
{/* Blocked Tasks */}
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Blocked
|
||||
<HelpTip label="Tasks this team currently has in the blocked status">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
Blocked
|
||||
</div>
|
||||
<span
|
||||
className={`font-medium ${health.blocked_tasks > 0 ? "text-red-600" : ""}`}
|
||||
>
|
||||
{health.blocked_tasks}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
className={`font-medium ${health.blocked_tasks > 0 ? "text-red-600" : ""}`}
|
||||
>
|
||||
{health.blocked_tasks}
|
||||
</span>
|
||||
</div>
|
||||
</HelpTip>
|
||||
|
||||
{/* Completed This Week */}
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Completed (7d)
|
||||
<HelpTip label="Tasks this team completed in the last 7 days">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Completed (7d)
|
||||
</div>
|
||||
<span className="font-medium">{health.completed_this_week}</span>
|
||||
</div>
|
||||
<span className="font-medium">{health.completed_this_week}</span>
|
||||
</div>
|
||||
</HelpTip>
|
||||
|
||||
{/* Blocked Ratio */}
|
||||
{health.blocked_ratio > 0 && (
|
||||
|
||||
@@ -113,10 +113,12 @@ export function UsageOverviewPanel() {
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Coins className="h-5 w-5" />
|
||||
Token Usage & Cost
|
||||
</CardTitle>
|
||||
<HelpTip label="Live token/cost across all agent sessions, from /ws/system or a 24h HTTP poll fallback">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Coins className="h-5 w-5" />
|
||||
Token Usage & Cost
|
||||
</CardTitle>
|
||||
</HelpTip>
|
||||
<HelpTip label="Connection to the live /ws/system stream — Polling means it's down and figures refresh via periodic HTTP fetch instead">
|
||||
<Badge className={badge.className}>
|
||||
{badge.icon}
|
||||
|
||||
@@ -366,15 +366,17 @@ function VideoPostRow({
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => onReject(post)}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
<HelpTip label="Cancels this draft — it will not be posted">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => onReject(post)}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
</HelpTip>
|
||||
<HelpTip label={approveHint(approving, overLimit)}>
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -146,15 +146,17 @@ function XPostRow({
|
||||
</HelpTip>
|
||||
|
||||
<div className="mt-2 flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => onReject(post)}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
<HelpTip label="Cancels this draft — it will not be posted">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
onClick={() => onReject(post)}
|
||||
>
|
||||
<XCircle className="mr-1 h-4 w-4" />
|
||||
Reject
|
||||
</Button>
|
||||
</HelpTip>
|
||||
<HelpTip
|
||||
label={approveHint(approving, overLimit, body.trim().length === 0)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user