-
+
#{task.id.slice(0, 8)}
{" "}
{task.title}
@@ -163,7 +182,10 @@ export function KanbanCard({ task, onAction, showQaActions, isDragging: isDraggi
- {task.sessions.length} linked session{task.sessions.length !== 1 ? "s" : ""}
+
+ {task.sessions.length} linked session
+ {task.sessions.length !== 1 ? "s" : ""}
+
{primarySession && (
Primary: #{primarySession.channel_slug}
@@ -194,7 +216,10 @@ export function KanbanCard({ task, onAction, showQaActions, isDragging: isDraggi
Assign
- e.stopPropagation()}>
+ e.stopPropagation()}
+ >
Assign to agent
diff --git a/panel/src/components/kanban/core/kanban-column.tsx b/panel/src/components/kanban/core/kanban-column.tsx
index bb82a9bb..21518b34 100644
--- a/panel/src/components/kanban/core/kanban-column.tsx
+++ b/panel/src/components/kanban/core/kanban-column.tsx
@@ -48,8 +48,13 @@ export function KanbanColumn({
)}
>
-
{title}
-
+
+ {title}
+
+
{isLoading ? "..." : tasks.length}
@@ -71,7 +76,11 @@ export function KanbanColumn({
key={task.id}
task={task}
onAction={onAction}
- showQaActions={showQaActions && (status === TaskStatus.AWAITING_QA || status === TaskStatus.VERIFYING)}
+ showQaActions={
+ showQaActions &&
+ (status === TaskStatus.AWAITING_QA ||
+ status === TaskStatus.VERIFYING)
+ }
/>
))}
diff --git a/panel/src/components/kanban/views/dev-kanban.tsx b/panel/src/components/kanban/views/dev-kanban.tsx
index 7df1bd0c..fe480214 100644
--- a/panel/src/components/kanban/views/dev-kanban.tsx
+++ b/panel/src/components/kanban/views/dev-kanban.tsx
@@ -5,14 +5,54 @@ import { TaskStatus, Team } from "@/types";
import { KanbanBoard } from "../core/kanban-board";
const DEV_COLUMNS = [
- { id: "backlog", status: TaskStatus.BACKLOG, title: "Backlog", color: "bg-slate-50 dark:bg-slate-900" },
- { id: "pending", status: TaskStatus.PENDING, title: "Ready", color: "bg-gray-100 dark:bg-gray-800" },
- { id: "assigned", status: TaskStatus.CLAIMED, title: "Assigned", color: "bg-blue-50 dark:bg-blue-950" },
- { id: "in-progress", status: TaskStatus.IN_PROGRESS, title: "In Progress", color: "bg-blue-100 dark:bg-blue-900" },
- { id: "blocked", status: TaskStatus.BLOCKED, title: "Blocked", color: "bg-red-50 dark:bg-red-950" },
- { id: "verifying", status: TaskStatus.VERIFYING, title: "Verifying", color: "bg-purple-50 dark:bg-purple-950" },
- { id: "qa-review", status: TaskStatus.AWAITING_QA, title: "QA Review", color: "bg-yellow-50 dark:bg-yellow-950" },
- { id: "done", status: TaskStatus.COMPLETED, title: "Done", color: "bg-green-50 dark:bg-green-950" },
+ {
+ id: "backlog",
+ status: TaskStatus.BACKLOG,
+ title: "Backlog",
+ color: "bg-slate-50 dark:bg-slate-900",
+ },
+ {
+ id: "pending",
+ status: TaskStatus.PENDING,
+ title: "Ready",
+ color: "bg-gray-100 dark:bg-gray-800",
+ },
+ {
+ id: "assigned",
+ status: TaskStatus.CLAIMED,
+ title: "Assigned",
+ color: "bg-blue-50 dark:bg-blue-950",
+ },
+ {
+ id: "in-progress",
+ status: TaskStatus.IN_PROGRESS,
+ title: "In Progress",
+ color: "bg-blue-100 dark:bg-blue-900",
+ },
+ {
+ id: "blocked",
+ status: TaskStatus.BLOCKED,
+ title: "Blocked",
+ color: "bg-red-50 dark:bg-red-950",
+ },
+ {
+ id: "verifying",
+ status: TaskStatus.VERIFYING,
+ title: "Verifying",
+ color: "bg-purple-50 dark:bg-purple-950",
+ },
+ {
+ id: "qa-review",
+ status: TaskStatus.AWAITING_QA,
+ title: "QA Review",
+ color: "bg-yellow-50 dark:bg-yellow-950",
+ },
+ {
+ id: "done",
+ status: TaskStatus.COMPLETED,
+ title: "Done",
+ color: "bg-green-50 dark:bg-green-950",
+ },
];
interface DevKanbanProps {
diff --git a/panel/src/components/kanban/views/pm-kanban.tsx b/panel/src/components/kanban/views/pm-kanban.tsx
index e59d4ad2..9887a391 100644
--- a/panel/src/components/kanban/views/pm-kanban.tsx
+++ b/panel/src/components/kanban/views/pm-kanban.tsx
@@ -11,20 +11,90 @@ import { KanbanBoard } from "../core/kanban-board";
// can recover a wedged task straight from the board. (verifying is omitted: a
// transient dev-internal self-check state, not a destination a human sets.)
const PM_COLUMNS = [
- { id: "backlog", status: TaskStatus.BACKLOG, title: "Backlog", color: "bg-slate-50 dark:bg-slate-900" },
- { id: "incoming", status: TaskStatus.PENDING, title: "Pending", color: "bg-gray-100 dark:bg-gray-800" },
- { id: "assigned", status: TaskStatus.CLAIMED, title: "Assigned", color: "bg-blue-50 dark:bg-blue-950" },
- { id: "in-progress", status: TaskStatus.IN_PROGRESS, title: "In Progress", color: "bg-blue-100 dark:bg-blue-900" },
- { id: "blocked", status: TaskStatus.BLOCKED, title: "Blocked", color: "bg-red-100 dark:bg-red-900" },
- { id: "paused", status: TaskStatus.PAUSED, title: "Paused", color: "bg-amber-50 dark:bg-amber-950" },
- { id: "qa", status: TaskStatus.AWAITING_QA, title: "In QA", color: "bg-yellow-50 dark:bg-yellow-950" },
- { id: "needs-revision", status: TaskStatus.NEEDS_REVISION, title: "Needs Revision", color: "bg-rose-50 dark:bg-rose-950" },
- { id: "docs", status: TaskStatus.AWAITING_DOCUMENTATION, title: "In Docs", color: "bg-purple-50 dark:bg-purple-950" },
- { id: "pr-review", status: TaskStatus.AWAITING_PR_REVIEW, title: "PR Review", color: "bg-teal-50 dark:bg-teal-950" },
- { id: "pm-review", status: TaskStatus.AWAITING_PM_REVIEW, title: "PM Review", color: "bg-orange-50 dark:bg-orange-950" },
- { id: "ceo-approval", status: TaskStatus.AWAITING_CEO_APPROVAL, title: "CEO Approval", color: "bg-indigo-50 dark:bg-indigo-950" },
- { id: "done", status: TaskStatus.COMPLETED, title: "Done", color: "bg-green-50 dark:bg-green-950" },
- { id: "cancelled", status: TaskStatus.CANCELLED, title: "Cancelled", color: "bg-zinc-100 dark:bg-zinc-900" },
+ {
+ id: "backlog",
+ status: TaskStatus.BACKLOG,
+ title: "Backlog",
+ color: "bg-slate-50 dark:bg-slate-900",
+ },
+ {
+ id: "incoming",
+ status: TaskStatus.PENDING,
+ title: "Pending",
+ color: "bg-gray-100 dark:bg-gray-800",
+ },
+ {
+ id: "assigned",
+ status: TaskStatus.CLAIMED,
+ title: "Assigned",
+ color: "bg-blue-50 dark:bg-blue-950",
+ },
+ {
+ id: "in-progress",
+ status: TaskStatus.IN_PROGRESS,
+ title: "In Progress",
+ color: "bg-blue-100 dark:bg-blue-900",
+ },
+ {
+ id: "blocked",
+ status: TaskStatus.BLOCKED,
+ title: "Blocked",
+ color: "bg-red-100 dark:bg-red-900",
+ },
+ {
+ id: "paused",
+ status: TaskStatus.PAUSED,
+ title: "Paused",
+ color: "bg-amber-50 dark:bg-amber-950",
+ },
+ {
+ id: "qa",
+ status: TaskStatus.AWAITING_QA,
+ title: "In QA",
+ color: "bg-yellow-50 dark:bg-yellow-950",
+ },
+ {
+ id: "needs-revision",
+ status: TaskStatus.NEEDS_REVISION,
+ title: "Needs Revision",
+ color: "bg-rose-50 dark:bg-rose-950",
+ },
+ {
+ id: "docs",
+ status: TaskStatus.AWAITING_DOCUMENTATION,
+ title: "In Docs",
+ color: "bg-purple-50 dark:bg-purple-950",
+ },
+ {
+ id: "pr-review",
+ status: TaskStatus.AWAITING_PR_REVIEW,
+ title: "PR Review",
+ color: "bg-teal-50 dark:bg-teal-950",
+ },
+ {
+ id: "pm-review",
+ status: TaskStatus.AWAITING_PM_REVIEW,
+ title: "PM Review",
+ color: "bg-orange-50 dark:bg-orange-950",
+ },
+ {
+ id: "ceo-approval",
+ status: TaskStatus.AWAITING_CEO_APPROVAL,
+ title: "CEO Approval",
+ color: "bg-indigo-50 dark:bg-indigo-950",
+ },
+ {
+ id: "done",
+ status: TaskStatus.COMPLETED,
+ title: "Done",
+ color: "bg-green-50 dark:bg-green-950",
+ },
+ {
+ id: "cancelled",
+ status: TaskStatus.CANCELLED,
+ title: "Cancelled",
+ color: "bg-zinc-100 dark:bg-zinc-900",
+ },
];
interface PmKanbanProps {
diff --git a/panel/src/components/kanban/views/pr-review-kanban.tsx b/panel/src/components/kanban/views/pr-review-kanban.tsx
index 333801de..3001bded 100644
--- a/panel/src/components/kanban/views/pr-review-kanban.tsx
+++ b/panel/src/components/kanban/views/pr-review-kanban.tsx
@@ -8,9 +8,24 @@ import { KanbanBoard } from "../core/kanban-board";
// root->master PR before the PM merges. A card sits in "Awaiting Review" until
// a reviewer pr_passes it on to PM Review or pr_fails it back for changes.
const PR_REVIEW_COLUMNS = [
- { id: "awaiting", status: TaskStatus.AWAITING_PR_REVIEW, title: "Awaiting Review", color: "bg-teal-100 dark:bg-teal-900" },
- { id: "passed", status: TaskStatus.AWAITING_PM_REVIEW, title: "Passed", color: "bg-green-50 dark:bg-green-950" },
- { id: "changes", status: TaskStatus.NEEDS_REVISION, title: "Changes Requested", color: "bg-red-50 dark:bg-red-950" },
+ {
+ id: "awaiting",
+ status: TaskStatus.AWAITING_PR_REVIEW,
+ title: "Awaiting Review",
+ color: "bg-teal-100 dark:bg-teal-900",
+ },
+ {
+ id: "passed",
+ status: TaskStatus.AWAITING_PM_REVIEW,
+ title: "Passed",
+ color: "bg-green-50 dark:bg-green-950",
+ },
+ {
+ id: "changes",
+ status: TaskStatus.NEEDS_REVISION,
+ title: "Changes Requested",
+ color: "bg-red-50 dark:bg-red-950",
+ },
];
interface PrReviewKanbanProps {
diff --git a/panel/src/components/kanban/views/qa-kanban.tsx b/panel/src/components/kanban/views/qa-kanban.tsx
index 5dcb7bb2..f7c19c48 100644
--- a/panel/src/components/kanban/views/qa-kanban.tsx
+++ b/panel/src/components/kanban/views/qa-kanban.tsx
@@ -5,10 +5,30 @@ import { TaskStatus, Team } from "@/types";
import { KanbanBoard } from "../core/kanban-board";
const QA_COLUMNS = [
- { id: "awaiting", status: TaskStatus.AWAITING_QA, title: "Awaiting Review", color: "bg-yellow-100 dark:bg-yellow-900" },
- { id: "verifying", status: TaskStatus.VERIFYING, title: "In Review", color: "bg-blue-100 dark:bg-blue-900" },
- { id: "passed", status: TaskStatus.AWAITING_DOCUMENTATION, title: "Passed", color: "bg-green-50 dark:bg-green-950" },
- { id: "failed", status: TaskStatus.NEEDS_REVISION, title: "Failed", color: "bg-red-50 dark:bg-red-950" },
+ {
+ id: "awaiting",
+ status: TaskStatus.AWAITING_QA,
+ title: "Awaiting Review",
+ color: "bg-yellow-100 dark:bg-yellow-900",
+ },
+ {
+ id: "verifying",
+ status: TaskStatus.VERIFYING,
+ title: "In Review",
+ color: "bg-blue-100 dark:bg-blue-900",
+ },
+ {
+ id: "passed",
+ status: TaskStatus.AWAITING_DOCUMENTATION,
+ title: "Passed",
+ color: "bg-green-50 dark:bg-green-950",
+ },
+ {
+ id: "failed",
+ status: TaskStatus.NEEDS_REVISION,
+ title: "Failed",
+ color: "bg-red-50 dark:bg-red-950",
+ },
];
interface QaKanbanProps {
diff --git a/panel/src/components/knowledge-base/index.ts b/panel/src/components/knowledge-base/index.ts
index b7594d30..bc66b70e 100644
--- a/panel/src/components/knowledge-base/index.ts
+++ b/panel/src/components/knowledge-base/index.ts
@@ -2,7 +2,11 @@
export { KnowledgeBaseBrowser } from "./knowledge-base-browser";
// Shared components
-export { KBIndexTypeBadge, getIndexTypeIcon, getIndexTypeLabel } from "./kb-index-type-badge";
+export {
+ KBIndexTypeBadge,
+ getIndexTypeIcon,
+ getIndexTypeLabel,
+} from "./kb-index-type-badge";
export { KBStatsCard } from "./kb-stats-card";
export { KBSearchBar } from "./kb-search-bar";
export { KBFilters } from "./kb-filters";
diff --git a/panel/src/components/knowledge-base/kb-category-nav.tsx b/panel/src/components/knowledge-base/kb-category-nav.tsx
index bda4ddcd..f78265c7 100644
--- a/panel/src/components/knowledge-base/kb-category-nav.tsx
+++ b/panel/src/components/knowledge-base/kb-category-nav.tsx
@@ -1,12 +1,25 @@
"use client";
import { KBIndexType, KBStats } from "@/types";
-import { FileText, MessageSquare, BookOpen, ChevronRight, AlertTriangle, Scale, GitBranch, ClipboardCheck, Lightbulb } from "lucide-react";
+import {
+ FileText,
+ MessageSquare,
+ BookOpen,
+ ChevronRight,
+ AlertTriangle,
+ Scale,
+ GitBranch,
+ ClipboardCheck,
+ Lightbulb,
+} from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
-const categoryConfig: Record = {
+const categoryConfig: Record<
+ KBIndexType,
+ { label: string; description: string; icon: React.ReactNode }
+> = {
[KBIndexType.DOCUMENTATION]: {
label: "Documentation",
description: "READMEs, guides, and API docs",
@@ -66,7 +79,10 @@ export function KBCategoryNav({
return (
{[1, 2, 3, 4].map((i) => (
-
+
@@ -100,7 +116,7 @@ export function KBCategoryNav({
"w-full h-auto justify-start gap-3 p-3 font-normal whitespace-normal",
isSelected
? "bg-primary/10 border-primary"
- : "hover:bg-muted/50 border-transparent hover:border-border"
+ : "hover:bg-muted/50 border-transparent hover:border-border",
)}
>
{config.icon}
@@ -111,9 +127,16 @@ export function KBCategoryNav({
{count.toLocaleString()} docs
-
{config.description}
+
+ {config.description}
+
-
+
);
})}
diff --git a/panel/src/components/knowledge-base/kb-category-view.tsx b/panel/src/components/knowledge-base/kb-category-view.tsx
index b1e516ef..6b659a81 100644
--- a/panel/src/components/knowledge-base/kb-category-view.tsx
+++ b/panel/src/components/knowledge-base/kb-category-view.tsx
@@ -7,7 +7,13 @@ import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { KBIndexTypeBadge } from "./kb-index-type-badge";
-import { FileCode, FolderOpen, Clock, ChevronLeft, ChevronRight } from "lucide-react";
+import {
+ FileCode,
+ FolderOpen,
+ Clock,
+ ChevronLeft,
+ ChevronRight,
+} from "lucide-react";
import { formatDistanceToNow } from "date-fns";
const PAGE_SIZE = 25;
@@ -24,14 +30,17 @@ function KBCategoryViewInner({ category }: { category: KBIndexType }) {
const { data, isLoading } = useKBDocuments(
category,
{ limit: PAGE_SIZE, offset },
- true
+ true,
);
if (isLoading) {
return (
{[1, 2, 3, 4, 5].map((i) => (
-
+
@@ -75,7 +84,9 @@ function KBCategoryViewInner({ category }: { category: KBIndexType }) {
- {formatDistanceToNow(new Date(doc.indexed_at), { addSuffix: true })}
+ {formatDistanceToNow(new Date(doc.indexed_at), {
+ addSuffix: true,
+ })}
diff --git a/panel/src/components/knowledge-base/kb-filters.tsx b/panel/src/components/knowledge-base/kb-filters.tsx
index dba37ef4..040f4b90 100644
--- a/panel/src/components/knowledge-base/kb-filters.tsx
+++ b/panel/src/components/knowledge-base/kb-filters.tsx
@@ -3,17 +3,53 @@
import { KBIndexType } from "@/types";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
-import { FileText, MessageSquare, BookOpen, AlertTriangle, Scale, GitBranch, ClipboardCheck, Lightbulb } from "lucide-react";
+import {
+ FileText,
+ MessageSquare,
+ BookOpen,
+ AlertTriangle,
+ Scale,
+ GitBranch,
+ ClipboardCheck,
+ Lightbulb,
+} from "lucide-react";
-const indexTypeConfig: Record
= {
- [KBIndexType.DOCUMENTATION]: { label: "Documentation", icon: },
- [KBIndexType.CONVERSATIONS]: { label: "Conversations", icon: },
- [KBIndexType.JOURNALS]: { label: "Journals", icon: },
- [KBIndexType.ERRORS]: { label: "Errors", icon: },
- [KBIndexType.STANDARDS]: { label: "Standards", icon: },
- [KBIndexType.DECISIONS]: { label: "Decisions", icon: },
- [KBIndexType.REVIEWS]: { label: "Reviews", icon: },
- [KBIndexType.LEARNINGS]: { label: "Learnings", icon: },
+const indexTypeConfig: Record<
+ KBIndexType,
+ { label: string; icon: React.ReactNode }
+> = {
+ [KBIndexType.DOCUMENTATION]: {
+ label: "Documentation",
+ icon: ,
+ },
+ [KBIndexType.CONVERSATIONS]: {
+ label: "Conversations",
+ icon: ,
+ },
+ [KBIndexType.JOURNALS]: {
+ label: "Journals",
+ icon: ,
+ },
+ [KBIndexType.ERRORS]: {
+ label: "Errors",
+ icon: ,
+ },
+ [KBIndexType.STANDARDS]: {
+ label: "Standards",
+ icon: ,
+ },
+ [KBIndexType.DECISIONS]: {
+ label: "Decisions",
+ icon: ,
+ },
+ [KBIndexType.REVIEWS]: {
+ label: "Reviews",
+ icon: ,
+ },
+ [KBIndexType.LEARNINGS]: {
+ label: "Learnings",
+ icon: ,
+ },
};
interface KBFiltersProps {
@@ -69,7 +105,8 @@ export function KBFilters({ selectedTypes, onTypesChange }: KBFiltersProps) {
{!allSelected && (
- Showing {selectedTypes.length} of {Object.values(KBIndexType).length} types
+ Showing {selectedTypes.length} of {Object.values(KBIndexType).length}{" "}
+ types
)}
diff --git a/panel/src/components/knowledge-base/kb-index-type-badge.tsx b/panel/src/components/knowledge-base/kb-index-type-badge.tsx
index 47323e16..8466c1b9 100644
--- a/panel/src/components/knowledge-base/kb-index-type-badge.tsx
+++ b/panel/src/components/knowledge-base/kb-index-type-badge.tsx
@@ -2,9 +2,21 @@
import { Badge } from "@/components/ui/badge";
import { KBIndexType } from "@/types";
-import { FileText, MessageSquare, BookOpen, AlertTriangle, Scale, GitBranch, ClipboardCheck, Lightbulb } from "lucide-react";
+import {
+ FileText,
+ MessageSquare,
+ BookOpen,
+ AlertTriangle,
+ Scale,
+ GitBranch,
+ ClipboardCheck,
+ Lightbulb,
+} from "lucide-react";
-const indexTypeConfig: Record
= {
+const indexTypeConfig: Record<
+ KBIndexType,
+ { label: string; color: string; icon: React.ReactNode }
+> = {
[KBIndexType.DOCUMENTATION]: {
label: "Docs",
color: "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
@@ -17,7 +29,8 @@ const indexTypeConfig: Record ,
},
[KBIndexType.ERRORS]: {
@@ -32,7 +45,8 @@ const indexTypeConfig: Record ,
},
[KBIndexType.REVIEWS]: {
@@ -42,7 +56,8 @@ const indexTypeConfig: Record ,
},
};
@@ -53,7 +68,11 @@ interface KBIndexTypeBadgeProps {
className?: string;
}
-export function KBIndexTypeBadge({ indexType, showIcon = true, className }: KBIndexTypeBadgeProps) {
+export function KBIndexTypeBadge({
+ indexType,
+ showIcon = true,
+ className,
+}: KBIndexTypeBadgeProps) {
const config = indexTypeConfig[indexType];
return (
diff --git a/panel/src/components/knowledge-base/kb-result-card.tsx b/panel/src/components/knowledge-base/kb-result-card.tsx
index 6698f2a8..8073d907 100644
--- a/panel/src/components/knowledge-base/kb-result-card.tsx
+++ b/panel/src/components/knowledge-base/kb-result-card.tsx
@@ -12,9 +12,10 @@ interface KBResultCardProps {
export function KBResultCard({ result, onClick }: KBResultCardProps) {
// Truncate content for display (snippet)
- const snippet = result.content.length > 300
- ? result.content.substring(0, 300) + "..."
- : result.content;
+ const snippet =
+ result.content.length > 300
+ ? result.content.substring(0, 300) + "..."
+ : result.content;
// Format source for display
const formatSource = (source: string) => {
@@ -58,7 +59,9 @@ export function KBResultCard({ result, onClick }: KBResultCardProps) {
{/* Source */}
- {formatSource(result.source)}
+
+ {formatSource(result.source)}
+
{/* Content snippet */}
diff --git a/panel/src/components/knowledge-base/kb-result-list.tsx b/panel/src/components/knowledge-base/kb-result-list.tsx
index 147dea07..00e11ccd 100644
--- a/panel/src/components/knowledge-base/kb-result-list.tsx
+++ b/panel/src/components/knowledge-base/kb-result-list.tsx
@@ -11,7 +11,11 @@ interface KBResultListProps {
query: string;
}
-export function KBResultList({ response, isLoading, query }: KBResultListProps) {
+export function KBResultList({
+ response,
+ isLoading,
+ query,
+}: KBResultListProps) {
if (isLoading) {
return (
@@ -35,8 +39,8 @@ export function KBResultList({ response, isLoading, query }: KBResultListProps)
Search the Knowledge Base
- Enter at least 3 characters to search across indexed code, documentation,
- conversations, and journals.
+ Enter at least 3 characters to search across indexed code,
+ documentation, conversations, and journals.
);
@@ -48,7 +52,8 @@ export function KBResultList({ response, isLoading, query }: KBResultListProps)
No results found
- No matches for “{query}”. Try different keywords or adjust your filters.
+ No matches for “{query}”. Try different keywords or adjust
+ your filters.
);
@@ -57,7 +62,8 @@ export function KBResultList({ response, isLoading, query }: KBResultListProps)
return (
- Found {response.total} result{response.total !== 1 ? "s" : ""} for “{response.query}”
+ Found {response.total} result{response.total !== 1 ? "s" : ""} for
+ “{response.query}”
{response.results.map((result, index) => (
diff --git a/panel/src/components/knowledge-base/kb-search-bar.tsx b/panel/src/components/knowledge-base/kb-search-bar.tsx
index 73d4b12c..923e159e 100644
--- a/panel/src/components/knowledge-base/kb-search-bar.tsx
+++ b/panel/src/components/knowledge-base/kb-search-bar.tsx
@@ -76,7 +76,10 @@ export function KBSearchBar({
)}
{onSearch && (
-
+
{isLoading ? : "Search"}
)}
diff --git a/panel/src/components/knowledge-base/kb-stats-card.tsx b/panel/src/components/knowledge-base/kb-stats-card.tsx
index ed895bb4..205541bc 100644
--- a/panel/src/components/knowledge-base/kb-stats-card.tsx
+++ b/panel/src/components/knowledge-base/kb-stats-card.tsx
@@ -3,12 +3,24 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { KBStats, KBIndexType } from "@/types";
-import { Database, FileText, MessageSquare, BookOpen, AlertTriangle, Scale, GitBranch, ClipboardCheck, Lightbulb } from "lucide-react";
+import {
+ Database,
+ FileText,
+ MessageSquare,
+ BookOpen,
+ AlertTriangle,
+ Scale,
+ GitBranch,
+ ClipboardCheck,
+ Lightbulb,
+} from "lucide-react";
import { formatDistanceToNow } from "date-fns";
const indexIcons: Record = {
[KBIndexType.DOCUMENTATION]: ,
- [KBIndexType.CONVERSATIONS]: ,
+ [KBIndexType.CONVERSATIONS]: (
+
+ ),
[KBIndexType.JOURNALS]: ,
[KBIndexType.ERRORS]: ,
[KBIndexType.STANDARDS]: ,
@@ -73,7 +85,9 @@ export function KBStatsCard({ stats, isLoading }: KBStatsCardProps) {
const latestUpdated = stats.indexes.reduce(
(acc, idx) =>
- idx.last_updated && (!acc || idx.last_updated > acc) ? idx.last_updated : acc,
+ idx.last_updated && (!acc || idx.last_updated > acc)
+ ? idx.last_updated
+ : acc,
null,
);
@@ -87,13 +101,18 @@ export function KBStatsCard({ stats, isLoading }: KBStatsCardProps) {
{stats.indexes.map((idx) => (
-
+
{indexIcons[idx.index_type]}
{indexLabels[idx.index_type]}
- {idx.document_count.toLocaleString()}
+
+ {idx.document_count.toLocaleString()}
+
docs
@@ -101,7 +120,9 @@ export function KBStatsCard({ stats, isLoading }: KBStatsCardProps) {
Total
- {stats.total_documents.toLocaleString()}
+
+ {stats.total_documents.toLocaleString()}
+
Chunks
diff --git a/panel/src/components/knowledge-base/knowledge-base-browser.tsx b/panel/src/components/knowledge-base/knowledge-base-browser.tsx
index 3a44b9f1..ffe0f245 100644
--- a/panel/src/components/knowledge-base/knowledge-base-browser.tsx
+++ b/panel/src/components/knowledge-base/knowledge-base-browser.tsx
@@ -95,9 +95,14 @@ function KnowledgeBaseBrowserContent() {
const searchQuery = searchParams.get("q") || "";
const filtersParam = searchParams.get("filters");
const searchFilters: KBIndexType[] = filtersParam
- ? (filtersParam.split(",").filter((f) => VALID_INDEX_TYPES.includes(f as KBIndexType)) as KBIndexType[])
+ ? (filtersParam
+ .split(",")
+ .filter((f) =>
+ VALID_INDEX_TYPES.includes(f as KBIndexType),
+ ) as KBIndexType[])
: [];
- const selectedCategory = (searchParams.get("category") as KBIndexType) || null;
+ const selectedCategory =
+ (searchParams.get("category") as KBIndexType) || null;
// RAG state (transient, not URL-persisted)
const [ragQuestion, setRagQuestion] = useState
(null);
@@ -118,7 +123,7 @@ function KnowledgeBaseBrowserContent() {
const query = params.toString();
router.push(query ? `/knowledge-base?${query}` : "/knowledge-base");
},
- [router, searchParams]
+ [router, searchParams],
);
// State update handlers
@@ -126,32 +131,37 @@ function KnowledgeBaseBrowserContent() {
(tab: TabValue) => {
updateParams({ tab: tab === "search" ? null : tab });
},
- [updateParams]
+ [updateParams],
);
const handleSearchChange = useCallback(
(query: string) => {
updateParams({ q: query || null });
},
- [updateParams]
+ [updateParams],
);
const handleFiltersChange = useCallback(
(filters: KBIndexType[]) => {
updateParams({ filters: filters.length > 0 ? filters.join(",") : null });
},
- [updateParams]
+ [updateParams],
);
const handleCategoryChange = useCallback(
(category: KBIndexType | null) => {
updateParams({ category });
},
- [updateParams]
+ [updateParams],
);
// Data hooks
- const { data: stats, isLoading: statsLoading, error: statsError, refetch: refetchStats } = useKBStats();
+ const {
+ data: stats,
+ isLoading: statsLoading,
+ error: statsError,
+ refetch: refetchStats,
+ } = useKBStats();
const { data: searchResults, isLoading: searchLoading } = useKBSearch({
query: searchQuery,
index_types: searchFilters.length > 0 ? searchFilters : undefined,
@@ -159,7 +169,11 @@ function KnowledgeBaseBrowserContent() {
const ragMutation = useRAGQuery();
// Admin hooks
- const { data: health, isLoading: loadingHealth, refetch: refetchHealth } = useRAGHealth();
+ const {
+ data: health,
+ isLoading: loadingHealth,
+ refetch: refetchHealth,
+ } = useRAGHealth();
const deleteIndex = useDeleteIndex();
const refreshIndex = useRefreshIndex();
const reindexAll = useReindexAll();
@@ -212,7 +226,7 @@ function KnowledgeBaseBrowserContent() {
toast.success(`Reindexed ${docsCount} docs.${warns}`);
} else {
toast.warning(
- `Reindex completed with issues: ${result.warnings?.join(", ") ?? "Unknown errors"}`
+ `Reindex completed with issues: ${result.warnings?.join(", ") ?? "Unknown errors"}`,
);
}
} catch (error) {
@@ -239,21 +253,25 @@ function KnowledgeBaseBrowserContent() {
};
// Calculate totals for admin
- const totalDocs = stats?.indexes.reduce((sum, idx) => sum + idx.document_count, 0) ?? 0;
- const totalChunks = stats?.indexes.reduce((sum, idx) => sum + idx.chunk_count, 0) ?? 0;
+ const totalDocs =
+ stats?.indexes.reduce((sum, idx) => sum + idx.document_count, 0) ?? 0;
+ const totalChunks =
+ stats?.indexes.reduce((sum, idx) => sum + idx.chunk_count, 0) ?? 0;
// Check if offline
- const isOffline = statsError && (
- statsError.message?.includes("Network Error") ||
- (statsError as { code?: string })?.code === "ERR_NETWORK"
- );
+ const isOffline =
+ statsError &&
+ (statsError.message?.includes("Network Error") ||
+ (statsError as { code?: string })?.code === "ERR_NETWORK");
if (isOffline) {
return (
-
Knowledge Base
+
+ Knowledge Base
+
Search and query indexed knowledge
@@ -285,7 +303,10 @@ function KnowledgeBaseBrowserContent() {
{/* Tabs */}
-
handleTabChange(v as TabValue)}>
+ handleTabChange(v as TabValue)}
+ >
@@ -371,10 +392,7 @@ function KnowledgeBaseBrowserContent() {
{/* Mentor Tab - Chat Interface */}
-
+
{/* Browse Tab */}
@@ -424,7 +442,9 @@ function KnowledgeBaseBrowserContent() {
)}
-
{health.healthy ? "Healthy" : "Unhealthy"}
+
+ {health.healthy ? "Healthy" : "Unhealthy"}
+
Embedding: {health.embedding_status}
@@ -436,7 +456,9 @@ function KnowledgeBaseBrowserContent() {
) : (
-
Health data unavailable
+
+ Health data unavailable
+
)}
@@ -457,13 +479,19 @@ function KnowledgeBaseBrowserContent() {
Reindex All Data?
- This will rebuild all indexes from scratch. This may take several minutes.
+ This will rebuild all indexes from scratch. This may
+ take several minutes.
Cancel
-
- {reindexAll.isPending && }
+
+ {reindexAll.isPending && (
+
+ )}
Reindex
@@ -484,7 +512,6 @@ function KnowledgeBaseBrowserContent() {
-
{/* Right Column - Index Management */}
@@ -508,9 +535,12 @@ function KnowledgeBaseBrowserContent() {
{stats?.indexes.map((index) => {
const indexType = index.index_type;
- const percentage = totalChunks > 0
- ? Math.round((index.chunk_count / totalChunks) * 100)
- : 0;
+ const percentage =
+ totalChunks > 0
+ ? Math.round(
+ (index.chunk_count / totalChunks) * 100,
+ )
+ : 0;
return (
-
{INDEX_LABELS[indexType]}
+
+ {INDEX_LABELS[indexType]}
+
{indexType}
@@ -529,7 +561,9 @@ function KnowledgeBaseBrowserContent() {
handleRefreshIndex(indexType)}
+ onClick={() =>
+ handleRefreshIndex(indexType)
+ }
disabled={refreshIndex.isPending}
>
{refreshIndex.isPending ? (
@@ -540,22 +574,34 @@ function KnowledgeBaseBrowserContent() {
-
+
- Delete {INDEX_LABELS[indexType]}?
+
+ Delete {INDEX_LABELS[indexType]}?
+
- This will permanently delete all {index.document_count} documents
- and {index.chunk_count} chunks from this index.
+ This will permanently delete all{" "}
+ {index.document_count} documents and{" "}
+ {index.chunk_count} chunks from this
+ index.
- Cancel
+
+ Cancel
+
handleDeleteIndex(indexType)}
+ onClick={() =>
+ handleDeleteIndex(indexType)
+ }
className="bg-red-600 hover:bg-red-700"
>
Delete
@@ -568,18 +614,30 @@ function KnowledgeBaseBrowserContent() {
- Documents: {" "}
- {index.document_count}
+
+ Documents:
+ {" "}
+
+ {index.document_count}
+
- Chunks: {" "}
- {index.chunk_count}
+
+ Chunks:
+ {" "}
+
+ {index.chunk_count}
+
- Updated: {" "}
+
+ Updated:
+ {" "}
{index.last_updated
- ? formatDistanceToNow(new Date(index.last_updated)) + " ago"
+ ? formatDistanceToNow(
+ new Date(index.last_updated),
+ ) + " ago"
: "Never"}
diff --git a/panel/src/components/knowledge-base/mentor-answer-display.tsx b/panel/src/components/knowledge-base/mentor-answer-display.tsx
index 3d4109a2..6418d90e 100644
--- a/panel/src/components/knowledge-base/mentor-answer-display.tsx
+++ b/panel/src/components/knowledge-base/mentor-answer-display.tsx
@@ -72,12 +72,19 @@ export function MentorAnswerDisplay({
AI Mentor
Your personalized AI mentor that knows your role and past experiences.
- Ask questions about standards, workflows, or get guidance on your tasks.
+ Ask questions about standards, workflows, or get guidance on your
+ tasks.
- Role-aware
- Personal context
- Follow-ups
+
+ Role-aware
+
+
+ Personal context
+
+
+ Follow-ups
+
);
@@ -117,7 +124,10 @@ export function MentorAnswerDisplay({
// Calculate total sources searched
const totalSearched = response.search_stats
- ? Object.values(response.search_stats).reduce((sum, count) => sum + (count > 0 ? count : 0), 0)
+ ? Object.values(response.search_stats).reduce(
+ (sum, count) => sum + (count > 0 ? count : 0),
+ 0,
+ )
: 0;
return (
@@ -145,18 +155,22 @@ export function MentorAnswerDisplay({
{response.agent_role}
{response.agent_team && (
- ({response.agent_team})
+
+ ({response.agent_team})
+
)}
)}
- {response.journal_entries_used !== undefined && response.journal_entries_used > 0 && (
-
-
-
- {response.journal_entries_used} personal journal{response.journal_entries_used !== 1 ? "s" : ""} used
+ {response.journal_entries_used !== undefined &&
+ response.journal_entries_used > 0 && (
+
+
+
+ {response.journal_entries_used} personal journal
+ {response.journal_entries_used !== 1 ? "s" : ""} used
+
-
- )}
+ )}
)}
@@ -180,52 +194,56 @@ export function MentorAnswerDisplay({
{/* Suggested Follow-ups */}
- {response.suggested_followups && response.suggested_followups.length > 0 && (
-
-
-
- Follow-up Questions
+ {response.suggested_followups &&
+ response.suggested_followups.length > 0 && (
+
+
+
+ Follow-up Questions
+
+
+ {response.suggested_followups.map((followup, index) => (
+ onFollowUp?.(followup)}
+ >
+
+ {followup}
+
+ ))}
+
-
- {response.suggested_followups.map((followup, index) => (
- onFollowUp?.(followup)}
- >
-
- {followup}
-
- ))}
-
-
- )}
+ )}
{/* Search Stats */}
- {response.search_stats && Object.keys(response.search_stats).length > 0 && (
-
-
-
- Search Stats
-
- ({totalSearched} total results)
-
+ {response.search_stats &&
+ Object.keys(response.search_stats).length > 0 && (
+
+
+
+ Search Stats
+
+ ({totalSearched} total results)
+
+
+
+ {Object.entries(response.search_stats).map(
+ ([indexType, count]) => (
+ 0 ? "secondary" : "outline"}
+ className={`text-xs ${count === -1 ? "text-red-500" : ""}`}
+ >
+ {indexType}: {count === -1 ? "error" : count}
+
+ ),
+ )}
+
-
- {Object.entries(response.search_stats).map(([indexType, count]) => (
- 0 ? "secondary" : "outline"}
- className={`text-xs ${count === -1 ? "text-red-500" : ""}`}
- >
- {indexType}: {count === -1 ? "error" : count}
-
- ))}
-
-
- )}
+ )}
{/* Citations */}
{response.sources.length > 0 && (
diff --git a/panel/src/components/knowledge-base/mentor-chat.tsx b/panel/src/components/knowledge-base/mentor-chat.tsx
index af693b79..ca572c99 100644
--- a/panel/src/components/knowledge-base/mentor-chat.tsx
+++ b/panel/src/components/knowledge-base/mentor-chat.tsx
@@ -31,7 +31,10 @@ interface ChatMessage {
}
interface MentorChatProps {
- onAsk: (question: string, conversationId?: string) => Promise
;
+ onAsk: (
+ question: string,
+ conversationId?: string,
+ ) => Promise;
isLoading: boolean;
}
@@ -111,8 +114,9 @@ export function MentorChat({ onAsk, isLoading }: MentorChatProps) {
AI Mentor
- Your personalized mentor that knows your role, searches your journals,
- and provides tailored guidance. Start a conversation below.
+ Your personalized mentor that knows your role, searches your
+ journals, and provides tailored guidance. Start a conversation
+ below.
@@ -194,16 +198,23 @@ export function MentorChat({ onAsk, isLoading }: MentorChatProps) {
{msg.agentRole && (
- Role: {msg.agentRole}
- {msg.agentTeam && ({msg.agentTeam}) }
-
- )}
- {msg.journalEntriesUsed !== undefined && msg.journalEntriesUsed > 0 && (
-
-
- {msg.journalEntriesUsed} journal{msg.journalEntriesUsed !== 1 ? "s" : ""} used
+ Role:{" "}
+
+ {msg.agentRole}
+
+ {msg.agentTeam && (
+ ({msg.agentTeam})
+ )}
)}
+ {msg.journalEntriesUsed !== undefined &&
+ msg.journalEntriesUsed > 0 && (
+
+
+ {msg.journalEntriesUsed} journal
+ {msg.journalEntriesUsed !== 1 ? "s" : ""} used
+
+ )}
)}
@@ -223,9 +234,14 @@ export function MentorChat({ onAsk, isLoading }: MentorChatProps) {
variant="ghost"
size="sm"
className="text-xs"
- onClick={() => setExpandedSources(expandedSources === idx ? null : idx)}
+ onClick={() =>
+ setExpandedSources(
+ expandedSources === idx ? null : idx,
+ )
+ }
>
- {expandedSources === idx ? "Hide" : "Show"} {msg.sources.length} sources
+ {expandedSources === idx ? "Hide" : "Show"}{" "}
+ {msg.sources.length} sources
{expandedSources === idx && (
diff --git a/panel/src/components/knowledge-base/mentor-query-input.tsx b/panel/src/components/knowledge-base/mentor-query-input.tsx
index 357277dc..5fce3ecb 100644
--- a/panel/src/components/knowledge-base/mentor-query-input.tsx
+++ b/panel/src/components/knowledge-base/mentor-query-input.tsx
@@ -5,14 +5,24 @@ import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
-import { Send, Loader2, Brain, User, BookMarked, MessageCircle } from "lucide-react";
+import {
+ Send,
+ Loader2,
+ Brain,
+ User,
+ BookMarked,
+ MessageCircle,
+} from "lucide-react";
interface MentorQueryInputProps {
onSubmit: (question: string) => void;
isLoading: boolean;
}
-export function MentorQueryInput({ onSubmit, isLoading }: MentorQueryInputProps) {
+export function MentorQueryInput({
+ onSubmit,
+ isLoading,
+}: MentorQueryInputProps) {
const [question, setQuestion] = useState("");
const handleSubmit = () => {
diff --git a/panel/src/components/knowledge-base/rag-answer-display.tsx b/panel/src/components/knowledge-base/rag-answer-display.tsx
index a0fc857e..b0364348 100644
--- a/panel/src/components/knowledge-base/rag-answer-display.tsx
+++ b/panel/src/components/knowledge-base/rag-answer-display.tsx
@@ -5,7 +5,13 @@ import { RAGQueryResponse } from "@/types";
import { RAGCitationCard } from "./rag-citation-card";
import { Markdown } from "@/components/ui/markdown";
import { Skeleton } from "@/components/ui/skeleton";
-import { Bot, BookOpen, MessageSquareText, Sparkles, AlertCircle } from "lucide-react";
+import {
+ Bot,
+ BookOpen,
+ MessageSquareText,
+ Sparkles,
+ AlertCircle,
+} from "lucide-react";
interface RAGAnswerDisplayProps {
response: RAGQueryResponse | null;
@@ -14,7 +20,12 @@ interface RAGAnswerDisplayProps {
error?: string | null;
}
-export function RAGAnswerDisplay({ response, isLoading, question, error }: RAGAnswerDisplayProps) {
+export function RAGAnswerDisplay({
+ response,
+ isLoading,
+ question,
+ error,
+}: RAGAnswerDisplayProps) {
if (isLoading) {
return (
@@ -51,8 +62,9 @@ export function RAGAnswerDisplay({ response, isLoading, question, error }: RAGAn
Ask AI
- Ask questions about your codebase, documentation, or past conversations.
- The AI will provide answers with citations from the knowledge base.
+ Ask questions about your codebase, documentation, or past
+ conversations. The AI will provide answers with citations from the
+ knowledge base.
);
diff --git a/panel/src/components/knowledge-base/rag-citation-card.tsx b/panel/src/components/knowledge-base/rag-citation-card.tsx
index 776b58b3..3c93c9b1 100644
--- a/panel/src/components/knowledge-base/rag-citation-card.tsx
+++ b/panel/src/components/knowledge-base/rag-citation-card.tsx
@@ -12,9 +12,10 @@ interface RAGCitationCardProps {
export function RAGCitationCard({ citation, index }: RAGCitationCardProps) {
// Truncate content
- const snippet = citation.content.length > 200
- ? citation.content.substring(0, 200) + "..."
- : citation.content;
+ const snippet =
+ citation.content.length > 200
+ ? citation.content.substring(0, 200) + "..."
+ : citation.content;
// Format source
const formatSource = (source: string) => {
@@ -38,7 +39,10 @@ export function RAGCitationCard({ citation, index }: RAGCitationCardProps) {
-
+
{scorePercent}%
@@ -49,7 +53,9 @@ export function RAGCitationCard({ citation, index }: RAGCitationCardProps) {
-
{snippet}
+
+ {snippet}
+
diff --git a/panel/src/components/layout/connection-status.tsx b/panel/src/components/layout/connection-status.tsx
index 2ce35120..7d2a195b 100644
--- a/panel/src/components/layout/connection-status.tsx
+++ b/panel/src/components/layout/connection-status.tsx
@@ -40,7 +40,10 @@ export function ConnectionStatus() {
if (state === "connected") {
return (
-
+
Connected
@@ -48,7 +51,10 @@ export function ConnectionStatus() {
}
return (
-
+
Offline
diff --git a/panel/src/components/layout/header.tsx b/panel/src/components/layout/header.tsx
index 9575ade3..befd4cc5 100644
--- a/panel/src/components/layout/header.tsx
+++ b/panel/src/components/layout/header.tsx
@@ -83,7 +83,9 @@ export function Header() {
{/* User */}
diff --git a/panel/src/components/layout/sidebar.tsx b/panel/src/components/layout/sidebar.tsx
index ddc82236..939bd66c 100644
--- a/panel/src/components/layout/sidebar.tsx
+++ b/panel/src/components/layout/sidebar.tsx
@@ -89,7 +89,7 @@ export function SidebarNav({
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
- collapsed && "justify-center px-2"
+ collapsed && "justify-center px-2",
)}
title={collapsed ? item.title : undefined}
>
@@ -119,7 +119,7 @@ export function SidebarFooter({
onClick={onNavigate}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
- collapsed && "justify-center px-2"
+ collapsed && "justify-center px-2",
)}
title={collapsed ? item.title : undefined}
>
@@ -140,7 +140,7 @@ export function Sidebar() {
// Hidden on mobile — the Header's hamburger opens the same nav in a
// Sheet drawer there (see MobileSidebar). Shown from md upward.
"hidden h-screen flex-col border-r bg-background transition-all duration-300 md:flex",
- sidebarCollapsed ? "w-16" : "w-64"
+ sidebarCollapsed ? "w-16" : "w-64",
)}
>
{/* Logo */}
@@ -168,7 +168,7 @@ export function Sidebar() {
diff --git a/panel/src/components/metrics/agent-usage-chart.tsx b/panel/src/components/metrics/agent-usage-chart.tsx
index bafdfa12..a25919c5 100644
--- a/panel/src/components/metrics/agent-usage-chart.tsx
+++ b/panel/src/components/metrics/agent-usage-chart.tsx
@@ -69,7 +69,11 @@ export function AgentUsageChart({ data, isLoading }: AgentUsageChartProps) {
]}
contentStyle={{ fontSize: 12 }}
/>
-
+
)}
diff --git a/panel/src/components/metrics/delivery-tab.tsx b/panel/src/components/metrics/delivery-tab.tsx
index 195f82ec..0fca7619 100644
--- a/panel/src/components/metrics/delivery-tab.tsx
+++ b/panel/src/components/metrics/delivery-tab.tsx
@@ -50,7 +50,9 @@ function CycleTimeCard() {
return (
- Cycle Time by Stage (avg, 30d)
+
+ Cycle Time by Stage (avg, 30d)
+
{isLoading ? (
@@ -61,7 +63,10 @@ function CycleTimeCard() {
) : (
-
+
[value + "h", "Avg"]}
contentStyle={{ fontSize: 12 }}
/>
-
+
)}
@@ -122,7 +131,8 @@ function BottlenecksCard() {
{label(s.status)}
- {fmtDuration(s.cumulative_seconds)} · {s.parked_now} parked
+ {fmtDuration(s.cumulative_seconds)} · {s.parked_now}{" "}
+ parked
@@ -163,8 +173,8 @@ function ReworkCard() {
{pct(data?.rate ?? 0)}
- {data?.total_reworked ?? 0}/{data?.total_completed ?? 0} bounced ·
- ${(data?.rework_cost_usd ?? 0).toFixed(2)} cost
+ {data?.total_reworked ?? 0}/{data?.total_completed ?? 0} bounced
+ · ${(data?.rework_cost_usd ?? 0).toFixed(2)} cost
@@ -186,7 +196,10 @@ function ReworkCard() {
{(data?.by_agent ?? []).slice(0, 8).map((a) => (
-
+
{a.agent_slug}
{pct(a.rate)}
{a.qa_fails}
@@ -235,7 +248,9 @@ function ScorecardBody({ card }: { card: Scorecard | undefined }) {
{stat("Completed", String(card?.tasks_completed ?? 0))}
{stat(
"Avg cycle",
- card?.avg_cycle_hours != null ? card.avg_cycle_hours.toFixed(1) + "h" : "—",
+ card?.avg_cycle_hours != null
+ ? card.avg_cycle_hours.toFixed(1) + "h"
+ : "—",
)}
{stat("Rework", pct(card?.rework_rate ?? 0))}
{stat("Cost", "$" + (card?.cost_usd ?? 0).toFixed(2))}
diff --git a/panel/src/components/metrics/sessions-table.tsx b/panel/src/components/metrics/sessions-table.tsx
index 81068fdb..023c82e4 100644
--- a/panel/src/components/metrics/sessions-table.tsx
+++ b/panel/src/components/metrics/sessions-table.tsx
@@ -48,7 +48,10 @@ const COLUMNS: Column[] = [
];
function formatTime(ts: string): string {
- return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
+ return new Date(ts).toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ });
}
function fmtK(n: number): string {
@@ -94,7 +97,8 @@ export function SessionsTable({ data, isLoading }: SessionsTableProps) {
}
function SortIcon({ col }: { col: SortKey }) {
- if (sortKey !== col) return ;
+ if (sortKey !== col)
+ return ;
return sortDir === "asc" ? (
) : (
@@ -135,21 +139,38 @@ export function SessionsTable({ data, isLoading }: SessionsTableProps) {
{visible.length === 0 ? (
-
+
No sessions recorded yet
) : (
visible.map((s) => (
- {s.agent_slug}
+
+ {s.agent_slug}
+
{s.model}
- {formatTime(s.started_at)}
- {fmtK(s.total_tokens)}
- {fmtK(s.tokens_input)}
- {fmtK(s.tokens_output)}
- {fmtK(s.tokens_cache)}
- ${s.cost.toFixed(4)}
+
+ {formatTime(s.started_at)}
+
+
+ {fmtK(s.total_tokens)}
+
+
+ {fmtK(s.tokens_input)}
+
+
+ {fmtK(s.tokens_output)}
+
+
+ {fmtK(s.tokens_cache)}
+
+
+ ${s.cost.toFixed(4)}
+
))
)}
@@ -176,7 +197,9 @@ export function SessionsTable({ data, isLoading }: SessionsTableProps) {
setPage((p) => Math.min(totalPages - 1, p + 1))}
+ onClick={() =>
+ setPage((p) => Math.min(totalPages - 1, p + 1))
+ }
disabled={page >= totalPages - 1}
>
Next
diff --git a/panel/src/components/metrics/team-usage-chart.tsx b/panel/src/components/metrics/team-usage-chart.tsx
index 0c87baa7..b7b17c1a 100644
--- a/panel/src/components/metrics/team-usage-chart.tsx
+++ b/panel/src/components/metrics/team-usage-chart.tsx
@@ -66,7 +66,11 @@ export function TeamUsageChart({ data, isLoading }: TeamUsageChartProps) {
]}
contentStyle={{ fontSize: 12 }}
/>
-
+
)}
diff --git a/panel/src/components/metrics/usage-time-series-chart.tsx b/panel/src/components/metrics/usage-time-series-chart.tsx
index fea4f995..7761863f 100644
--- a/panel/src/components/metrics/usage-time-series-chart.tsx
+++ b/panel/src/components/metrics/usage-time-series-chart.tsx
@@ -23,11 +23,12 @@ function formatBucket(bucket: string): string {
const d = new Date(bucket);
// If the bucket has a non-zero time component it is an hourly bucket → show HH:00.
// Otherwise it is a daily bucket → show MM/DD.
- const isHourly = d.getMinutes() === 0 && (d.getHours() !== 0 || bucket.includes("T"));
+ const isHourly =
+ d.getMinutes() === 0 && (d.getHours() !== 0 || bucket.includes("T"));
if (isHourly && d.getSeconds() === 0 && !bucket.endsWith("T00:00:00.000Z")) {
return d.getHours().toString().padStart(2, "0") + ":00";
}
- return (d.getMonth() + 1) + "/" + d.getDate();
+ return d.getMonth() + 1 + "/" + d.getDate();
}
function fmtK(n: number): string {
@@ -35,7 +36,10 @@ function fmtK(n: number): string {
return String(n);
}
-export function UsageTimeSeriesChart({ data, isLoading }: UsageTimeSeriesChartProps) {
+export function UsageTimeSeriesChart({
+ data,
+ isLoading,
+}: UsageTimeSeriesChartProps) {
const chartData = (data ?? []).map((p) => ({
hour: formatBucket(p.bucket),
Input: p.tokens_input,
@@ -58,12 +62,28 @@ export function UsageTimeSeriesChart({ data, isLoading }: UsageTimeSeriesChartPr
>
-
-
+
+
-
-
+
+
diff --git a/panel/src/components/notifications/notification-bell.tsx b/panel/src/components/notifications/notification-bell.tsx
index ffaa57bd..568f3774 100644
--- a/panel/src/components/notifications/notification-bell.tsx
+++ b/panel/src/components/notifications/notification-bell.tsx
@@ -24,9 +24,7 @@ export function NotificationBell() {
{unreadCount > 0 && (
-
+
{unreadCount > 9 ? "9+" : unreadCount}
)}
@@ -49,34 +47,37 @@ export function NotificationBell() {
)}
-
+
{notifications.length === 0 ? (
No new notifications
) : (
- {notifications.slice(-10).reverse().map((notification, i) => (
-
-
-
- {notification.subject}
-
-
- {notification.priority}
-
+ {notifications
+ .slice(-10)
+ .reverse()
+ .map((notification, i) => (
+
+
+
+ {notification.subject}
+
+
+ {notification.priority}
+
+
+
+ {notification.notification_type}
+
-
- {notification.notification_type}
-
-
- ))}
+ ))}
)}
-
+
setOpen(false)}>
diff --git a/panel/src/components/products/create-product-dialog.tsx b/panel/src/components/products/create-product-dialog.tsx
index c3dfa329..8a7b4655 100644
--- a/panel/src/components/products/create-product-dialog.tsx
+++ b/panel/src/components/products/create-product-dialog.tsx
@@ -42,10 +42,15 @@ function generateSlug(name: string): string {
}
// Build the cells payload from the per-cell project selections (only mapped cells)
-function buildCells(mapping: Partial>): ProductCellMapping[] {
+function buildCells(
+ mapping: Partial>,
+): ProductCellMapping[] {
return cells
.filter((cell) => mapping[cell.value])
- .map((cell) => ({ team: cell.value, project_id: mapping[cell.value] as string }));
+ .map((cell) => ({
+ team: cell.value,
+ project_id: mapping[cell.value] as string,
+ }));
}
export function CreateProductDialog() {
@@ -53,7 +58,9 @@ export function CreateProductDialog() {
const [name, setName] = useState("");
const [slug, setSlug] = useState("");
const [description, setDescription] = useState("");
- const [cellMapping, setCellMapping] = useState>>({});
+ const [cellMapping, setCellMapping] = useState>>(
+ {},
+ );
const createProduct = useCreateProduct();
const { data: projects } = useProjects();
@@ -90,7 +97,7 @@ export function CreateProductDialog() {
resetForm();
} catch (error) {
toast.error(
- `Failed to create product: ${error instanceof Error ? error.message : "Unknown error"}`
+ `Failed to create product: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
};
@@ -154,7 +161,10 @@ export function CreateProductDialog() {
Cell Project Mapping
{cells.map((cell) => (
-
+
{cell.label}
- setOpen(false)}>
+ setOpen(false)}
+ >
Cancel
diff --git a/panel/src/components/products/edit-product-dialog.tsx b/panel/src/components/products/edit-product-dialog.tsx
index 5460bc0d..911c30be 100644
--- a/panel/src/components/products/edit-product-dialog.tsx
+++ b/panel/src/components/products/edit-product-dialog.tsx
@@ -24,7 +24,12 @@ import {
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { toast } from "sonner";
-import { Team, type Product, type ProductCellMapping, type ProductUpdate } from "@/types";
+import {
+ Team,
+ type Product,
+ type ProductCellMapping,
+ type ProductUpdate,
+} from "@/types";
const cells: { value: Team; label: string }[] = [
{ value: Team.BACKEND, label: "Backend" },
@@ -33,7 +38,9 @@ const cells: { value: Team; label: string }[] = [
];
// Build a team -> project_id lookup from the product's cell mappings
-function mappingFromCells(productCells: ProductCellMapping[]): Partial> {
+function mappingFromCells(
+ productCells: ProductCellMapping[],
+): Partial> {
return productCells.reduce>>((acc, cell) => {
acc[cell.team] = cell.project_id;
return acc;
@@ -41,10 +48,15 @@ function mappingFromCells(productCells: ProductCellMapping[]): Partial>): ProductCellMapping[] {
+function buildCells(
+ mapping: Partial>,
+): ProductCellMapping[] {
return cells
.filter((cell) => mapping[cell.value])
- .map((cell) => ({ team: cell.value, project_id: mapping[cell.value] as string }));
+ .map((cell) => ({
+ team: cell.value,
+ project_id: mapping[cell.value] as string,
+ }));
}
interface EditProductDialogProps {
@@ -69,7 +81,7 @@ function EditProductForm({
const [name, setName] = useState(product.name);
const [description, setDescription] = useState(product.description ?? "");
const [cellMapping, setCellMapping] = useState>>(
- mappingFromCells(product.cells)
+ mappingFromCells(product.cells),
);
const handleSubmit = async (e: React.FormEvent) => {
@@ -92,7 +104,7 @@ function EditProductForm({
onSuccess();
} catch (error) {
toast.error(
- `Failed to update product: ${error instanceof Error ? error.message : "Unknown error"}`
+ `Failed to update product: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
};
@@ -101,7 +113,9 @@ function EditProductForm({
@@ -226,7 +242,9 @@ export function CreateProjectDialog() {
setFormData({ ...formData, test_command: e.target.value })}
+ onChange={(e) =>
+ setFormData({ ...formData, test_command: e.target.value })
+ }
placeholder="uv run pytest"
/>
@@ -236,7 +254,9 @@ export function CreateProjectDialog() {
setFormData({ ...formData, lint_command: e.target.value })}
+ onChange={(e) =>
+ setFormData({ ...formData, lint_command: e.target.value })
+ }
placeholder="uv run ruff check ."
/>
@@ -246,7 +266,12 @@ export function CreateProjectDialog() {
setFormData({ ...formData, format_command: e.target.value })}
+ onChange={(e) =>
+ setFormData({
+ ...formData,
+ format_command: e.target.value,
+ })
+ }
placeholder="uv run ruff format ."
/>
@@ -257,7 +282,10 @@ export function CreateProjectDialog() {
id="typecheck_command"
value={formData.typecheck_command || ""}
onChange={(e) =>
- setFormData({ ...formData, typecheck_command: e.target.value })
+ setFormData({
+ ...formData,
+ typecheck_command: e.target.value,
+ })
}
placeholder="uv run mypy src/"
/>
@@ -268,7 +296,12 @@ export function CreateProjectDialog() {
setFormData({ ...formData, build_command: e.target.value })}
+ onChange={(e) =>
+ setFormData({
+ ...formData,
+ build_command: e.target.value,
+ })
+ }
placeholder="pnpm build"
/>
@@ -279,20 +312,27 @@ export function CreateProjectDialog() {
id="quality_command"
value={formData.quality_command || ""}
onChange={(e) =>
- setFormData({ ...formData, quality_command: e.target.value })
+ setFormData({
+ ...formData,
+ quality_command: e.target.value,
+ })
}
placeholder="make gate"
/>
- Fast pre-submit gate (lint + types + complexity, no tests) run
- in the dev's workspace at hand-off to QA.
+ Fast pre-submit gate (lint + types + complexity, no tests)
+ run in the dev's workspace at hand-off to QA.
>
)}
- setOpen(false)}>
+ setOpen(false)}
+ >
Cancel
diff --git a/panel/src/components/projects/edit-project-dialog.tsx b/panel/src/components/projects/edit-project-dialog.tsx
index 082a1ae3..220b2e14 100644
--- a/panel/src/components/projects/edit-project-dialog.tsx
+++ b/panel/src/components/projects/edit-project-dialog.tsx
@@ -70,7 +70,9 @@ function EditProjectForm({
const [qualityCommand, setQualityCommand] = useState(
project.quality_command || "",
);
- const [ciWatchEnabled, setCiWatchEnabled] = useState(project.ci_watch_enabled);
+ const [ciWatchEnabled, setCiWatchEnabled] = useState(
+ project.ci_watch_enabled,
+ );
const [ciWatchWorkflow, setCiWatchWorkflow] = useState(
project.ci_watch_workflow || "",
);
diff --git a/panel/src/components/projects/project-selector.tsx b/panel/src/components/projects/project-selector.tsx
index de18f146..797adde7 100644
--- a/panel/src/components/projects/project-selector.tsx
+++ b/panel/src/components/projects/project-selector.tsx
@@ -101,7 +101,8 @@ export function ProjectSelector({
{selectedProject.name}
{selectedProject.assigned_cell && (
- {TEAM_LABELS[selectedProject.assigned_cell] || selectedProject.assigned_cell}
+ {TEAM_LABELS[selectedProject.assigned_cell] ||
+ selectedProject.assigned_cell}
)}
diff --git a/panel/src/components/projects/project-table.tsx b/panel/src/components/projects/project-table.tsx
index 1eda54a6..4fff48bf 100644
--- a/panel/src/components/projects/project-table.tsx
+++ b/panel/src/components/projects/project-table.tsx
@@ -74,7 +74,9 @@ export function ProjectTable({ projects, isLoading }: ProjectTableProps) {
No projects found
-
Create a project to get started with git integration
+
+ Create a project to get started with git integration
+
);
}
@@ -131,7 +133,9 @@ export function ProjectTable({ projects, isLoading }: ProjectTableProps) {
{getTokenBadge(project.has_git_token)}
{project.is_active ? (
- Active
+
+ Active
+
) : (
Inactive
@@ -148,7 +152,12 @@ export function ProjectTable({ projects, isLoading }: ProjectTableProps) {
>
-
+
-
+
{content}
@@ -78,8 +81,9 @@ export function ChatMessages({
What would you like to build?
- Describe your task idea. I'll help you refine it into a structured task with acceptance
- criteria ready to hand off to the team.
+ Describe your task idea. I'll help you refine it into a
+ structured task with acceptance criteria ready to hand off to the
+ team.
);
@@ -124,7 +128,7 @@ export function ChatMessages({
diff --git a/panel/src/components/prompter/draft-proposal-card.tsx b/panel/src/components/prompter/draft-proposal-card.tsx
index 5eb22daf..1a75c690 100644
--- a/panel/src/components/prompter/draft-proposal-card.tsx
+++ b/panel/src/components/prompter/draft-proposal-card.tsx
@@ -2,7 +2,13 @@
import { MessageCircle, Users, Rocket, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { CopyButton } from "@/components/ui/copy-button";
import type { DraftProposal } from "@/lib/api/prompter";
@@ -36,7 +42,7 @@ function draftToText(draft: DraftProposal): string {
lines.push(
"## What This Builds",
...draft.what_this_builds.map((b) => `- ${b}`),
- ""
+ "",
);
}
if (draft.the_work?.length) {
@@ -54,7 +60,7 @@ function draftToText(draft: DraftProposal): string {
lines.push(
"## Success Criteria",
...draft.acceptance_criteria.map((c) => `- ${c}`),
- ""
+ "",
);
}
return lines.join("\n").trim();
@@ -132,7 +138,9 @@ export function DraftProposalCard({
- {criterion}
+
+ {criterion}
+
))}
{draft.acceptance_criteria.length > 4 && (
@@ -170,7 +178,11 @@ export function DraftProposalCard({
Board review & Start
{/* Approve & Start → PENDING, straight to Main PM (skip the board) */}
- onStart("main_pm")} disabled={isLaunching}>
+ onStart("main_pm")}
+ disabled={isLaunching}
+ >
{isLaunching ? (
) : (
diff --git a/panel/src/components/prompter/success-card.tsx b/panel/src/components/prompter/success-card.tsx
index 8b95e151..98672935 100644
--- a/panel/src/components/prompter/success-card.tsx
+++ b/panel/src/components/prompter/success-card.tsx
@@ -3,7 +3,13 @@
import Link from "next/link";
import { CheckCircle2, ExternalLink, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import type { Team } from "@/types";
@@ -37,13 +43,19 @@ export function SuccessCard({
{team.replace("_", " ")}
- ID: {taskId.slice(0, 8)}…
+
+ ID: {taskId.slice(0, 8)}…
+
-
+
View Task
diff --git a/panel/src/components/providers.tsx b/panel/src/components/providers.tsx
index c413bfb7..fad4a4db 100644
--- a/panel/src/components/providers.tsx
+++ b/panel/src/components/providers.tsx
@@ -32,7 +32,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
retry: 1,
},
},
- })
+ }),
);
return (
diff --git a/panel/src/components/rate-limit/rate-limit-banner.tsx b/panel/src/components/rate-limit/rate-limit-banner.tsx
index 7c3ff8ea..95696ff5 100644
--- a/panel/src/components/rate-limit/rate-limit-banner.tsx
+++ b/panel/src/components/rate-limit/rate-limit-banner.tsx
@@ -14,14 +14,14 @@ import type { RateLimitEntry } from "@/types/rate-limits";
function computeSecondsLeft(resumeAt: string): number {
return Math.max(
0,
- Math.ceil((new Date(resumeAt).getTime() - Date.now()) / 1000)
+ Math.ceil((new Date(resumeAt).getTime() - Date.now()) / 1000),
);
}
function RateLimitRow({ entry }: { entry: RateLimitEntry }) {
const resumeAt = entry.resumeAt;
const [secondsLeft, setSecondsLeft] = useState(() =>
- computeSecondsLeft(resumeAt)
+ computeSecondsLeft(resumeAt),
);
useEffect(() => {
@@ -45,9 +45,7 @@ function RateLimitRow({ entry }: { entry: RateLimitEntry }) {
{agentCount} agent{agentCount !== 1 ? "s" : ""} affected
)}
-
- {secondsLeft}s
-
+ {secondsLeft}s
operations paused — resuming automatically
diff --git a/panel/src/components/settings/ai-routing-card.tsx b/panel/src/components/settings/ai-routing-card.tsx
index 6e46906f..c69d2eae 100644
--- a/panel/src/components/settings/ai-routing-card.tsx
+++ b/panel/src/components/settings/ai-routing-card.tsx
@@ -179,18 +179,22 @@ export function AIRoutingCard() {
const catalogForMix = catalog;
const catalogOllamaOnly = catalog.filter(
- (c: { provider_type: ModelProvider }) => c.provider_type === ModelProvider.OLLAMA_CLOUD,
+ (c: { provider_type: ModelProvider }) =>
+ c.provider_type === ModelProvider.OLLAMA_CLOUD,
);
const catalogGrokOnly = catalog.filter(
- (c: { provider_type: ModelProvider }) => c.provider_type === ModelProvider.GROK,
+ (c: { provider_type: ModelProvider }) =>
+ c.provider_type === ModelProvider.GROK,
);
const catalogAnthropicOnly = catalog.filter(
- (c: { provider_type: ModelProvider }) => c.provider_type === ModelProvider.ANTHROPIC,
+ (c: { provider_type: ModelProvider }) =>
+ c.provider_type === ModelProvider.ANTHROPIC,
);
// --- Mode toggle handlers ---
const flipToAnthropic = async () => {
- if (!confirm("Switch every agent to Anthropic? Clears any overrides.")) return;
+ if (!confirm("Switch every agent to Anthropic? Clears any overrides."))
+ return;
try {
await applyMode.mutateAsync({ mode: "anthropic" });
toast.success("All agents now on Anthropic");
@@ -274,8 +278,7 @@ export function AIRoutingCard() {
const needsKey = Object.values(per_agent).some((m) =>
catalog.find(
(c: { model_name: string; provider_type: ModelProvider }) =>
- c.model_name === m &&
- c.provider_type === ModelProvider.OLLAMA_CLOUD,
+ c.model_name === m && c.provider_type === ModelProvider.OLLAMA_CLOUD,
),
);
if (needsKey && !hasOllamaKey) {
@@ -315,7 +318,6 @@ export function AIRoutingCard() {
-
{/* -------- Grok (xAI) key -------- */}
@@ -386,7 +388,9 @@ export function AIRoutingCard() {
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={
- hasOllamaKey ? "•••••••••••• (leave blank to keep)" : "ollama_xxx…"
+ hasOllamaKey
+ ? "•••••••••••• (leave blank to keep)"
+ : "ollama_xxx…"
}
disabled={clearKey}
/>
@@ -487,8 +491,8 @@ export function AIRoutingCard() {
{currentMode === "mix" && !hasOllamaKey ? (
- Some agents may already be routed to Ollama but no key is
- saved — those agents will fall back to Anthropic at spawn.
+ Some agents may already be routed to Ollama but no key is saved —
+ those agents will fall back to Anthropic at spawn.
) : null}
{currentMode === "grok" || currentMode === "mix" ? (
@@ -522,7 +526,9 @@ export function AIRoutingCard() {
- (use server default)
+
+ (use server default)
+
{selfHostedModels.map((m) => (
{m.display_name}
@@ -544,17 +550,13 @@ export function AIRoutingCard() {
Per-agent override (mix mode)
-
+
{applyMode.isPending ? "Saving…" : "Save mix"}
- Leave a row blank to inherit from the global mode. Saving
- overwrites all per-agent overrides with what's picked here.
+ Leave a row blank to inherit from the global mode. Saving overwrites
+ all per-agent overrides with what's picked here.
{AGENTS.map((a) => (
@@ -564,9 +566,7 @@ export function AIRoutingCard() {
>
{a.slug}
-
- {a.label}
-
+
{a.label}
) : null}
- {description}
+
+ {description}
+
);
}
diff --git a/panel/src/components/settings/feature-flags-card.tsx b/panel/src/components/settings/feature-flags-card.tsx
index b8e9e3ce..148b1c34 100644
--- a/panel/src/components/settings/feature-flags-card.tsx
+++ b/panel/src/components/settings/feature-flags-card.tsx
@@ -18,8 +18,10 @@ import { toast } from "sonner";
// One-line blurb per flag so the operator knows what each master switch gates.
const FLAG_DESCRIPTIONS: Record = {
- external_pr_enabled: "Discover and review inbound external/fork pull requests.",
- internal_pr_enabled: "Run the read-only safety reviewer on internal branch PRs.",
+ external_pr_enabled:
+ "Discover and review inbound external/fork pull requests.",
+ internal_pr_enabled:
+ "Run the read-only safety reviewer on internal branch PRs.",
research_enabled: "Let the Board and PMs run web research.",
strategy_engine_enabled: "Generate and maintain company strategy artifacts.",
self_heal_enabled: "Watch RoboCo's own CI and notify you when it regresses.",
@@ -30,8 +32,10 @@ const FLAG_DESCRIPTIONS: Record = {
"Provision each agent workspace with the target project's Python (not RoboCo's) and block delivery gates when its test suite can't be executed.",
conventions_enabled:
"Enforce a per-project architectural standard (.roboco/conventions.yml): inject the map, attach baseline constraints, and block i_am_done / pr_pass on misplaced definitions or lint suppressions.",
- rag_auto_update_enabled: "Keep the knowledge base index refreshed automatically.",
- transcript_prune_enabled: "Run the background sweep that prunes old transcripts.",
+ rag_auto_update_enabled:
+ "Keep the knowledge base index refreshed automatically.",
+ transcript_prune_enabled:
+ "Run the background sweep that prunes old transcripts.",
gateway_health_enabled:
"Recover an agent whose MCP gateway has broken (it can run no tools) while its container stays up — kill + respawn it instead of shielding it from the reaper forever.",
ci_watch_enabled:
@@ -85,10 +89,14 @@ export function FeatureFlagsCard() {
{isLoading && (
- Loading feature flags…
+
+ Loading feature flags…
+
)}
{!isLoading && flags.length === 0 && (
- No feature flags available.
+
+ No feature flags available.
+
)}
{flags.map((flag, i) => (
diff --git a/panel/src/components/settings/self-hosted-section.tsx b/panel/src/components/settings/self-hosted-section.tsx
index e1f4f85a..eb76ce91 100644
--- a/panel/src/components/settings/self-hosted-section.tsx
+++ b/panel/src/components/settings/self-hosted-section.tsx
@@ -71,7 +71,7 @@ export function SelfHostedSection({
// Timestamps for "Last refreshed" label
const [lastRefreshed, setLastRefreshed] = useState
(null);
- const hasSavedUrl = !!(config?.base_url);
+ const hasSavedUrl = !!config?.base_url;
// ---- Save handler --------------------------------------------------------
const handleSave = async () => {
@@ -167,7 +167,7 @@ export function SelfHostedSection({
onChange={(e) => setBaseUrl(e.target.value)}
placeholder={
hasSavedUrl
- ? config.base_url ?? "http://localhost:11434"
+ ? (config.base_url ?? "http://localhost:11434")
: "http://localhost:11434"
}
className="font-mono text-sm"
@@ -317,9 +317,7 @@ export function SelfHostedSection({
Last refreshed:{" "}
-
- {relativeTime(lastRefreshed)}
-
+ {relativeTime(lastRefreshed)}
- Retention window (days)
+
+ Retention window (days)
+
-
+
{saveMutation.isPending ? "Saving..." : "Save"}
diff --git a/panel/src/components/tasks/acceptance-criteria-editor.tsx b/panel/src/components/tasks/acceptance-criteria-editor.tsx
index 95251f95..8e31b5fd 100644
--- a/panel/src/components/tasks/acceptance-criteria-editor.tsx
+++ b/panel/src/components/tasks/acceptance-criteria-editor.tsx
@@ -62,7 +62,9 @@ export function AcceptanceCriteriaEditor({
{criteria.map((criterion, index) => (
-
{index + 1}.
+
+ {index + 1}.
+
handleUpdate(index, e.target.value)}
@@ -91,7 +93,12 @@ export function AcceptanceCriteriaEditor({
placeholder="Enter acceptance criterion and press Enter..."
className="flex-1"
/>
-
+
Add
@@ -99,8 +106,8 @@ export function AcceptanceCriteriaEditor({
{/* Helper text */}
- Define at least one acceptance criterion. Each criterion should describe a specific,
- testable condition for task completion.
+ Define at least one acceptance criterion. Each criterion should describe
+ a specific, testable condition for task completion.
{/* Error message */}
diff --git a/panel/src/components/tasks/approve-and-start-button.tsx b/panel/src/components/tasks/approve-and-start-button.tsx
index ba118dc9..3503b077 100644
--- a/panel/src/components/tasks/approve-and-start-button.tsx
+++ b/panel/src/components/tasks/approve-and-start-button.tsx
@@ -82,7 +82,10 @@ export function ApproveAndStartButton({ task }: ApproveAndStartButtonProps) {
Approve & Start
-
(next ? setOpen(true) : closeDialog())}>
+ (next ? setOpen(true) : closeDialog())}
+ >
Approve & Start
@@ -121,7 +124,9 @@ export function ApproveAndStartButton({ task }: ApproveAndStartButtonProps) {
-
Approval notes (required)
+
+ Approval notes (required)
+
{/* Description */}
@@ -243,7 +254,10 @@ export function CreateTaskDialog() {
Status
- setStatus(v as TaskStatus)}>
+ setStatus(v as TaskStatus)}
+ >
@@ -276,7 +290,10 @@ export function CreateTaskDialog() {
Complexity
- setComplexity(v as Complexity)}>
+ setComplexity(v as Complexity)}
+ >
@@ -291,7 +308,10 @@ export function CreateTaskDialog() {
Nature
-
setNature(v as TaskNature)}>
+ setNature(v as TaskNature)}
+ >
@@ -322,7 +342,11 @@ export function CreateTaskDialog() {
{/* Advanced Options */}
-
+
Advanced Options
{advancedOpen ? (
@@ -356,7 +380,8 @@ export function CreateTaskDialog() {
filterByTeam={team}
/>
- Leave unassigned to let the orchestrator route automatically, or manually assign to a specific agent
+ Leave unassigned to let the orchestrator route automatically,
+ or manually assign to a specific agent
@@ -364,13 +389,18 @@ export function CreateTaskDialog() {
- Git & Work Configuration
+
+ Git & Work Configuration
+
{/* Task Type */}
Task Type
-
setTaskType(v as TaskType)}>
+ setTaskType(v as TaskType)}
+ >
@@ -396,11 +426,14 @@ export function CreateTaskDialog() {
placeholder="Select project..."
/>
{errors.project_id && (
- {errors.project_id}
+
+ {errors.project_id}
+
)}
- The repo this task targets. Optional if you pick a Product below — a
- fan-out task routes each cell's subtask via the Product instead.
+ The repo this task targets. Optional if you pick a Product
+ below — a fan-out task routes each cell's subtask via
+ the Product instead.
@@ -415,7 +448,9 @@ export function CreateTaskDialog() {
- None (single project)
+
+ None (single project)
+
{products.map((p) => (
{p.name}
@@ -424,8 +459,8 @@ export function CreateTaskDialog() {
- Optional. When set, delegated subtasks route to each cell's
- mapped project (manage these in Products).
+ Optional. When set, delegated subtasks route to each
+ cell's mapped project (manage these in Products).
diff --git a/panel/src/components/tasks/dependency-selector.tsx b/panel/src/components/tasks/dependency-selector.tsx
index 63b0dcb3..687c3278 100644
--- a/panel/src/components/tasks/dependency-selector.tsx
+++ b/panel/src/components/tasks/dependency-selector.tsx
@@ -9,7 +9,11 @@ import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
-import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
import { Search, X, Link2 } from "lucide-react";
interface DependencySelectorProps {
@@ -32,14 +36,14 @@ export function DependencySelector({
(task) =>
task.id !== excludeTaskId &&
task.status !== TaskStatus.COMPLETED &&
- task.status !== TaskStatus.CANCELLED
+ task.status !== TaskStatus.CANCELLED,
);
// Filter by search term
const filteredTasks = availableTasks.filter(
(task) =>
task.title.toLowerCase().includes(search.toLowerCase()) ||
- task.id.toLowerCase().includes(search.toLowerCase())
+ task.id.toLowerCase().includes(search.toLowerCase()),
);
// Get selected task objects
@@ -65,7 +69,10 @@ export function DependencySelector({
{selectedTasks.length > 0 && (
{selectedTasks.map((task) => (
-
+
{task.title}
@@ -90,7 +97,11 @@ export function DependencySelector({
{/* Add dependency popover */}
-
+
Search tasks to add as dependencies...
@@ -133,10 +144,15 @@ export function DependencySelector({
{task.title}
-
+
{task.id.slice(0, 8)}
- {task.status.replace(/_/g, " ")}
+
+ {task.status.replace(/_/g, " ")}
+
diff --git a/panel/src/components/tasks/docs-status-badge.tsx b/panel/src/components/tasks/docs-status-badge.tsx
index ed2bdddb..c7983087 100644
--- a/panel/src/components/tasks/docs-status-badge.tsx
+++ b/panel/src/components/tasks/docs-status-badge.tsx
@@ -30,7 +30,7 @@ export function DocsStatusBadge({
"text-xs",
docsComplete
? "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300"
- : "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300"
+ : "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300",
)}
>
@@ -44,7 +44,7 @@ export function DocsStatusBadge({
"text-xs",
prCreated
? "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300"
- : "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300"
+ : "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300",
)}
>
diff --git a/panel/src/components/tasks/edit-task-dialog.tsx b/panel/src/components/tasks/edit-task-dialog.tsx
index 4ba2a501..e09f2076 100644
--- a/panel/src/components/tasks/edit-task-dialog.tsx
+++ b/panel/src/components/tasks/edit-task-dialog.tsx
@@ -69,18 +69,34 @@ interface EditTaskDialogProps {
}
// Inner component that resets when task.id changes via key
-function EditTaskDialogInner({ task, onOpenChange }: { task: Task; onOpenChange: (open: boolean) => void }) {
+function EditTaskDialogInner({
+ task,
+ onOpenChange,
+}: {
+ task: Task;
+ onOpenChange: (open: boolean) => void;
+}) {
const [title, setTitle] = useState(task.title);
const [description, setDescription] = useState(task.description);
const [team, setTeam] = useState(task.team);
const [priority, setPriority] = useState(task.priority);
- const [complexity, setComplexity] = useState(task.estimated_complexity);
- const [nature, setNature] = useState(task.nature ?? TaskNature.TECHNICAL);
- const [taskType, setTaskType] = useState(task.task_type ?? TaskType.CODE);
+ const [complexity, setComplexity] = useState(
+ task.estimated_complexity,
+ );
+ const [nature, setNature] = useState(
+ task.nature ?? TaskNature.TECHNICAL,
+ );
+ const [taskType, setTaskType] = useState(
+ task.task_type ?? TaskType.CODE,
+ );
const [projectId, setProjectId] = useState(task.project_id ?? "");
- const [assignedTo, setAssignedTo] = useState(task.assigned_to ?? null);
+ const [assignedTo, setAssignedTo] = useState(
+ task.assigned_to ?? null,
+ );
const [targetDate, setTargetDate] = useState(
- task.target_date ? new Date(task.target_date).toISOString().slice(0, 16) : ""
+ task.target_date
+ ? new Date(task.target_date).toISOString().slice(0, 16)
+ : "",
);
const [advancedOpen, setAdvancedOpen] = useState(false);
@@ -189,7 +205,10 @@ function EditTaskDialogInner({ task, onOpenChange }: { task: Task; onOpenChange:
Complexity
- setComplexity(v as Complexity)}>
+ setComplexity(v as Complexity)}
+ >
@@ -204,7 +223,10 @@ function EditTaskDialogInner({ task, onOpenChange }: { task: Task; onOpenChange:
Nature
-
setNature(v as TaskNature)}>
+ setNature(v as TaskNature)}
+ >
@@ -222,7 +244,11 @@ function EditTaskDialogInner({ task, onOpenChange }: { task: Task; onOpenChange:
{/* Advanced Options */}
-
+
Advanced Options
{advancedOpen ? (
@@ -257,13 +283,18 @@ function EditTaskDialogInner({ task, onOpenChange }: { task: Task; onOpenChange:
- Git & Work Configuration
+
+ Git & Work Configuration
+
{/* Task Type */}
Task Type
-
setTaskType(v as TaskType)}>
+ setTaskType(v as TaskType)}
+ >
@@ -316,7 +347,17 @@ function EditTaskDialogInner({ task, onOpenChange }: { task: Task; onOpenChange:
}
// Wrapper component that uses key to reset form when task changes
-export function EditTaskDialog({ task, open, onOpenChange }: EditTaskDialogProps) {
+export function EditTaskDialog({
+ task,
+ open,
+ onOpenChange,
+}: EditTaskDialogProps) {
if (!open) return null;
- return ;
+ return (
+
+ );
}
diff --git a/panel/src/components/tasks/markdown-editor.tsx b/panel/src/components/tasks/markdown-editor.tsx
index a4342423..decea516 100644
--- a/panel/src/components/tasks/markdown-editor.tsx
+++ b/panel/src/components/tasks/markdown-editor.tsx
@@ -34,7 +34,10 @@ export function MarkdownEditor({
{label} {required && * }
- setMode(v as "write" | "preview")}>
+ setMode(v as "write" | "preview")}
+ >
@@ -60,7 +63,9 @@ export function MarkdownEditor({
{value ? (
{value}
) : (
- Nothing to preview
+
+ Nothing to preview
+
)}
)}
diff --git a/panel/src/components/tasks/task-actions.tsx b/panel/src/components/tasks/task-actions.tsx
index 1e0a2601..907b4a8d 100644
--- a/panel/src/components/tasks/task-actions.tsx
+++ b/panel/src/components/tasks/task-actions.tsx
@@ -22,7 +22,17 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
-import { MoreHorizontal, Play, Pause, CheckCircle, XCircle, Pencil, Trash2, Clock, MessageSquare } from "lucide-react";
+import {
+ MoreHorizontal,
+ Play,
+ Pause,
+ CheckCircle,
+ XCircle,
+ Pencil,
+ Trash2,
+ Clock,
+ MessageSquare,
+} from "lucide-react";
import { toast } from "sonner";
import { EditTaskDialog } from "./edit-task-dialog";
import { RequiredNotesDialog } from "./task-detail/task-action-dialogs";
@@ -155,7 +165,10 @@ export function TaskActions({
Awaiting PM Activation
{!hasSessions && (
-
+
Needs session created
@@ -202,26 +215,32 @@ export function TaskActions({
)}
- {task.status !== TaskStatus.COMPLETED &&
- task.status !== TaskStatus.CANCELLED &&
- task.status !== TaskStatus.BACKLOG && (
- <>
-
+ {task.status !== TaskStatus.COMPLETED &&
+ task.status !== TaskStatus.CANCELLED &&
+ task.status !== TaskStatus.BACKLOG && (
+ <>
+
-
handleAction("complete")}>
-
- Complete
-
-
handleAction("cancel")} className="text-orange-600">
-
- Cancel
-
- >
- )}
+
handleAction("complete")}>
+
+ Complete
+
+
handleAction("cancel")}
+ className="text-orange-600"
+ >
+
+ Cancel
+
+ >
+ )}
{/* Cancel for backlog tasks (allowed - can remove from backlog) */}
{isBacklog && (
-
handleAction("cancel")} className="text-orange-600">
+ handleAction("cancel")}
+ className="text-orange-600"
+ >
Cancel
@@ -244,11 +263,7 @@ export function TaskActions({
{/* Edit Dialog */}
-
+
{/* Complete Dialog */}
Delete Task?
- This will permanently delete "{task.title}". This action cannot be undone.
+ This will permanently delete "{task.title}". This action
+ cannot be undone.
diff --git a/panel/src/components/tasks/task-detail/acceptance-criteria.tsx b/panel/src/components/tasks/task-detail/acceptance-criteria.tsx
index 04d8c583..5910b547 100644
--- a/panel/src/components/tasks/task-detail/acceptance-criteria.tsx
+++ b/panel/src/components/tasks/task-detail/acceptance-criteria.tsx
@@ -44,7 +44,9 @@ export function AcceptanceCriteria({ task }: AcceptanceCriteriaProps) {
}, [editingIndex]);
// Parse criterion to get text and completion status
- const parseCriterion = (criterion: string): { text: string; completed: boolean } => {
+ const parseCriterion = (
+ criterion: string,
+ ): { text: string; completed: boolean } => {
if (criterion.startsWith("[x]") || criterion.startsWith("[X]")) {
return { text: criterion.slice(3).trim(), completed: true };
}
@@ -62,7 +64,7 @@ export function AcceptanceCriteria({ task }: AcceptanceCriteriaProps) {
// Count completed criteria
const completedCount = criteria.filter(
- (c) => c.startsWith("[x]") || c.startsWith("[X]")
+ (c) => c.startsWith("[x]") || c.startsWith("[X]"),
).length;
// Toggle criterion completion
@@ -89,7 +91,10 @@ export function AcceptanceCriteria({ task }: AcceptanceCriteriaProps) {
return;
}
- const newCriteria = [...criteria, formatCriterion(newCriterion.trim(), false)];
+ const newCriteria = [
+ ...criteria,
+ formatCriterion(newCriterion.trim(), false),
+ ];
try {
await updateTask.mutateAsync({
diff --git a/panel/src/components/tasks/task-detail/progress-timeline.tsx b/panel/src/components/tasks/task-detail/progress-timeline.tsx
index a1e7469d..17dadce5 100644
--- a/panel/src/components/tasks/task-detail/progress-timeline.tsx
+++ b/panel/src/components/tasks/task-detail/progress-timeline.tsx
@@ -33,7 +33,7 @@ function formatTime(timestamp: string): string {
export function ProgressTimeline({ updates }: ProgressTimelineProps) {
// Sort by most recent first
const sortedUpdates = [...updates].sort(
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
);
// Get latest percentage if available
@@ -61,7 +61,9 @@ export function ProgressTimeline({ updates }: ProgressTimelineProps) {
{sortedUpdates.length === 0 ? (
- No progress updates yet.
+
+ No progress updates yet.
+
) : (
{/* Timeline line */}
@@ -89,7 +91,10 @@ export function ProgressTimeline({ updates }: ProgressTimelineProps) {
{update.percentage !== null && (
-
+
{update.percentage}%
diff --git a/panel/src/components/tasks/task-detail/subtasks-list.tsx b/panel/src/components/tasks/task-detail/subtasks-list.tsx
index 6093ca04..88275ecf 100644
--- a/panel/src/components/tasks/task-detail/subtasks-list.tsx
+++ b/panel/src/components/tasks/task-detail/subtasks-list.tsx
@@ -16,21 +16,36 @@ interface SubtasksListProps {
// Status colors for badges
const STATUS_COLORS: Record
= {
- [TaskStatus.BACKLOG]: "bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400",
- [TaskStatus.PENDING]: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300",
- [TaskStatus.CLAIMED]: "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
- [TaskStatus.IN_PROGRESS]: "bg-blue-200 text-blue-800 dark:bg-blue-800 dark:text-blue-200",
- [TaskStatus.BLOCKED]: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
- [TaskStatus.PAUSED]: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300",
- [TaskStatus.VERIFYING]: "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300",
- [TaskStatus.NEEDS_REVISION]: "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
- [TaskStatus.AWAITING_QA]: "bg-yellow-200 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-200",
- [TaskStatus.AWAITING_DOCUMENTATION]: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-300",
- [TaskStatus.AWAITING_PR_REVIEW]: "bg-teal-100 text-teal-700 dark:bg-teal-900 dark:text-teal-300",
- [TaskStatus.AWAITING_PM_REVIEW]: "bg-orange-200 text-orange-800 dark:bg-orange-800 dark:text-orange-200",
- [TaskStatus.AWAITING_CEO_APPROVAL]: "bg-amber-200 text-amber-800 dark:bg-amber-800 dark:text-amber-200",
- [TaskStatus.COMPLETED]: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
- [TaskStatus.CANCELLED]: "bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-400",
+ [TaskStatus.BACKLOG]:
+ "bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400",
+ [TaskStatus.PENDING]:
+ "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300",
+ [TaskStatus.CLAIMED]:
+ "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
+ [TaskStatus.IN_PROGRESS]:
+ "bg-blue-200 text-blue-800 dark:bg-blue-800 dark:text-blue-200",
+ [TaskStatus.BLOCKED]:
+ "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
+ [TaskStatus.PAUSED]:
+ "bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300",
+ [TaskStatus.VERIFYING]:
+ "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300",
+ [TaskStatus.NEEDS_REVISION]:
+ "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
+ [TaskStatus.AWAITING_QA]:
+ "bg-yellow-200 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-200",
+ [TaskStatus.AWAITING_DOCUMENTATION]:
+ "bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-300",
+ [TaskStatus.AWAITING_PR_REVIEW]:
+ "bg-teal-100 text-teal-700 dark:bg-teal-900 dark:text-teal-300",
+ [TaskStatus.AWAITING_PM_REVIEW]:
+ "bg-orange-200 text-orange-800 dark:bg-orange-800 dark:text-orange-200",
+ [TaskStatus.AWAITING_CEO_APPROVAL]:
+ "bg-amber-200 text-amber-800 dark:bg-amber-800 dark:text-amber-200",
+ [TaskStatus.COMPLETED]:
+ "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
+ [TaskStatus.CANCELLED]:
+ "bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-400",
};
export function SubtasksList({ task }: SubtasksListProps) {
@@ -38,10 +53,11 @@ export function SubtasksList({ task }: SubtasksListProps) {
// Calculate completion stats
const completedCount = subtasks.filter(
- (t) => t.status === TaskStatus.COMPLETED
+ (t) => t.status === TaskStatus.COMPLETED,
).length;
const totalCount = subtasks.length;
- const completionPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
+ const completionPercent =
+ totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
return (
diff --git a/panel/src/components/tasks/task-detail/tab-commits.tsx b/panel/src/components/tasks/task-detail/tab-commits.tsx
index 4edd3629..b29db557 100644
--- a/panel/src/components/tasks/task-detail/tab-commits.tsx
+++ b/panel/src/components/tasks/task-detail/tab-commits.tsx
@@ -7,7 +7,17 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
-import { GitCommit, GitBranch, ExternalLink, Clock, User, Plus, Trash2, X, Check } from "lucide-react";
+import {
+ GitCommit,
+ GitBranch,
+ ExternalLink,
+ Clock,
+ User,
+ Plus,
+ Trash2,
+ X,
+ Check,
+} from "lucide-react";
import { toast } from "sonner";
import { getAgentDisplayName } from "@/lib/agent-utils";
@@ -47,7 +57,7 @@ export function TabCommits({ task }: TabCommitsProps) {
// Sort commits by timestamp (newest first)
const sortedCommits = [...commits].sort(
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
);
const handleAdd = async () => {
@@ -112,18 +122,20 @@ export function TabCommits({ task }: TabCommitsProps) {
{!isAdding && (
-
setIsAdding(true)}>
- Link
+ setIsAdding(true)}
+ >
+
+ Link
)}
-
{/* Add new commit form */}
{isAdding && (
@@ -208,7 +243,10 @@ export function TabCommits({ task }: TabCommitsProps) {
{/* Meta info */}
{/* Hash */}
-
+
{commit.hash.slice(0, 7)}
diff --git a/panel/src/components/tasks/task-detail/tab-dependencies.tsx b/panel/src/components/tasks/task-detail/tab-dependencies.tsx
index 904346ee..3c869579 100644
--- a/panel/src/components/tasks/task-detail/tab-dependencies.tsx
+++ b/panel/src/components/tasks/task-detail/tab-dependencies.tsx
@@ -7,7 +7,17 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
-import { ArrowUp, Link2, AlertTriangle, Plus, Trash2, X, Check, Hash, Pencil } from "lucide-react";
+import {
+ ArrowUp,
+ Link2,
+ AlertTriangle,
+ Plus,
+ Trash2,
+ X,
+ Check,
+ Hash,
+ Pencil,
+} from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
@@ -78,7 +88,9 @@ function DependencyList({
setNewId("");
// Keep adding mode open for quick entry
} catch {
- toast.error(`Failed to add ${field === "dependency_ids" ? "dependency" : "blocker"}`);
+ toast.error(
+ `Failed to add ${field === "dependency_ids" ? "dependency" : "blocker"}`,
+ );
}
};
@@ -91,7 +103,9 @@ function DependencyList({
updates: { [field]: newIds },
});
} catch {
- toast.error(`Failed to remove ${field === "dependency_ids" ? "dependency" : "blocker"}`);
+ toast.error(
+ `Failed to remove ${field === "dependency_ids" ? "dependency" : "blocker"}`,
+ );
}
};
@@ -118,11 +132,7 @@ function DependencyList({
)}
{!isAdding && (
- setIsAdding(true)}
- >
+ setIsAdding(true)}>
Add
@@ -141,10 +151,14 @@ function DependencyList({
{ids.map((depId) => (
-
+
-
{depId.slice(0, 8)}...
+
+ {depId.slice(0, 8)}...
+
{badgeLabel}
@@ -212,7 +226,9 @@ export function TabDependencies({ task }: TabDependenciesProps) {
const parentInputRef = useRef(null);
// Display prop value when not editing, local value when editing
- const parentValue = editingParent ? localParentValue : (task.parent_task_id ?? "");
+ const parentValue = editingParent
+ ? localParentValue
+ : (task.parent_task_id ?? "");
const setParentValue = (value: string) => setLocalParentValue(value);
// Start editing - copy current prop value to local state
@@ -389,8 +405,14 @@ export function TabDependencies({ task }: TabDependenciesProps) {
title="Click to edit"
>
- e.stopPropagation()}>
- {task.parent_task_id.slice(0, 8)}...
+ e.stopPropagation()}
+ >
+
+ {task.parent_task_id.slice(0, 8)}...
+
View Parent
diff --git a/panel/src/components/tasks/task-detail/tab-notes.tsx b/panel/src/components/tasks/task-detail/tab-notes.tsx
index 90ec5f15..a273134c 100644
--- a/panel/src/components/tasks/task-detail/tab-notes.tsx
+++ b/panel/src/components/tasks/task-detail/tab-notes.tsx
@@ -77,7 +77,8 @@ function prReviewCardBg(task: Task): string {
"bg-green-50 dark:bg-green-950 border border-green-200 dark:border-green-800",
changes_requested:
"bg-amber-50 dark:bg-amber-950 border border-amber-200 dark:border-amber-800",
- failed: "bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800",
+ failed:
+ "bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800",
};
return (
(verdict ? map[verdict] : undefined) ??
diff --git a/panel/src/components/tasks/task-detail/tab-overview.tsx b/panel/src/components/tasks/task-detail/tab-overview.tsx
index e67a5919..a529a3ee 100644
--- a/panel/src/components/tasks/task-detail/tab-overview.tsx
+++ b/panel/src/components/tasks/task-detail/tab-overview.tsx
@@ -66,27 +66,31 @@ export function TabOverview({ task }: TabOverviewProps) {
- Self Verified:
+
+ Self Verified:
+
{task.self_verified ? "Yes" : "No"}
- QA Verified:
+
+ QA Verified:
+
{task.qa_verified === true
? "Passed"
: task.qa_verified === false
- ? "Failed"
- : "Pending"}
+ ? "Failed"
+ : "Pending"}
diff --git a/panel/src/components/tasks/task-detail/tab-plan.tsx b/panel/src/components/tasks/task-detail/tab-plan.tsx
index 8fa74e3f..28ae9b2d 100644
--- a/panel/src/components/tasks/task-detail/tab-plan.tsx
+++ b/panel/src/components/tasks/task-detail/tab-plan.tsx
@@ -40,7 +40,8 @@ interface TabPlanProps {
// Risk severity colors
const severityColors: Record = {
low: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
- medium: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300",
+ medium:
+ "bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300",
high: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
};
@@ -110,24 +111,38 @@ function ApproachSection({ task, plan }: { task: Task; plan: TaskPlan }) {
{isEditing ? (
- setEditMode(v as "write" | "preview")}>
+ setEditMode(v as "write" | "preview")}
+ >
- Write
+
+ Write
- Preview
+
+ Preview
-
- e.preventDefault()} disabled={updateTask.isPending}>
- Save
+
+
+
+ e.preventDefault()}
+ disabled={updateTask.isPending}
+ >
+
+ Save
) : (
- Edit
+
+ Edit
)}
@@ -146,13 +161,24 @@ function ApproachSection({ task, plan }: { task: Task; plan: TaskPlan }) {
/>
) : (
- {editValue ?
{editValue} :
Nothing to preview
}
+ {editValue ? (
+
{editValue}
+ ) : (
+
+ Nothing to preview
+
+ )}
)}
-
Markdown supported. Ctrl/Cmd + Enter to save.
+
+ Markdown supported. Ctrl/Cmd + Enter to save.
+
) : (
-
+
{plan.approach}
)}
@@ -202,7 +228,7 @@ function SubTasksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
const handleToggle = async (id: string) => {
const newSubTasks = subTasks.map((st) =>
- st.id === id ? { ...st, completed: !st.completed } : st
+ st.id === id ? { ...st, completed: !st.completed } : st,
);
await updatePlan(newSubTasks);
};
@@ -231,7 +257,7 @@ function SubTasksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
return;
}
const newSubTasks = subTasks.map((st) =>
- st.id === id ? { ...st, title: editTitle.trim() } : st
+ st.id === id ? { ...st, title: editTitle.trim() } : st,
);
await updatePlan(newSubTasks);
setEditingId(null);
@@ -251,10 +277,17 @@ function SubTasksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
Sub-Tasks
-
{completedCount}/{subTasks.length} completed
+
+ {completedCount}/{subTasks.length} completed
+
{!isAdding && (
-
setIsAdding(true)}>
- Add
+ setIsAdding(true)}
+ >
+
+ Add
)}
@@ -265,7 +298,10 @@ function SubTasksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
{subTasks
.sort((a, b) => a.order - b.order)
.map((subtask) => (
-
+
handleToggle(subtask.id)}
@@ -289,7 +325,9 @@ function SubTasksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
<>
{
setEditingId(subtask.id);
@@ -298,7 +336,9 @@ function SubTasksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
>
{subtask.title}
{subtask.estimated_hours && (
- ~{subtask.estimated_hours}h
+
+ ~{subtask.estimated_hours}h
+
)}
- { setNewTitle(""); setIsAdding(false); }} className="h-7 w-7 p-0">
+ {
+ setNewTitle("");
+ setIsAdding(false);
+ }}
+ className="h-7 w-7 p-0"
+ >
- e.preventDefault()} disabled={!newTitle.trim()} className="h-7 w-7 p-0">
+ e.preventDefault()}
+ disabled={!newTitle.trim()}
+ className="h-7 w-7 p-0"
+ >
)}
{subTasks.length === 0 && !isAdding && (
-
setIsAdding(true)}>
+
setIsAdding(true)}
+ >
No sub-tasks. Click to add one.
)}
@@ -352,7 +409,13 @@ function SubTasksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
// ============================================================================
// Technical Considerations Section
// ============================================================================
-function TechConsiderationsSection({ task, plan }: { task: Task; plan: TaskPlan }) {
+function TechConsiderationsSection({
+ task,
+ plan,
+}: {
+ task: Task;
+ plan: TaskPlan;
+}) {
const updateTask = useUpdateTask();
const items = plan.technical_considerations;
@@ -422,7 +485,8 @@ function TechConsiderationsSection({ task, plan }: { task: Task; plan: TaskPlan
{!isAdding && (
setIsAdding(true)}>
- Add
+
+ Add
)}
@@ -486,17 +550,34 @@ function TechConsiderationsSection({ task, plan }: { task: Task; plan: TaskPlan
placeholder="Add consideration..."
className="h-8 text-sm flex-1"
/>
-
{ setNewItem(""); setIsAdding(false); }} className="h-7 w-7 p-0">
+ {
+ setNewItem("");
+ setIsAdding(false);
+ }}
+ className="h-7 w-7 p-0"
+ >
- e.preventDefault()} disabled={!newItem.trim()} className="h-7 w-7 p-0">
+ e.preventDefault()}
+ disabled={!newItem.trim()}
+ className="h-7 w-7 p-0"
+ >
)}
{items.length === 0 && !isAdding && (
- setIsAdding(true)}>
+
setIsAdding(true)}
+ >
No technical considerations. Click to add one.
)}
@@ -550,7 +631,14 @@ function RisksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
setIsAdding(false);
return;
}
- await updatePlan([...risks, { description: newDesc.trim(), mitigation: newMit.trim(), severity: newSeverity }]);
+ await updatePlan([
+ ...risks,
+ {
+ description: newDesc.trim(),
+ mitigation: newMit.trim(),
+ severity: newSeverity,
+ },
+ ]);
setNewDesc("");
setNewMit("");
setNewSeverity("medium");
@@ -563,7 +651,11 @@ function RisksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
return;
}
const newRisks = [...risks];
- newRisks[editingIdx] = { description: editDesc.trim(), mitigation: editMit.trim(), severity: editSeverity };
+ newRisks[editingIdx] = {
+ description: editDesc.trim(),
+ mitigation: editMit.trim(),
+ severity: editSeverity,
+ };
await updatePlan(newRisks);
setEditingIdx(null);
};
@@ -584,7 +676,8 @@ function RisksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
{!isAdding && (
setIsAdding(true)}>
- Add
+
+ Add
)}
@@ -619,8 +712,20 @@ function RisksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
- setEditingIdx(null)}>
- e.preventDefault()}>
+ setEditingIdx(null)}
+ >
+
+
+ e.preventDefault()}
+ >
+
+
) : (
@@ -635,13 +740,22 @@ function RisksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
}}
>
-
{risk.description}
+
+ {risk.description}
+
- {risk.severity && {risk.severity} }
+ {risk.severity && (
+
+ {risk.severity}
+
+ )}
{ e.stopPropagation(); handleDelete(idx); }}
+ onClick={(e) => {
+ e.stopPropagation();
+ handleDelete(idx);
+ }}
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 text-destructive"
>
@@ -649,10 +763,11 @@ function RisksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
- Mitigation: {risk.mitigation || "Not specified"}
+ Mitigation: {" "}
+ {risk.mitigation || "Not specified"}
- )
+ ),
)}
{isAdding && (
@@ -681,14 +796,34 @@ function RisksSection({ task, plan }: { task: Task; plan: TaskPlan }) {
-
{ setNewDesc(""); setNewMit(""); setIsAdding(false); }}>
-
e.preventDefault()} disabled={!newDesc.trim()}>
+
{
+ setNewDesc("");
+ setNewMit("");
+ setIsAdding(false);
+ }}
+ >
+
+
+
e.preventDefault()}
+ disabled={!newDesc.trim()}
+ >
+
+
)}
{risks.length === 0 && !isAdding && (
- setIsAdding(true)}>
+
setIsAdding(true)}
+ >
No risks identified. Click to add one.
)}
@@ -737,7 +872,15 @@ function OpenQuestionsSection({ task, plan }: { task: Task; plan: TaskPlan }) {
setIsAdding(false);
return;
}
- await updatePlan([...questions, { question: newQuestion.trim(), answer: null, answered_by: null, answered_at: null }]);
+ await updatePlan([
+ ...questions,
+ {
+ question: newQuestion.trim(),
+ answer: null,
+ answered_by: null,
+ answered_at: null,
+ },
+ ]);
setNewQuestion("");
};
@@ -775,7 +918,8 @@ function OpenQuestionsSection({ task, plan }: { task: Task; plan: TaskPlan }) {
{!isAdding && (
setIsAdding(true)}>
- Add
+
+ Add
)}
@@ -800,8 +944,20 @@ function OpenQuestionsSection({ task, plan }: { task: Task; plan: TaskPlan }) {
/>
-
setEditingIdx(null)}>
-
e.preventDefault()}>
+
setEditingIdx(null)}
+ >
+
+
+
e.preventDefault()}
+ >
+
+
) : (
@@ -816,11 +972,16 @@ function OpenQuestionsSection({ task, plan }: { task: Task; plan: TaskPlan }) {
>
-
{q.question}
+
+ {q.question}
+
{ e.stopPropagation(); handleDelete(idx); }}
+ onClick={(e) => {
+ e.stopPropagation();
+ handleDelete(idx);
+ }}
className="h-7 w-7 p-0 opacity-0 group-hover:opacity-100 text-destructive"
>
@@ -829,17 +990,24 @@ function OpenQuestionsSection({ task, plan }: { task: Task; plan: TaskPlan }) {
{q.answer ? (
-
Answered by {q.answered_by?.slice(0, 12) ?? "Unknown"}
+
+ Answered by {q.answered_by?.slice(0, 12) ?? "Unknown"}
+
{q.answer}
) : (
- Awaiting Answer
+
+ Awaiting Answer
+
)}
- )
+ ),
)}
{isAdding && (
@@ -859,14 +1027,33 @@ function OpenQuestionsSection({ task, plan }: { task: Task; plan: TaskPlan }) {
/>
-
{ setNewQuestion(""); setIsAdding(false); }}>
-
e.preventDefault()} disabled={!newQuestion.trim()}>
+
{
+ setNewQuestion("");
+ setIsAdding(false);
+ }}
+ >
+
+
+
e.preventDefault()}
+ disabled={!newQuestion.trim()}
+ >
+
+
)}
{questions.length === 0 && !isAdding && (
-
setIsAdding(true)}>
+
setIsAdding(true)}
+ >
No open questions. Click to add one.
)}
@@ -909,7 +1096,8 @@ export function TabPlan({ task }: TabPlanProps) {
No implementation plan has been created yet.
- A plan will be created once an agent claims and starts working on this task.
+ A plan will be created once an agent claims and starts working on
+ this task.
diff --git a/panel/src/components/tasks/task-detail/tab-progress.tsx b/panel/src/components/tasks/task-detail/tab-progress.tsx
index d2767c32..728990ab 100644
--- a/panel/src/components/tasks/task-detail/tab-progress.tsx
+++ b/panel/src/components/tasks/task-detail/tab-progress.tsx
@@ -71,7 +71,7 @@ function ProgressUpdatesSection({ task }: { task: Task }) {
// Sort by most recent first
const sortedUpdates = [...updates].sort(
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
);
const latestWithPercentage = sortedUpdates.find((u) => u.percentage !== null);
@@ -107,7 +107,7 @@ function ProgressUpdatesSection({ task }: { task: Task }) {
// Find the actual index in unsorted array
const update = sortedUpdates[idx];
const actualIdx = updates.findIndex(
- (u) => u.timestamp === update.timestamp && u.message === update.message
+ (u) => u.timestamp === update.timestamp && u.message === update.message,
);
if (actualIdx === -1) return;
@@ -133,8 +133,13 @@ function ProgressUpdatesSection({ task }: { task: Task }) {
{updates.length} update{updates.length !== 1 ? "s" : ""}
{!isAdding && (
- setIsAdding(true)}>
- Add
+ setIsAdding(true)}
+ >
+
+ Add
)}
@@ -171,11 +176,24 @@ function ProgressUpdatesSection({ task }: { task: Task }) {
max="100"
/>
- { setNewMessage(""); setNewPercentage(""); setIsAdding(false); }}>
+ {
+ setNewMessage("");
+ setNewPercentage("");
+ setIsAdding(false);
+ }}
+ >
-
- Add Update
+
+
+ Add Update
@@ -225,7 +243,10 @@ function ProgressUpdatesSection({ task }: { task: Task }) {
{update.percentage !== null && (
-
+
{update.percentage}%
@@ -263,7 +284,7 @@ function CheckpointsSection({ task }: { task: Task }) {
// Sort by timestamp (newest first)
const sortedCheckpoints = [...checkpoints].sort(
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
);
const handleAdd = async () => {
@@ -324,8 +345,13 @@ function CheckpointsSection({ task }: { task: Task }) {
{checkpoints.length} saved
{!isAdding && (
-
setIsAdding(true)}>
- Add
+ setIsAdding(true)}
+ >
+
+ Add
)}
@@ -336,7 +362,9 @@ function CheckpointsSection({ task }: { task: Task }) {
{isAdding && (
- State Summary
+
+ State Summary
+
- Remaining Work (one per line)
+
+ Remaining Work (one per line)
+
- Notes (optional)
+
+ Notes (optional)
+
setNewNotes(e.target.value)}
@@ -365,11 +397,25 @@ function CheckpointsSection({ task }: { task: Task }) {
-
{ setNewSummary(""); setNewRemaining(""); setNewNotes(""); setIsAdding(false); }}>
+ {
+ setNewSummary("");
+ setNewRemaining("");
+ setNewNotes("");
+ setIsAdding(false);
+ }}
+ >
-
- Save Checkpoint
+
+
+ Save Checkpoint
@@ -410,7 +456,9 @@ function CheckpointsSection({ task }: { task: Task }) {
{/* Agent */}
- Saved by {getAgentDisplayName(checkpoint.agent_id)}
+
+ Saved by {getAgentDisplayName(checkpoint.agent_id)}
+
{/* State Summary */}
diff --git a/panel/src/components/tasks/task-detail/tab-sessions.tsx b/panel/src/components/tasks/task-detail/tab-sessions.tsx
index 7e17ea6f..afb1e572 100644
--- a/panel/src/components/tasks/task-detail/tab-sessions.tsx
+++ b/panel/src/components/tasks/task-detail/tab-sessions.tsx
@@ -1,7 +1,13 @@
"use client";
import { Task, TaskSessionLink, SessionScope } from "@/types";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { MessageSquare, ExternalLink, Star, Hash } from "lucide-react";
@@ -12,9 +18,12 @@ interface TabSessionsProps {
}
const scopeColors: Record
= {
- [SessionScope.INITIATIVE]: "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300",
- [SessionScope.CELL]: "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
- [SessionScope.TASK]: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
+ [SessionScope.INITIATIVE]:
+ "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300",
+ [SessionScope.CELL]:
+ "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
+ [SessionScope.TASK]:
+ "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
};
const scopeLabels: Record = {
@@ -54,7 +63,10 @@ function SessionCard({ session }: { session: TaskSessionLink }) {
- {relationshipLabels[session.relationship_type] || session.relationship_type}
+
+ {relationshipLabels[session.relationship_type] ||
+ session.relationship_type}
+
•
@@ -87,8 +99,8 @@ export function TabSessions({ task }: TabSessionsProps) {
No Linked Sessions
- This task does not have any linked discussion sessions yet.
- A PM will create a session when work begins.
+ This task does not have any linked discussion sessions yet. A PM
+ will create a session when work begins.
@@ -111,7 +123,9 @@ export function TabSessions({ task }: TabSessionsProps) {
Discussion sessions related to this task
- {sessions.length} session{sessions.length !== 1 ? 's' : ''}
+
+ {sessions.length} session{sessions.length !== 1 ? "s" : ""}
+
diff --git a/panel/src/components/tasks/task-detail/task-action-dialogs.tsx b/panel/src/components/tasks/task-detail/task-action-dialogs.tsx
index 7903e5ef..7ffee7a4 100644
--- a/panel/src/components/tasks/task-detail/task-action-dialogs.tsx
+++ b/panel/src/components/tasks/task-detail/task-action-dialogs.tsx
@@ -74,7 +74,10 @@ export function EscalateToCeoDialog({
handleOpenChange(false)}>
Cancel
-
+
{isPending ? "Escalating..." : "Escalate"}
@@ -170,12 +173,16 @@ export function ApproveAndMergeDialog({
Approve & Merge
- This will approve the completed work and merge the pull request into the
- target branch. This action cannot be undone.
+ This will approve the completed work and merge the pull request into
+ the target branch. This action cannot be undone.
- onOpenChange(false)} disabled={isPending}>
+ onOpenChange(false)}
+ disabled={isPending}
+ >
Cancel
@@ -374,7 +381,8 @@ export function CreateBranchDialog({
Create Branch
- Create a new branch for this task. The branch name will be generated automatically.
+ Create a new branch for this task. The branch name will be generated
+ automatically.
diff --git a/panel/src/components/tasks/task-detail/task-description.tsx b/panel/src/components/tasks/task-detail/task-description.tsx
index 42048468..2700c8d7 100644
--- a/panel/src/components/tasks/task-detail/task-description.tsx
+++ b/panel/src/components/tasks/task-detail/task-description.tsx
@@ -84,7 +84,10 @@ export function TaskDescription({ task }: TaskDescriptionProps) {
Description
{isEditing ? (
- setEditMode(v as "write" | "preview")}>
+ setEditMode(v as "write" | "preview")}
+ >
@@ -114,11 +117,7 @@ export function TaskDescription({ task }: TaskDescriptionProps) {
) : (
-
+
Edit
@@ -143,12 +142,15 @@ export function TaskDescription({ task }: TaskDescriptionProps) {
{editValue ? (
{editValue}
) : (
- Nothing to preview
+
+ Nothing to preview
+
)}
)}
- Markdown supported. Press Ctrl/Cmd + Enter to save, Escape to cancel.
+ Markdown supported. Press Ctrl/Cmd + Enter to save, Escape to
+ cancel.
) : task.description ? (
diff --git a/panel/src/components/tasks/task-detail/work-session-card.tsx b/panel/src/components/tasks/task-detail/work-session-card.tsx
index 549bd81d..eb7b94d9 100644
--- a/panel/src/components/tasks/task-detail/work-session-card.tsx
+++ b/panel/src/components/tasks/task-detail/work-session-card.tsx
@@ -128,7 +128,9 @@ export function WorkSessionCard({ taskId }: WorkSessionCardProps) {
No active work session
-
A work session will be created when development begins
+
+ A work session will be created when development begins
+
@@ -180,14 +182,21 @@ export function WorkSessionCard({ taskId }: WorkSessionCardProps) {
{session.pr_created_at && (
- Created {formatDistanceToNow(new Date(session.pr_created_at), { addSuffix: true })}
+ Created{" "}
+ {formatDistanceToNow(new Date(session.pr_created_at), {
+ addSuffix: true,
+ })}
)}
{session.pr_url && (
-
+
View PR
@@ -200,14 +209,23 @@ export function WorkSessionCard({ taskId }: WorkSessionCardProps) {
- {session.commits.length} commit{session.commits.length !== 1 ? 's' : ''}
+
+ {session.commits.length} commit
+ {session.commits.length !== 1 ? "s" : ""}
+
- {session.files_modified.length} file{session.files_modified.length !== 1 ? 's' : ''}
+
+ {session.files_modified.length} file
+ {session.files_modified.length !== 1 ? "s" : ""}
+
- Started {formatDistanceToNow(new Date(session.started_at), { addSuffix: true })}
+ Started{" "}
+ {formatDistanceToNow(new Date(session.started_at), {
+ addSuffix: true,
+ })}
diff --git a/panel/src/components/tasks/task-filters.tsx b/panel/src/components/tasks/task-filters.tsx
index 124f8ca3..f869956e 100644
--- a/panel/src/components/tasks/task-filters.tsx
+++ b/panel/src/components/tasks/task-filters.tsx
@@ -114,7 +114,7 @@ export function TaskFilters({
onProjectChange(
projectFilter.includes(id)
? projectFilter.filter((p) => p !== id)
- : [...projectFilter, id]
+ : [...projectFilter, id],
);
};
@@ -123,7 +123,7 @@ export function TaskFilters({
onProductChange(
productFilter.includes(id)
? productFilter.filter((p) => p !== id)
- : [...productFilter, id]
+ : [...productFilter, id],
);
};
@@ -157,8 +157,8 @@ export function TaskFilters({
{statusFilter.length === 0
? "All Statuses"
: statusFilter.length === 1
- ? STATUS_LABELS[statusFilter[0]]
- : `${statusFilter.length} statuses`}
+ ? STATUS_LABELS[statusFilter[0]]
+ : `${statusFilter.length} statuses`}
@@ -202,8 +202,8 @@ export function TaskFilters({
{teamFilter.length === 0
? "All Teams"
: teamFilter.length === 1
- ? TEAM_LABELS[teamFilter[0]]
- : `${teamFilter.length} teams`}
+ ? TEAM_LABELS[teamFilter[0]]
+ : `${teamFilter.length} teams`}
@@ -243,13 +243,16 @@ export function TaskFilters({
{onTaskTypeChange && (
-
+
{taskTypeFilter.length === 0
? "All Types"
: taskTypeFilter.length === 1
- ? TASK_TYPE_LABELS[taskTypeFilter[0]]
- : `${taskTypeFilter.length} types`}
+ ? TASK_TYPE_LABELS[taskTypeFilter[0]]
+ : `${taskTypeFilter.length} types`}
@@ -278,7 +281,9 @@ export function TaskFilters({
checked={taskTypeFilter.includes(type)}
onCheckedChange={() => toggleTaskType(type)}
/>
- {TASK_TYPE_LABELS[type]}
+
+ {TASK_TYPE_LABELS[type]}
+
))}
@@ -290,13 +295,16 @@ export function TaskFilters({
{onProjectChange && projectOptions.length > 0 && (
-
+
{projectFilter.length === 0
? "All Projects"
: projectFilter.length === 1
- ? projectLabel(projectFilter[0])
- : `${projectFilter.length} projects`}
+ ? projectLabel(projectFilter[0])
+ : `${projectFilter.length} projects`}
@@ -337,13 +345,16 @@ export function TaskFilters({
{onProductChange && productOptions.length > 0 && (
-
+
{productFilter.length === 0
? "All Products"
: productFilter.length === 1
- ? productLabel(productFilter[0])
- : `${productFilter.length} products`}
+ ? productLabel(productFilter[0])
+ : `${productFilter.length} products`}
@@ -383,7 +394,11 @@ export function TaskFilters({
{/* Active Filters */}
- {(statusFilter.length > 0 || teamFilter.length > 0 || taskTypeFilter.length > 0 || projectFilter.length > 0 || productFilter.length > 0) && (
+ {(statusFilter.length > 0 ||
+ teamFilter.length > 0 ||
+ taskTypeFilter.length > 0 ||
+ projectFilter.length > 0 ||
+ productFilter.length > 0) && (
{statusFilter.map((status) => (
@@ -430,7 +445,11 @@ export function TaskFilters({
/>
))}
- {(statusFilter.length > 0 || teamFilter.length > 0 || taskTypeFilter.length > 0 || projectFilter.length > 0 || productFilter.length > 0) && (
+ {(statusFilter.length > 0 ||
+ teamFilter.length > 0 ||
+ taskTypeFilter.length > 0 ||
+ projectFilter.length > 0 ||
+ productFilter.length > 0) && (
t.status !== TaskStatus.CANCELLED
- );
+ filtered = filtered.filter((t) => t.status !== TaskStatus.CANCELLED);
// Sort by recency
return filtered.sort(
- (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
+ (a, b) =>
+ new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
);
}, [tasks, excludeTaskId, filterByTeam]);
@@ -131,7 +130,9 @@ export function TaskSelector({
{selectedTask ? (
- {truncateTitle(selectedTask.title)}
+
+ {truncateTitle(selectedTask.title)}
+
) : (
placeholder
@@ -155,7 +156,9 @@ export function TaskSelector({
{groupedTasks.board.slice(0, 10).map((task) => (
-
{truncateTitle(task.title, 30)}
+
+ {truncateTitle(task.title, 30)}
+
(
-
{truncateTitle(task.title, 30)}
+
+ {truncateTitle(task.title, 30)}
+
(
-
{truncateTitle(task.title, 30)}
+
+ {truncateTitle(task.title, 30)}
+
(
-
{truncateTitle(task.title, 30)}
+
+ {truncateTitle(task.title, 30)}
+
(
-
{truncateTitle(task.title, 30)}
+
+ {truncateTitle(task.title, 30)}
+
(
-
{truncateTitle(task.title, 30)}
+
+ {truncateTitle(task.title, 30)}
+
= {
+const TYPE_CONFIG: Record<
+ TaskType,
+ { label: string; icon: React.ReactNode; color: string }
+> = {
[TaskType.CODE]: {
label: "Code",
icon: ,
- color: "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300",
+ color:
+ "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300",
},
[TaskType.DOCUMENTATION]: {
label: "Docs",
@@ -34,7 +38,8 @@ const TYPE_CONFIG: Record ,
- color: "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
+ color:
+ "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
},
[TaskType.DESIGN]: {
label: "Design",
@@ -48,7 +53,11 @@ const TYPE_CONFIG: Record) {
- return
+ return ;
}
function AlertDialogTrigger({
@@ -17,7 +17,7 @@ function AlertDialogTrigger({
}: React.ComponentProps) {
return (
- )
+ );
}
function AlertDialogPortal({
@@ -25,7 +25,7 @@ function AlertDialogPortal({
}: React.ComponentProps) {
return (
- )
+ );
}
function AlertDialogOverlay({
@@ -37,11 +37,11 @@ function AlertDialogOverlay({
data-slot="alert-dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AlertDialogContent({
@@ -55,12 +55,12 @@ function AlertDialogContent({
data-slot="alert-dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AlertDialogHeader({
@@ -73,7 +73,7 @@ function AlertDialogHeader({
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
- )
+ );
}
function AlertDialogFooter({
@@ -85,11 +85,11 @@ function AlertDialogFooter({
data-slot="alert-dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AlertDialogTitle({
@@ -102,7 +102,7 @@ function AlertDialogTitle({
className={cn("text-lg font-semibold", className)}
{...props}
/>
- )
+ );
}
function AlertDialogDescription({
@@ -115,7 +115,7 @@ function AlertDialogDescription({
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
function AlertDialogAction({
@@ -127,7 +127,7 @@ function AlertDialogAction({
className={cn(buttonVariants(), className)}
{...props}
/>
- )
+ );
}
function AlertDialogCancel({
@@ -139,7 +139,7 @@ function AlertDialogCancel({
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props}
/>
- )
+ );
}
export {
@@ -154,4 +154,4 @@ export {
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
-}
+};
diff --git a/panel/src/components/ui/avatar.tsx b/panel/src/components/ui/avatar.tsx
index 71e428b4..c4475c2e 100644
--- a/panel/src/components/ui/avatar.tsx
+++ b/panel/src/components/ui/avatar.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as AvatarPrimitive from "@radix-ui/react-avatar"
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Avatar({
className,
@@ -14,11 +14,11 @@ function Avatar({
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AvatarImage({
@@ -31,7 +31,7 @@ function AvatarImage({
className={cn("aspect-square size-full", className)}
{...props}
/>
- )
+ );
}
function AvatarFallback({
@@ -43,11 +43,11 @@ function AvatarFallback({
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Avatar, AvatarImage, AvatarFallback }
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/panel/src/components/ui/badge.tsx b/panel/src/components/ui/badge.tsx
index fd3a406b..55f8352b 100644
--- a/panel/src/components/ui/badge.tsx
+++ b/panel/src/components/ui/badge.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
@@ -22,8 +22,8 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
- }
-)
+ },
+);
function Badge({
className,
@@ -32,7 +32,7 @@ function Badge({
...props
}: React.ComponentProps<"span"> &
VariantProps & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "span"
+ const Comp = asChild ? Slot : "span";
return (
- )
+ );
}
-export { Badge, badgeVariants }
+export { Badge, badgeVariants };
diff --git a/panel/src/components/ui/button.tsx b/panel/src/components/ui/button.tsx
index 37a7d4b9..46b3a48c 100644
--- a/panel/src/components/ui/button.tsx
+++ b/panel/src/components/ui/button.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@@ -33,8 +33,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
- }
-)
+ },
+);
function Button({
className,
@@ -44,9 +44,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps & {
- asChild?: boolean
+ asChild?: boolean;
}) {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : "button";
return (
- )
+ );
}
-export { Button, buttonVariants }
+export { Button, buttonVariants };
diff --git a/panel/src/components/ui/card.tsx b/panel/src/components/ui/card.tsx
index 681ad980..4f880247 100644
--- a/panel/src/components/ui/card.tsx
+++ b/panel/src/components/ui/card.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
@@ -8,11 +8,11 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -21,11 +21,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -35,7 +35,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
className={cn("leading-none font-semibold", className)}
{...props}
/>
- )
+ );
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
@@ -45,7 +45,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
@@ -54,11 +54,11 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
@@ -68,7 +68,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
className={cn("px-6", className)}
{...props}
/>
- )
+ );
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -78,7 +78,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
- )
+ );
}
export {
@@ -89,4 +89,4 @@ export {
CardAction,
CardDescription,
CardContent,
-}
+};
diff --git a/panel/src/components/ui/checkbox.tsx b/panel/src/components/ui/checkbox.tsx
index cb0b07b4..2b4d53b9 100644
--- a/panel/src/components/ui/checkbox.tsx
+++ b/panel/src/components/ui/checkbox.tsx
@@ -1,10 +1,10 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
-import { CheckIcon } from "lucide-react"
+import * as React from "react";
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
+import { CheckIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Checkbox({
className,
@@ -15,7 +15,7 @@ function Checkbox({
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
- className
+ className,
)}
{...props}
>
@@ -26,7 +26,7 @@ function Checkbox({
- )
+ );
}
-export { Checkbox }
+export { Checkbox };
diff --git a/panel/src/components/ui/collapsible.tsx b/panel/src/components/ui/collapsible.tsx
index 9fa48946..cb003d17 100644
--- a/panel/src/components/ui/collapsible.tsx
+++ b/panel/src/components/ui/collapsible.tsx
@@ -1,11 +1,11 @@
-"use client"
+"use client";
-import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
-const Collapsible = CollapsiblePrimitive.Root
+const Collapsible = CollapsiblePrimitive.Root;
-const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
-const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
-export { Collapsible, CollapsibleTrigger, CollapsibleContent }
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/panel/src/components/ui/copy-button.tsx b/panel/src/components/ui/copy-button.tsx
index 7ed1b085..e58ab628 100644
--- a/panel/src/components/ui/copy-button.tsx
+++ b/panel/src/components/ui/copy-button.tsx
@@ -69,7 +69,7 @@ export function CopyButton({ value, label, className }: CopyButtonProps) {
title={label ?? "Copy"}
className={cn(
"inline-flex shrink-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
- className
+ className,
)}
>
{copied ? (
diff --git a/panel/src/components/ui/dialog.tsx b/panel/src/components/ui/dialog.tsx
index 47cf4875..679f4bba 100644
--- a/panel/src/components/ui/dialog.tsx
+++ b/panel/src/components/ui/dialog.tsx
@@ -1,33 +1,33 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { XIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Dialog({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogPortal({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogClose({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogOverlay({
@@ -39,11 +39,11 @@ function DialogOverlay({
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DialogContent({
@@ -52,7 +52,7 @@ function DialogContent({
showCloseButton = true,
...props
}: React.ComponentProps & {
- showCloseButton?: boolean
+ showCloseButton?: boolean;
}) {
return (
@@ -61,7 +61,7 @@ function DialogContent({
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid max-h-[85vh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
- className
+ className,
)}
{...props}
>
@@ -77,7 +77,7 @@ function DialogContent({
)}
- )
+ );
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -87,7 +87,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
- )
+ );
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -96,11 +96,11 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="dialog-footer"
className={cn(
"bg-background sticky bottom-0 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DialogTitle({
@@ -113,7 +113,7 @@ function DialogTitle({
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
- )
+ );
}
function DialogDescription({
@@ -126,7 +126,7 @@ function DialogDescription({
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
export {
@@ -140,4 +140,4 @@ export {
DialogPortal,
DialogTitle,
DialogTrigger,
-}
+};
diff --git a/panel/src/components/ui/dropdown-menu.tsx b/panel/src/components/ui/dropdown-menu.tsx
index bbe6fb01..0329b9b0 100644
--- a/panel/src/components/ui/dropdown-menu.tsx
+++ b/panel/src/components/ui/dropdown-menu.tsx
@@ -1,15 +1,15 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+import * as React from "react";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function DropdownMenu({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuPortal({
@@ -17,7 +17,7 @@ function DropdownMenuPortal({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuTrigger({
@@ -28,7 +28,7 @@ function DropdownMenuTrigger({
data-slot="dropdown-menu-trigger"
{...props}
/>
- )
+ );
}
function DropdownMenuContent({
@@ -43,12 +43,12 @@ function DropdownMenuContent({
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DropdownMenuGroup({
@@ -56,7 +56,7 @@ function DropdownMenuGroup({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuItem({
@@ -65,8 +65,8 @@ function DropdownMenuItem({
variant = "default",
...props
}: React.ComponentProps & {
- inset?: boolean
- variant?: "default" | "destructive"
+ inset?: boolean;
+ variant?: "default" | "destructive";
}) {
return (
- )
+ );
}
function DropdownMenuCheckboxItem({
@@ -93,7 +93,7 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
- className
+ className,
)}
checked={checked}
{...props}
@@ -105,7 +105,7 @@ function DropdownMenuCheckboxItem({
{children}
- )
+ );
}
function DropdownMenuRadioGroup({
@@ -116,7 +116,7 @@ function DropdownMenuRadioGroup({
data-slot="dropdown-menu-radio-group"
{...props}
/>
- )
+ );
}
function DropdownMenuRadioItem({
@@ -129,7 +129,7 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
- className
+ className,
)}
{...props}
>
@@ -140,7 +140,7 @@ function DropdownMenuRadioItem({
{children}
- )
+ );
}
function DropdownMenuLabel({
@@ -148,7 +148,7 @@ function DropdownMenuLabel({
inset,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
- )
+ );
}
function DropdownMenuSeparator({
@@ -173,7 +173,7 @@ function DropdownMenuSeparator({
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
- )
+ );
}
function DropdownMenuShortcut({
@@ -185,17 +185,17 @@ function DropdownMenuShortcut({
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DropdownMenuSub({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuSubTrigger({
@@ -204,7 +204,7 @@ function DropdownMenuSubTrigger({
children,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
{children}
- )
+ );
}
function DropdownMenuSubContent({
@@ -231,11 +231,11 @@ function DropdownMenuSubContent({
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
export {
@@ -254,4 +254,4 @@ export {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
-}
+};
diff --git a/panel/src/components/ui/form.tsx b/panel/src/components/ui/form.tsx
index 2b529e6e..b8a8499a 100644
--- a/panel/src/components/ui/form.tsx
+++ b/panel/src/components/ui/form.tsx
@@ -1,8 +1,8 @@
-"use client"
+"use client";
-import * as React from "react"
-import type * as LabelPrimitive from "@radix-ui/react-label"
-import { Slot } from "@radix-ui/react-slot"
+import * as React from "react";
+import type * as LabelPrimitive from "@radix-ui/react-label";
+import { Slot } from "@radix-ui/react-slot";
import {
Controller,
FormProvider,
@@ -11,23 +11,23 @@ import {
type ControllerProps,
type FieldPath,
type FieldValues,
-} from "react-hook-form"
+} from "react-hook-form";
-import { cn } from "@/lib/utils"
-import { Label } from "@/components/ui/label"
+import { cn } from "@/lib/utils";
+import { Label } from "@/components/ui/label";
-const Form = FormProvider
+const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath = FieldPath,
> = {
- name: TName
-}
+ name: TName;
+};
const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
-)
+ {} as FormFieldContextValue,
+);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
@@ -39,21 +39,21 @@ const FormField = <
- )
-}
+ );
+};
const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext)
- const itemContext = React.useContext(FormItemContext)
- const { getFieldState } = useFormContext()
- const formState = useFormState({ name: fieldContext.name })
- const fieldState = getFieldState(fieldContext.name, formState)
+ const fieldContext = React.useContext(FormFieldContext);
+ const itemContext = React.useContext(FormItemContext);
+ const { getFieldState } = useFormContext();
+ const formState = useFormState({ name: fieldContext.name });
+ const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
- throw new Error("useFormField should be used within ")
+ throw new Error("useFormField should be used within ");
}
- const { id } = itemContext
+ const { id } = itemContext;
return {
id,
@@ -62,19 +62,19 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
- }
-}
+ };
+};
type FormItemContextValue = {
- id: string
-}
+ id: string;
+};
const FormItemContext = React.createContext(
- {} as FormItemContextValue
-)
+ {} as FormItemContextValue,
+);
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
- const id = React.useId()
+ const id = React.useId();
return (
@@ -84,14 +84,14 @@ function FormItem({ className, ...props }: React.ComponentProps<"div">) {
{...props}
/>
- )
+ );
}
function FormLabel({
className,
...props
}: React.ComponentProps) {
- const { error, formItemId } = useFormField()
+ const { error, formItemId } = useFormField();
return (
- )
+ );
}
function FormControl({ ...props }: React.ComponentProps) {
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField();
return (
) {
aria-invalid={!!error}
{...props}
/>
- )
+ );
}
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
- const { formDescriptionId } = useFormField()
+ const { formDescriptionId } = useFormField();
return (
) {
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
- const { error, formMessageId } = useFormField()
- const body = error ? String(error?.message ?? "") : props.children
+ const { error, formMessageId } = useFormField();
+ const body = error ? String(error?.message ?? "") : props.children;
if (!body) {
- return null
+ return null;
}
return (
@@ -152,7 +153,7 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
>
{body}
- )
+ );
}
export {
@@ -164,4 +165,4 @@ export {
FormDescription,
FormMessage,
FormField,
-}
+};
diff --git a/panel/src/components/ui/input.tsx b/panel/src/components/ui/input.tsx
index 89169058..f16c2c0e 100644
--- a/panel/src/components/ui/input.tsx
+++ b/panel/src/components/ui/input.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
@@ -11,11 +11,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Input }
+export { Input };
diff --git a/panel/src/components/ui/label.tsx b/panel/src/components/ui/label.tsx
index fb5fbc3e..79d77b4b 100644
--- a/panel/src/components/ui/label.tsx
+++ b/panel/src/components/ui/label.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Label({
className,
@@ -14,11 +14,11 @@ function Label({
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Label }
+export { Label };
diff --git a/panel/src/components/ui/markdown.tsx b/panel/src/components/ui/markdown.tsx
index 34dc653b..5b51a81f 100644
--- a/panel/src/components/ui/markdown.tsx
+++ b/panel/src/components/ui/markdown.tsx
@@ -22,7 +22,13 @@ interface MarkdownProps {
* Uses Tailwind's prose classes for typography.
* Supports GitHub Flavored Markdown (tables, checkboxes, strikethrough, autolinks).
*/
-export function Markdown({ children, className, onCheckboxChange, disabled, compact }: MarkdownProps) {
+export function Markdown({
+ children,
+ className,
+ onCheckboxChange,
+ disabled,
+ compact,
+}: MarkdownProps) {
const checkboxIndexRef = useRef(0);
// Reset checkbox index on each render
@@ -46,7 +52,7 @@ export function Markdown({ children, className, onCheckboxChange, disabled, comp
onCheckboxChange(newContent);
},
- [children, onCheckboxChange]
+ [children, onCheckboxChange],
);
// Custom components for ReactMarkdown
@@ -59,7 +65,9 @@ export function Markdown({ children, className, onCheckboxChange, disabled, comp
toggleCheckbox(index, !!newChecked)}
+ onCheckedChange={(newChecked) =>
+ toggleCheckbox(index, !!newChecked)
+ }
className="mr-2 align-middle"
/>
);
@@ -124,7 +132,7 @@ export function Markdown({ children, className, onCheckboxChange, disabled, comp
"prose-table:border prose-table:border-border prose-th:border prose-th:border-border prose-th:bg-muted prose-td:border prose-td:border-border",
// Strong/bold
"prose-strong:font-semibold prose-strong:text-foreground",
- className
+ className,
)}
>
diff --git a/panel/src/components/ui/offline-state.tsx b/panel/src/components/ui/offline-state.tsx
index 8319ff40..7e8ba2ba 100644
--- a/panel/src/components/ui/offline-state.tsx
+++ b/panel/src/components/ui/offline-state.tsx
@@ -1,4 +1,10 @@
-import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+} from "@/components/ui/card";
import { WifiOff, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
@@ -8,7 +14,7 @@ interface OfflineStateProps {
onRetry?: () => void;
}
-export function OfflineState({
+export function OfflineState({
title = "Backend Not Connected",
description = "The orchestrator API is not available. Start the backend to see live data.",
onRetry,
diff --git a/panel/src/components/ui/popover.tsx b/panel/src/components/ui/popover.tsx
index 01e468b6..5d6f78d3 100644
--- a/panel/src/components/ui/popover.tsx
+++ b/panel/src/components/ui/popover.tsx
@@ -1,20 +1,20 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
+import * as React from "react";
+import * as PopoverPrimitive from "@radix-ui/react-popover";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Popover({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function PopoverTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function PopoverContent({
@@ -31,18 +31,18 @@ function PopoverContent({
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function PopoverAnchor({
...props
}: React.ComponentProps) {
- return
+ return ;
}
-export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
diff --git a/panel/src/components/ui/progress.tsx b/panel/src/components/ui/progress.tsx
index e7a416c3..e4356372 100644
--- a/panel/src/components/ui/progress.tsx
+++ b/panel/src/components/ui/progress.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as ProgressPrimitive from "@radix-ui/react-progress"
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Progress({
className,
@@ -15,7 +15,7 @@ function Progress({
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
- className
+ className,
)}
{...props}
>
@@ -25,7 +25,7 @@ function Progress({
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
- )
+ );
}
-export { Progress }
+export { Progress };
diff --git a/panel/src/components/ui/required-notes-dialog.tsx b/panel/src/components/ui/required-notes-dialog.tsx
index 33e9c3e1..68c3afd6 100644
--- a/panel/src/components/ui/required-notes-dialog.tsx
+++ b/panel/src/components/ui/required-notes-dialog.tsx
@@ -70,9 +70,7 @@ function RequiredNotesDialogInner({
{title}
- {description && (
- {description}
- )}
+ {description && {description} }
diff --git a/panel/src/components/ui/scroll-area.tsx b/panel/src/components/ui/scroll-area.tsx
index 8e4fa13f..5524f1ca 100644
--- a/panel/src/components/ui/scroll-area.tsx
+++ b/panel/src/components/ui/scroll-area.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+import * as React from "react";
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function ScrollArea({
className,
@@ -25,7 +25,7 @@ function ScrollArea({
- )
+ );
}
function ScrollBar({
@@ -43,7 +43,7 @@ function ScrollBar({
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
- className
+ className,
)}
{...props}
>
@@ -52,7 +52,7 @@ function ScrollBar({
className="bg-border relative flex-1 rounded-full"
/>
- )
+ );
}
-export { ScrollArea, ScrollBar }
+export { ScrollArea, ScrollBar };
diff --git a/panel/src/components/ui/select.tsx b/panel/src/components/ui/select.tsx
index 88302a8d..c3f080d4 100644
--- a/panel/src/components/ui/select.tsx
+++ b/panel/src/components/ui/select.tsx
@@ -1,27 +1,27 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+import * as React from "react";
+import * as SelectPrimitive from "@radix-ui/react-select";
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Select({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SelectGroup({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SelectValue({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SelectTrigger({
@@ -30,7 +30,7 @@ function SelectTrigger({
children,
...props
}: React.ComponentProps & {
- size?: "sm" | "default"
+ size?: "sm" | "default";
}) {
return (
@@ -47,7 +47,7 @@ function SelectTrigger({
- )
+ );
}
function SelectContent({
@@ -65,7 +65,7 @@ function SelectContent({
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
- className
+ className,
)}
position={position}
align={align}
@@ -76,7 +76,7 @@ function SelectContent({
className={cn(
"p-1",
position === "popper" &&
- "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
)}
>
{children}
@@ -84,7 +84,7 @@ function SelectContent({
- )
+ );
}
function SelectLabel({
@@ -97,7 +97,7 @@ function SelectLabel({
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props}
/>
- )
+ );
}
function SelectItem({
@@ -110,7 +110,7 @@ function SelectItem({
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
- className
+ className,
)}
{...props}
>
@@ -124,7 +124,7 @@ function SelectItem({
{children}
- )
+ );
}
function SelectSeparator({
@@ -137,7 +137,7 @@ function SelectSeparator({
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
- )
+ );
}
function SelectScrollUpButton({
@@ -149,13 +149,13 @@ function SelectScrollUpButton({
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
- className
+ className,
)}
{...props}
>
- )
+ );
}
function SelectScrollDownButton({
@@ -167,13 +167,13 @@ function SelectScrollDownButton({
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
- className
+ className,
)}
{...props}
>
- )
+ );
}
export {
@@ -187,4 +187,4 @@ export {
SelectSeparator,
SelectTrigger,
SelectValue,
-}
+};
diff --git a/panel/src/components/ui/separator.tsx b/panel/src/components/ui/separator.tsx
index 275381ca..72c18e33 100644
--- a/panel/src/components/ui/separator.tsx
+++ b/panel/src/components/ui/separator.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as SeparatorPrimitive from "@radix-ui/react-separator"
+import * as React from "react";
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Separator({
className,
@@ -18,11 +18,11 @@ function Separator({
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Separator }
+export { Separator };
diff --git a/panel/src/components/ui/sheet.tsx b/panel/src/components/ui/sheet.tsx
index 84649ad0..d30779f4 100644
--- a/panel/src/components/ui/sheet.tsx
+++ b/panel/src/components/ui/sheet.tsx
@@ -1,31 +1,31 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as SheetPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
+import * as React from "react";
+import * as SheetPrimitive from "@radix-ui/react-dialog";
+import { XIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Sheet({ ...props }: React.ComponentProps) {
- return
+ return ;
}
function SheetTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetClose({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetPortal({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function SheetOverlay({
@@ -37,11 +37,11 @@ function SheetOverlay({
data-slot="sheet-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function SheetContent({
@@ -50,7 +50,7 @@ function SheetContent({
side = "right",
...props
}: React.ComponentProps & {
- side?: "top" | "right" | "bottom" | "left"
+ side?: "top" | "right" | "bottom" | "left";
}) {
return (
@@ -67,7 +67,7 @@ function SheetContent({
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
- className
+ className,
)}
{...props}
>
@@ -78,7 +78,7 @@ function SheetContent({
- )
+ );
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -88,7 +88,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/>
- )
+ );
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -98,7 +98,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
- )
+ );
}
function SheetTitle({
@@ -111,7 +111,7 @@ function SheetTitle({
className={cn("text-foreground font-semibold", className)}
{...props}
/>
- )
+ );
}
function SheetDescription({
@@ -124,7 +124,7 @@ function SheetDescription({
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
export {
@@ -136,4 +136,4 @@ export {
SheetFooter,
SheetTitle,
SheetDescription,
-}
+};
diff --git a/panel/src/components/ui/skeleton.tsx b/panel/src/components/ui/skeleton.tsx
index 32ea0ef7..01689981 100644
--- a/panel/src/components/ui/skeleton.tsx
+++ b/panel/src/components/ui/skeleton.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
@@ -7,7 +7,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props}
/>
- )
+ );
}
-export { Skeleton }
+export { Skeleton };
diff --git a/panel/src/components/ui/sonner.tsx b/panel/src/components/ui/sonner.tsx
index 9b20afe2..28c5ec35 100644
--- a/panel/src/components/ui/sonner.tsx
+++ b/panel/src/components/ui/sonner.tsx
@@ -1,4 +1,4 @@
-"use client"
+"use client";
import {
CircleCheckIcon,
@@ -6,12 +6,12 @@ import {
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
-} from "lucide-react"
-import { useTheme } from "next-themes"
-import { Toaster as Sonner, type ToasterProps } from "sonner"
+} from "lucide-react";
+import { useTheme } from "next-themes";
+import { Toaster as Sonner, type ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme()
+ const { theme = "system" } = useTheme();
return (
{
}
{...props}
/>
- )
-}
+ );
+};
-export { Toaster }
+export { Toaster };
diff --git a/panel/src/components/ui/switch.tsx b/panel/src/components/ui/switch.tsx
index 6a2b5241..d63e4b77 100644
--- a/panel/src/components/ui/switch.tsx
+++ b/panel/src/components/ui/switch.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as SwitchPrimitive from "@radix-ui/react-switch"
+import * as React from "react";
+import * as SwitchPrimitive from "@radix-ui/react-switch";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Switch({
className,
@@ -14,18 +14,18 @@ function Switch({
data-slot="switch"
className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
- className
+ className,
)}
{...props}
>
- )
+ );
}
-export { Switch }
+export { Switch };
diff --git a/panel/src/components/ui/table.tsx b/panel/src/components/ui/table.tsx
index 51b74dd5..4b3c98ea 100644
--- a/panel/src/components/ui/table.tsx
+++ b/panel/src/components/ui/table.tsx
@@ -1,8 +1,8 @@
-"use client"
+"use client";
-import * as React from "react"
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
@@ -16,7 +16,7 @@ function Table({ className, ...props }: React.ComponentProps<"table">) {
{...props}
/>
- )
+ );
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
@@ -26,7 +26,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
className={cn("[&_tr]:border-b", className)}
{...props}
/>
- )
+ );
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
@@ -36,7 +36,7 @@ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
- )
+ );
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
@@ -45,11 +45,11 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
data-slot="table-footer"
className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
@@ -58,11 +58,11 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
data-slot="table-row"
className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
@@ -71,11 +71,11 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
@@ -84,11 +84,11 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function TableCaption({
@@ -101,7 +101,7 @@ function TableCaption({
className={cn("text-muted-foreground mt-4 text-sm", className)}
{...props}
/>
- )
+ );
}
export {
@@ -113,4 +113,4 @@ export {
TableRow,
TableCell,
TableCaption,
-}
+};
diff --git a/panel/src/components/ui/tabs.tsx b/panel/src/components/ui/tabs.tsx
index 497ba5ea..469a958d 100644
--- a/panel/src/components/ui/tabs.tsx
+++ b/panel/src/components/ui/tabs.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as TabsPrimitive from "@radix-ui/react-tabs"
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Tabs({
className,
@@ -15,7 +15,7 @@ function Tabs({
className={cn("flex flex-col gap-2", className)}
{...props}
/>
- )
+ );
}
function TabsList({
@@ -27,11 +27,11 @@ function TabsList({
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function TabsTrigger({
@@ -43,11 +43,11 @@ function TabsTrigger({
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function TabsContent({
@@ -60,7 +60,7 @@ function TabsContent({
className={cn("flex-1 outline-none", className)}
{...props}
/>
- )
+ );
}
-export { Tabs, TabsList, TabsTrigger, TabsContent }
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/panel/src/components/ui/textarea.tsx b/panel/src/components/ui/textarea.tsx
index 7f21b5e7..0735a8ca 100644
--- a/panel/src/components/ui/textarea.tsx
+++ b/panel/src/components/ui/textarea.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
@@ -8,11 +8,11 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Textarea }
+export { Textarea };
diff --git a/panel/src/components/ui/tooltip.tsx b/panel/src/components/ui/tooltip.tsx
index a66b3f22..3e3f0be4 100644
--- a/panel/src/components/ui/tooltip.tsx
+++ b/panel/src/components/ui/tooltip.tsx
@@ -1,15 +1,15 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+import * as React from "react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const TooltipProvider = TooltipPrimitive.Provider
+const TooltipProvider = TooltipPrimitive.Provider;
-const Tooltip = TooltipPrimitive.Root
+const Tooltip = TooltipPrimitive.Root;
-const TooltipTrigger = TooltipPrimitive.Trigger
+const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef,
@@ -21,12 +21,12 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
-))
-TooltipContent.displayName = TooltipPrimitive.Content.displayName
+));
+TooltipContent.displayName = TooltipPrimitive.Content.displayName;
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/panel/src/components/work-sessions/work-session-table.tsx b/panel/src/components/work-sessions/work-session-table.tsx
index 38026713..084a07f6 100644
--- a/panel/src/components/work-sessions/work-session-table.tsx
+++ b/panel/src/components/work-sessions/work-session-table.tsx
@@ -57,7 +57,10 @@ function getStatusBadge(status: WorkSessionStatus) {
}
}
-export function WorkSessionTable({ sessions, isLoading }: WorkSessionTableProps) {
+export function WorkSessionTable({
+ sessions,
+ isLoading,
+}: WorkSessionTableProps) {
if (isLoading) {
return (
@@ -73,7 +76,9 @@ export function WorkSessionTable({ sessions, isLoading }: WorkSessionTableProps)
No work sessions found
-
Work sessions are created when agents start working on tasks
+
+ Work sessions are created when agents start working on tasks
+
);
}
@@ -125,7 +130,9 @@ export function WorkSessionTable({ sessions, isLoading }: WorkSessionTableProps)
)}
- {formatDistanceToNow(new Date(session.started_at), { addSuffix: true })}
+ {formatDistanceToNow(new Date(session.started_at), {
+ addSuffix: true,
+ })}
diff --git a/panel/src/hooks/use-agents.ts b/panel/src/hooks/use-agents.ts
index bb98d6d0..8fa2457f 100644
--- a/panel/src/hooks/use-agents.ts
+++ b/panel/src/hooks/use-agents.ts
@@ -1,6 +1,9 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
-import { orchestratorApi, type SpawnAgentRequest } from "@/lib/api/orchestrator";
+import {
+ orchestratorApi,
+ type SpawnAgentRequest,
+} from "@/lib/api/orchestrator";
import { agentsApi, type AgentDefinition } from "@/lib/api/agents";
import { registerAgentRoster } from "@/lib/agent-utils";
import type { Agent, AgentRole, Team, AgentState } from "@/types";
@@ -12,32 +15,208 @@ export type { AgentDefinition } from "@/lib/api/agents";
// agents, but it is not the source of truth and may lag the backend.
const AGENT_ROSTER: Agent[] = [
// Board / Management
- { id: "1", agent_id: "main-pm", name: "Main PM", role: "main_pm" as AgentRole, team: null, cell: null, status: "idle" as AgentState },
- { id: "2", agent_id: "product-owner", name: "Product Owner", role: "product_owner" as AgentRole, team: "board" as Team, cell: null, status: "idle" as AgentState },
- { id: "3", agent_id: "head-marketing", name: "Head of Marketing", role: "head_marketing" as AgentRole, team: "board" as Team, cell: null, status: "idle" as AgentState },
- { id: "4", agent_id: "auditor", name: "Auditor", role: "auditor" as AgentRole, team: null, cell: null, status: "idle" as AgentState },
+ {
+ id: "1",
+ agent_id: "main-pm",
+ name: "Main PM",
+ role: "main_pm" as AgentRole,
+ team: null,
+ cell: null,
+ status: "idle" as AgentState,
+ },
+ {
+ id: "2",
+ agent_id: "product-owner",
+ name: "Product Owner",
+ role: "product_owner" as AgentRole,
+ team: "board" as Team,
+ cell: null,
+ status: "idle" as AgentState,
+ },
+ {
+ id: "3",
+ agent_id: "head-marketing",
+ name: "Head of Marketing",
+ role: "head_marketing" as AgentRole,
+ team: "board" as Team,
+ cell: null,
+ status: "idle" as AgentState,
+ },
+ {
+ id: "4",
+ agent_id: "auditor",
+ name: "Auditor",
+ role: "auditor" as AgentRole,
+ team: null,
+ cell: null,
+ status: "idle" as AgentState,
+ },
// Board-adjacent singletons
- { id: "20", agent_id: "intake-1", name: "Intake", role: "prompter" as AgentRole, team: "board" as Team, cell: null, status: "idle" as AgentState },
- { id: "21", agent_id: "secretary-1", name: "Secretary", role: "secretary" as AgentRole, team: "board" as Team, cell: null, status: "idle" as AgentState },
- { id: "22", agent_id: "pr-reviewer-1", name: "PR Reviewer", role: "pr_reviewer" as AgentRole, team: "board" as Team, cell: null, status: "idle" as AgentState },
+ {
+ id: "20",
+ agent_id: "intake-1",
+ name: "Intake",
+ role: "prompter" as AgentRole,
+ team: "board" as Team,
+ cell: null,
+ status: "idle" as AgentState,
+ },
+ {
+ id: "21",
+ agent_id: "secretary-1",
+ name: "Secretary",
+ role: "secretary" as AgentRole,
+ team: "board" as Team,
+ cell: null,
+ status: "idle" as AgentState,
+ },
+ {
+ id: "22",
+ agent_id: "pr-reviewer-1",
+ name: "PR Reviewer",
+ role: "pr_reviewer" as AgentRole,
+ team: "board" as Team,
+ cell: null,
+ status: "idle" as AgentState,
+ },
// Backend Cell
- { id: "5", agent_id: "be-dev-1", name: "Backend Dev 1", role: "developer" as AgentRole, team: "backend" as Team, cell: "backend", status: "idle" as AgentState },
- { id: "6", agent_id: "be-dev-2", name: "Backend Dev 2", role: "developer" as AgentRole, team: "backend" as Team, cell: "backend", status: "idle" as AgentState },
- { id: "7", agent_id: "be-qa", name: "Backend QA", role: "qa" as AgentRole, team: "backend" as Team, cell: "backend", status: "idle" as AgentState },
- { id: "8", agent_id: "be-pm", name: "Backend PM", role: "cell_pm" as AgentRole, team: "backend" as Team, cell: "backend", status: "idle" as AgentState },
- { id: "9", agent_id: "be-doc", name: "Backend Documenter", role: "documenter" as AgentRole, team: "backend" as Team, cell: "backend", status: "idle" as AgentState },
+ {
+ id: "5",
+ agent_id: "be-dev-1",
+ name: "Backend Dev 1",
+ role: "developer" as AgentRole,
+ team: "backend" as Team,
+ cell: "backend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "6",
+ agent_id: "be-dev-2",
+ name: "Backend Dev 2",
+ role: "developer" as AgentRole,
+ team: "backend" as Team,
+ cell: "backend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "7",
+ agent_id: "be-qa",
+ name: "Backend QA",
+ role: "qa" as AgentRole,
+ team: "backend" as Team,
+ cell: "backend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "8",
+ agent_id: "be-pm",
+ name: "Backend PM",
+ role: "cell_pm" as AgentRole,
+ team: "backend" as Team,
+ cell: "backend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "9",
+ agent_id: "be-doc",
+ name: "Backend Documenter",
+ role: "documenter" as AgentRole,
+ team: "backend" as Team,
+ cell: "backend",
+ status: "idle" as AgentState,
+ },
// Frontend Cell
- { id: "10", agent_id: "fe-dev-1", name: "Frontend Dev 1", role: "developer" as AgentRole, team: "frontend" as Team, cell: "frontend", status: "idle" as AgentState },
- { id: "11", agent_id: "fe-dev-2", name: "Frontend Dev 2", role: "developer" as AgentRole, team: "frontend" as Team, cell: "frontend", status: "idle" as AgentState },
- { id: "12", agent_id: "fe-qa", name: "Frontend QA", role: "qa" as AgentRole, team: "frontend" as Team, cell: "frontend", status: "idle" as AgentState },
- { id: "13", agent_id: "fe-pm", name: "Frontend PM", role: "cell_pm" as AgentRole, team: "frontend" as Team, cell: "frontend", status: "idle" as AgentState },
- { id: "14", agent_id: "fe-doc", name: "Frontend Documenter", role: "documenter" as AgentRole, team: "frontend" as Team, cell: "frontend", status: "idle" as AgentState },
+ {
+ id: "10",
+ agent_id: "fe-dev-1",
+ name: "Frontend Dev 1",
+ role: "developer" as AgentRole,
+ team: "frontend" as Team,
+ cell: "frontend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "11",
+ agent_id: "fe-dev-2",
+ name: "Frontend Dev 2",
+ role: "developer" as AgentRole,
+ team: "frontend" as Team,
+ cell: "frontend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "12",
+ agent_id: "fe-qa",
+ name: "Frontend QA",
+ role: "qa" as AgentRole,
+ team: "frontend" as Team,
+ cell: "frontend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "13",
+ agent_id: "fe-pm",
+ name: "Frontend PM",
+ role: "cell_pm" as AgentRole,
+ team: "frontend" as Team,
+ cell: "frontend",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "14",
+ agent_id: "fe-doc",
+ name: "Frontend Documenter",
+ role: "documenter" as AgentRole,
+ team: "frontend" as Team,
+ cell: "frontend",
+ status: "idle" as AgentState,
+ },
// UX/UI Cell
- { id: "15", agent_id: "ux-dev-1", name: "UX/UI Dev 1", role: "developer" as AgentRole, team: "ux_ui" as Team, cell: "ux_ui", status: "idle" as AgentState },
- { id: "16", agent_id: "ux-dev-2", name: "UX/UI Dev 2", role: "developer" as AgentRole, team: "ux_ui" as Team, cell: "ux_ui", status: "idle" as AgentState },
- { id: "19", agent_id: "ux-qa", name: "UX/UI QA", role: "qa" as AgentRole, team: "ux_ui" as Team, cell: "ux_ui", status: "idle" as AgentState },
- { id: "17", agent_id: "ux-pm", name: "UX/UI PM", role: "cell_pm" as AgentRole, team: "ux_ui" as Team, cell: "ux_ui", status: "idle" as AgentState },
- { id: "18", agent_id: "ux-doc", name: "UX/UI Documenter", role: "documenter" as AgentRole, team: "ux_ui" as Team, cell: "ux_ui", status: "idle" as AgentState },
+ {
+ id: "15",
+ agent_id: "ux-dev-1",
+ name: "UX/UI Dev 1",
+ role: "developer" as AgentRole,
+ team: "ux_ui" as Team,
+ cell: "ux_ui",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "16",
+ agent_id: "ux-dev-2",
+ name: "UX/UI Dev 2",
+ role: "developer" as AgentRole,
+ team: "ux_ui" as Team,
+ cell: "ux_ui",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "19",
+ agent_id: "ux-qa",
+ name: "UX/UI QA",
+ role: "qa" as AgentRole,
+ team: "ux_ui" as Team,
+ cell: "ux_ui",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "17",
+ agent_id: "ux-pm",
+ name: "UX/UI PM",
+ role: "cell_pm" as AgentRole,
+ team: "ux_ui" as Team,
+ cell: "ux_ui",
+ status: "idle" as AgentState,
+ },
+ {
+ id: "18",
+ agent_id: "ux-doc",
+ name: "UX/UI Documenter",
+ role: "documenter" as AgentRole,
+ team: "ux_ui" as Team,
+ cell: "ux_ui",
+ status: "idle" as AgentState,
+ },
];
// Query keys
@@ -78,7 +257,11 @@ export function useAgentRosterSync(): void {
}
// Map team → cell (cells carry a cell name; board/management agents have none).
-const TEAM_CELLS: ReadonlyArray = ["backend", "frontend", "ux_ui"] as Team[];
+const TEAM_CELLS: ReadonlyArray = [
+ "backend",
+ "frontend",
+ "ux_ui",
+] as Team[];
function definitionToAgent(def: AgentDefinition): Agent {
return {
@@ -163,8 +346,13 @@ export function useSpawnAgent() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ agentId, request }: { agentId: string; request?: SpawnAgentRequest }) =>
- orchestratorApi.spawn(agentId, request),
+ mutationFn: ({
+ agentId,
+ request,
+ }: {
+ agentId: string;
+ request?: SpawnAgentRequest;
+ }) => orchestratorApi.spawn(agentId, request),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: agentKeys.orchestrator() });
},
@@ -175,8 +363,13 @@ export function useStopAgent() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ agentId, graceful = true }: { agentId: string; graceful?: boolean }) =>
- orchestratorApi.stop(agentId, graceful),
+ mutationFn: ({
+ agentId,
+ graceful = true,
+ }: {
+ agentId: string;
+ graceful?: boolean;
+ }) => orchestratorApi.stop(agentId, graceful),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: agentKeys.orchestrator() });
},
@@ -187,8 +380,13 @@ export function useResolveWait() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ agentId, resolution }: { agentId: string; resolution: string }) =>
- orchestratorApi.resolveWait(agentId, resolution),
+ mutationFn: ({
+ agentId,
+ resolution,
+ }: {
+ agentId: string;
+ resolution: string;
+ }) => orchestratorApi.resolveWait(agentId, resolution),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: agentKeys.orchestrator() });
},
diff --git a/panel/src/hooks/use-channels.ts b/panel/src/hooks/use-channels.ts
index 1f1bb3ee..f9815569 100644
--- a/panel/src/hooks/use-channels.ts
+++ b/panel/src/hooks/use-channels.ts
@@ -22,9 +22,11 @@ function retryUnlessNotFound(failureCount: number, error: unknown): boolean {
export const channelKeys = {
all: ["channels"] as const,
- list: (filters?: ChannelFilters) => [...channelKeys.all, "list", filters] as const,
+ list: (filters?: ChannelFilters) =>
+ [...channelKeys.all, "list", filters] as const,
detail: (id: string) => [...channelKeys.all, "detail", id] as const,
- groups: (channelId: string) => [...channelKeys.all, "groups", channelId] as const,
+ groups: (channelId: string) =>
+ [...channelKeys.all, "groups", channelId] as const,
};
export const sessionKeys = {
@@ -105,7 +107,7 @@ export function useSession(sessionId: string | null) {
relationship_type: link.relationship_type,
};
}
- })
+ }),
);
return {
diff --git a/panel/src/hooks/use-dashboard.ts b/panel/src/hooks/use-dashboard.ts
index 9cf4e7c0..32cab371 100644
--- a/panel/src/hooks/use-dashboard.ts
+++ b/panel/src/hooks/use-dashboard.ts
@@ -62,12 +62,13 @@ export function useMetrics() {
queryKey: dashboardKeys.metrics(),
queryFn: async (): Promise => {
// Fetch all metrics in parallel, including real agent status
- const [velocity, blockers, communication, agentStatus] = await Promise.all([
- dashboardApi.getVelocityMetrics(),
- dashboardApi.getBlockerMetrics(),
- dashboardApi.getCommunicationMetrics(),
- dashboardApi.getAgentStatus(),
- ]);
+ const [velocity, blockers, communication, agentStatus] =
+ await Promise.all([
+ dashboardApi.getVelocityMetrics(),
+ dashboardApi.getBlockerMetrics(),
+ dashboardApi.getCommunicationMetrics(),
+ dashboardApi.getAgentStatus(),
+ ]);
return {
velocity,
blockers,
@@ -255,7 +256,8 @@ export function useCreateAuditorFlag() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: (data: CreateFlagRequest) => dashboardApi.createAuditorFlag(data),
+ mutationFn: (data: CreateFlagRequest) =>
+ dashboardApi.createAuditorFlag(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.auditor() });
queryClient.invalidateQueries({
diff --git a/panel/src/hooks/use-git.ts b/panel/src/hooks/use-git.ts
index 20e1df98..5edc7c8f 100644
--- a/panel/src/hooks/use-git.ts
+++ b/panel/src/hooks/use-git.ts
@@ -37,7 +37,8 @@ import type {
export const gitKeys = {
all: ["git"] as const,
- status: (projectSlug: string) => [...gitKeys.all, "status", projectSlug] as const,
+ status: (projectSlug: string) =>
+ [...gitKeys.all, "status", projectSlug] as const,
log: (projectSlug: string, limit?: number, branch?: string) =>
[...gitKeys.all, "log", projectSlug, { limit, branch }] as const,
branches: (projectSlug: string, includeRemote?: boolean) =>
@@ -53,7 +54,11 @@ export const gitKeys = {
/**
* Get git status for a project
*/
-export function useGitStatus(projectSlug: string, taskId?: string, enabled: boolean = true) {
+export function useGitStatus(
+ projectSlug: string,
+ taskId?: string,
+ enabled: boolean = true,
+) {
return useQuery({
queryKey: gitKeys.status(projectSlug),
queryFn: () => gitApi.getStatus(projectSlug, taskId),
@@ -70,7 +75,7 @@ export function useGitLog(
projectSlug: string,
limit: number = 10,
branch?: string,
- enabled: boolean = true
+ enabled: boolean = true,
) {
return useQuery({
queryKey: gitKeys.log(projectSlug, limit, branch),
@@ -86,7 +91,7 @@ export function useGitLog(
export function useGitBranches(
projectSlug: string,
includeRemote: boolean = false,
- enabled: boolean = true
+ enabled: boolean = true,
) {
return useQuery({
queryKey: gitKeys.branches(projectSlug, includeRemote),
@@ -103,7 +108,7 @@ export function useGitDiff(
projectSlug: string,
staged: boolean = false,
filePath?: string,
- enabled: boolean = true
+ enabled: boolean = true,
) {
return useQuery({
queryKey: gitKeys.diff(projectSlug, staged, filePath),
@@ -127,7 +132,9 @@ export function useGitCommit() {
mutationFn: (request) => gitApi.commit(request),
onSuccess: (_, variables) => {
// Invalidate status and log after commit
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
queryClient.invalidateQueries({
queryKey: [...gitKeys.all, "log", variables.project_slug],
});
@@ -148,7 +155,9 @@ export function useGitPush() {
mutationFn: (request) => gitApi.push(request),
onSuccess: (_, variables) => {
// Invalidate status after push
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
},
});
}
@@ -166,7 +175,9 @@ export function useCreateBranch() {
queryClient.invalidateQueries({
queryKey: [...gitKeys.all, "branches", variables.project_slug],
});
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
},
});
}
@@ -181,7 +192,9 @@ export function useCheckout() {
mutationFn: (request) => gitApi.checkout(request),
onSuccess: (_, variables) => {
// Invalidate everything for this project after checkout
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
queryClient.invalidateQueries({
queryKey: [...gitKeys.all, "log", variables.project_slug],
});
@@ -205,7 +218,9 @@ export function useCreatePR() {
mutationFn: (request) => gitApi.createPR(request),
onSuccess: (_, variables) => {
// Invalidate status after PR creation
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
// Also invalidate tasks since PR creation updates task state
queryClient.invalidateQueries({ queryKey: ["tasks"] });
},
@@ -225,7 +240,9 @@ export function useMergePR() {
queryClient.invalidateQueries({
queryKey: [...gitKeys.all, "branches", variables.project_slug],
});
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
// Also invalidate tasks since merge updates task state
queryClient.invalidateQueries({ queryKey: ["tasks"] });
},
@@ -242,7 +259,9 @@ export function useGitPull() {
mutationFn: (request) => gitApi.pull(request),
onSuccess: (_, variables) => {
// Invalidate status and log after pull
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
queryClient.invalidateQueries({
queryKey: [...gitKeys.all, "log", variables.project_slug],
});
@@ -260,7 +279,9 @@ export function useGitFetch() {
mutationFn: (request) => gitApi.fetch(request),
onSuccess: (_, variables) => {
// Invalidate status after fetch
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
},
});
}
@@ -275,7 +296,9 @@ export function useGitRebase() {
mutationFn: (request) => gitApi.rebase(request),
onSuccess: (_, variables) => {
// Invalidate everything after rebase since history has changed
- queryClient.invalidateQueries({ queryKey: gitKeys.status(variables.project_slug) });
+ queryClient.invalidateQueries({
+ queryKey: gitKeys.status(variables.project_slug),
+ });
queryClient.invalidateQueries({
queryKey: [...gitKeys.all, "log", variables.project_slug],
});
diff --git a/panel/src/hooks/use-journals.ts b/panel/src/hooks/use-journals.ts
index 2e4a4c1e..8e9ed549 100644
--- a/panel/src/hooks/use-journals.ts
+++ b/panel/src/hooks/use-journals.ts
@@ -71,7 +71,7 @@ export function useAgentJournalEntries(
task_id?: string;
limit?: number;
offset?: number;
- }
+ },
) {
return useQuery({
queryKey: [...journalKeys.entries(), "agent", agentIdOrSlug, params],
diff --git a/panel/src/hooks/use-knowledge-base.ts b/panel/src/hooks/use-knowledge-base.ts
index fb817c4e..4f82cc9c 100644
--- a/panel/src/hooks/use-knowledge-base.ts
+++ b/panel/src/hooks/use-knowledge-base.ts
@@ -54,13 +54,18 @@ import type {
export const kbKeys = {
all: ["knowledge-base"] as const,
stats: () => [...kbKeys.all, "stats"] as const,
- indexStats: (indexType: KBIndexType) => [...kbKeys.all, "stats", indexType] as const,
+ indexStats: (indexType: KBIndexType) =>
+ [...kbKeys.all, "stats", indexType] as const,
health: () => [...kbKeys.all, "health"] as const,
staleness: () => [...kbKeys.all, "staleness"] as const,
- search: (query: string, filters?: string) => [...kbKeys.all, "search", query, filters] as const,
- documents: (indexType: KBIndexType, params?: string) => [...kbKeys.all, "documents", indexType, params] as const,
- learnings: (query: string, filters?: string) => [...kbKeys.all, "learnings", query, filters] as const,
- proactiveContext: (taskId: string) => [...kbKeys.all, "proactive-context", taskId] as const,
+ search: (query: string, filters?: string) =>
+ [...kbKeys.all, "search", query, filters] as const,
+ documents: (indexType: KBIndexType, params?: string) =>
+ [...kbKeys.all, "documents", indexType, params] as const,
+ learnings: (query: string, filters?: string) =>
+ [...kbKeys.all, "learnings", query, filters] as const,
+ proactiveContext: (taskId: string) =>
+ [...kbKeys.all, "proactive-context", taskId] as const,
};
// =============================================================================
@@ -116,7 +121,7 @@ export function useKBSearch(params: KBSearchRequest, enabled = true) {
export function useKBDocuments(
indexType: KBIndexType,
params?: { limit?: number; offset?: number },
- enabled = true
+ enabled = true,
) {
return useQuery({
queryKey: kbKeys.documents(indexType, JSON.stringify(params)),
@@ -144,7 +149,8 @@ export function useRAGQuery() {
*/
export function useRAGContext() {
return useMutation({
- mutationFn: (params: RAGQueryRequest) => knowledgeBaseApi.getContext(params),
+ mutationFn: (params: RAGQueryRequest) =>
+ knowledgeBaseApi.getContext(params),
});
}
@@ -338,9 +344,15 @@ export function useRecordLearning() {
/**
* Search learnings (as query for persistent results)
*/
-export function useSearchLearnings(request: LearningSearchRequest, enabled = true) {
+export function useSearchLearnings(
+ request: LearningSearchRequest,
+ enabled = true,
+) {
return useQuery({
- queryKey: kbKeys.learnings(request.query, JSON.stringify({ category: request.category, team: request.team })),
+ queryKey: kbKeys.learnings(
+ request.query,
+ JSON.stringify({ category: request.category, team: request.team }),
+ ),
queryFn: () => knowledgeBaseApi.searchLearnings(request),
enabled: enabled && request.query.length >= 3,
staleTime: 1000 * 60 * 2, // 2 minutes
diff --git a/panel/src/hooks/use-notifications.ts b/panel/src/hooks/use-notifications.ts
index bde78640..da63fc6e 100644
--- a/panel/src/hooks/use-notifications.ts
+++ b/panel/src/hooks/use-notifications.ts
@@ -1,11 +1,15 @@
"use client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
-import { notificationsApi, type NotificationFilters } from "@/lib/api/notifications";
+import {
+ notificationsApi,
+ type NotificationFilters,
+} from "@/lib/api/notifications";
export const notificationKeys = {
all: ["notifications"] as const,
- list: (filters?: NotificationFilters) => [...notificationKeys.all, "list", filters] as const,
+ list: (filters?: NotificationFilters) =>
+ [...notificationKeys.all, "list", filters] as const,
detail: (id: string) => [...notificationKeys.all, "detail", id] as const,
};
@@ -29,7 +33,8 @@ export function useMarkNotificationRead() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: (notificationId: string) => notificationsApi.markRead(notificationId),
+ mutationFn: (notificationId: string) =>
+ notificationsApi.markRead(notificationId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: notificationKeys.all });
},
@@ -40,7 +45,8 @@ export function useAcknowledgeNotification() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: (notificationId: string) => notificationsApi.acknowledge(notificationId),
+ mutationFn: (notificationId: string) =>
+ notificationsApi.acknowledge(notificationId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: notificationKeys.all });
},
diff --git a/panel/src/hooks/use-products.ts b/panel/src/hooks/use-products.ts
index 063c0c7c..dcc90411 100644
--- a/panel/src/hooks/use-products.ts
+++ b/panel/src/hooks/use-products.ts
@@ -10,11 +10,19 @@ export const productKeys = {
};
export function useProducts() {
- return useQuery({ queryKey: productKeys.lists(), queryFn: () => productsApi.list(), staleTime: 60000 });
+ return useQuery({
+ queryKey: productKeys.lists(),
+ queryFn: () => productsApi.list(),
+ staleTime: 60000,
+ });
}
export function useProduct(id: string) {
- return useQuery({ queryKey: productKeys.detail(id), queryFn: () => productsApi.get(id), enabled: !!id });
+ return useQuery({
+ queryKey: productKeys.detail(id),
+ queryFn: () => productsApi.get(id),
+ enabled: !!id,
+ });
}
export function useCreateProduct() {
@@ -28,7 +36,8 @@ export function useCreateProduct() {
export function useUpdateProduct() {
const qc = useQueryClient();
return useMutation({
- mutationFn: ({ id, patch }: { id: string; patch: ProductUpdate }) => productsApi.update(id, patch),
+ mutationFn: ({ id, patch }: { id: string; patch: ProductUpdate }) =>
+ productsApi.update(id, patch),
onSuccess: (_d, v) => {
qc.invalidateQueries({ queryKey: productKeys.lists() });
qc.invalidateQueries({ queryKey: productKeys.detail(v.id) });
diff --git a/panel/src/hooks/use-projects.ts b/panel/src/hooks/use-projects.ts
index 3aad33e4..b8d5c281 100644
--- a/panel/src/hooks/use-projects.ts
+++ b/panel/src/hooks/use-projects.ts
@@ -6,7 +6,8 @@ import type { ProjectCreate, ProjectUpdate } from "@/types";
export const projectKeys = {
all: ["projects"] as const,
lists: () => [...projectKeys.all, "list"] as const,
- list: (filters?: ProjectFilters) => [...projectKeys.lists(), filters] as const,
+ list: (filters?: ProjectFilters) =>
+ [...projectKeys.lists(), filters] as const,
details: () => [...projectKeys.all, "detail"] as const,
detail: (id: string) => [...projectKeys.details(), id] as const,
};
@@ -43,8 +44,13 @@ export function useUpdateProject() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ projectId, updates }: { projectId: string; updates: ProjectUpdate }) =>
- projectsApi.update(projectId, updates),
+ mutationFn: ({
+ projectId,
+ updates,
+ }: {
+ projectId: string;
+ updates: ProjectUpdate;
+ }) => projectsApi.update(projectId, updates),
onSuccess: (project) => {
queryClient.invalidateQueries({ queryKey: projectKeys.lists() });
queryClient.setQueryData(projectKeys.detail(project.id), project);
@@ -56,8 +62,13 @@ export function useSetWorkspace() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ projectId, localPath }: { projectId: string; localPath: string }) =>
- projectsApi.setWorkspace(projectId, localPath),
+ mutationFn: ({
+ projectId,
+ localPath,
+ }: {
+ projectId: string;
+ localPath: string;
+ }) => projectsApi.setWorkspace(projectId, localPath),
onSuccess: (project) => {
queryClient.invalidateQueries({ queryKey: projectKeys.lists() });
queryClient.setQueryData(projectKeys.detail(project.id), project);
diff --git a/panel/src/hooks/use-rate-limit-sync.ts b/panel/src/hooks/use-rate-limit-sync.ts
index 2aae3104..be013490 100644
--- a/panel/src/hooks/use-rate-limit-sync.ts
+++ b/panel/src/hooks/use-rate-limit-sync.ts
@@ -18,11 +18,17 @@ export function useRateLimitSync() {
syncFromApi(response);
} catch (err) {
// Endpoint unavailable — treat as no-op per acceptance criteria
- const status = (err as { response?: { status?: number } })?.response?.status;
+ const status = (err as { response?: { status?: number } })?.response
+ ?.status;
if (status === 404) {
- console.warn("[rate-limits] GET /api/system/rate-limits returned 404 — endpoint not available");
+ console.warn(
+ "[rate-limits] GET /api/system/rate-limits returned 404 — endpoint not available",
+ );
} else {
- console.warn("[rate-limits] GET /api/system/rate-limits unavailable:", err);
+ console.warn(
+ "[rate-limits] GET /api/system/rate-limits unavailable:",
+ err,
+ );
}
}
}, [syncFromApi]);
diff --git a/panel/src/hooks/use-rate-limit-websocket.ts b/panel/src/hooks/use-rate-limit-websocket.ts
index 5a7e45a1..6a376423 100644
--- a/panel/src/hooks/use-rate-limit-websocket.ts
+++ b/panel/src/hooks/use-rate-limit-websocket.ts
@@ -4,7 +4,10 @@ import { useEffect, useRef } from "react";
import { useWebSocket } from "./use-websocket";
import { useRateLimitStore } from "@/store/rate-limit-store";
import { useUsageStore } from "@/store/usage-store";
-import type { RateLimitHitEvent, RateLimitLiftedEvent } from "@/types/rate-limits";
+import type {
+ RateLimitHitEvent,
+ RateLimitLiftedEvent,
+} from "@/types/rate-limits";
/**
* Unified shape for all messages arriving on the /ws/system endpoint.
@@ -43,7 +46,9 @@ interface UseRateLimitWebSocketOptions {
* Accepts an optional onReconnect callback that fires when the connection
* recovers from a reconnecting state.
*/
-export function useRateLimitWebSocket(options: UseRateLimitWebSocketOptions = {}) {
+export function useRateLimitWebSocket(
+ options: UseRateLimitWebSocketOptions = {},
+) {
const { onReconnect } = options;
const prevStateRef = useRef(null);
@@ -53,7 +58,7 @@ export function useRateLimitWebSocket(options: UseRateLimitWebSocketOptions = {}
const { state, lastMessage } = useWebSocket(
"/system",
undefined,
- true
+ true,
);
// Sync WS connection state into useUsageStore for cross-component visibility.
diff --git a/panel/src/hooks/use-scroll-restoration.ts b/panel/src/hooks/use-scroll-restoration.ts
index dbc1b916..64e56a22 100644
--- a/panel/src/hooks/use-scroll-restoration.ts
+++ b/panel/src/hooks/use-scroll-restoration.ts
@@ -10,7 +10,9 @@ import { useEffect, useRef } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { useUIStore } from "@/lib/stores/ui-store";
-export function useScrollRestoration(scrollContainerRef?: React.RefObject) {
+export function useScrollRestoration(
+ scrollContainerRef?: React.RefObject,
+) {
const pathname = usePathname();
const searchParams = useSearchParams();
const { setScrollPosition, getScrollPosition } = useUIStore();
@@ -27,7 +29,10 @@ export function useScrollRestoration(scrollContainerRef?: React.RefObject {
const position = isWindow
? { x: window.scrollX, y: window.scrollY }
- : { x: (container as HTMLElement).scrollLeft, y: (container as HTMLElement).scrollTop };
+ : {
+ x: (container as HTMLElement).scrollLeft,
+ y: (container as HTMLElement).scrollTop,
+ };
setScrollPosition(routeKey, position);
};
diff --git a/panel/src/hooks/use-secretary.ts b/panel/src/hooks/use-secretary.ts
index e69d628e..22207cbe 100644
--- a/panel/src/hooks/use-secretary.ts
+++ b/panel/src/hooks/use-secretary.ts
@@ -70,10 +70,10 @@ export function useSecretary() {
esRef.current = source;
const listener = (e: MessageEvent) => handleEvent(e.data);
LIVE_EVENT_KINDS.forEach((kind) =>
- source.addEventListener(kind, listener as EventListener)
+ source.addEventListener(kind, listener as EventListener),
);
},
- [closeStream, handleEvent]
+ [closeStream, handleEvent],
);
const start = useCallback(
@@ -81,11 +81,13 @@ export function useSecretary() {
const { session_id } = await secretaryApi.startLive(initialMessage);
setSessionId(session_id);
bufRef.current = "";
- setMessages(initialMessage ? [{ role: "user", text: initialMessage }] : []);
+ setMessages(
+ initialMessage ? [{ role: "user", text: initialMessage }] : [],
+ );
openStream(session_id);
return session_id;
},
- [openStream]
+ [openStream],
);
const send = useCallback(
@@ -95,7 +97,7 @@ export function useSecretary() {
setMessages((prev) => [...prev, { role: "user", text }]);
await secretaryApi.sendMessage(sessionId, text);
},
- [sessionId]
+ [sessionId],
);
const stop = useCallback(async (): Promise => {
diff --git a/panel/src/hooks/use-tasks.ts b/panel/src/hooks/use-tasks.ts
index d2218065..307f1cb5 100644
--- a/panel/src/hooks/use-tasks.ts
+++ b/panel/src/hooks/use-tasks.ts
@@ -22,7 +22,8 @@ export const taskKeys = {
list: (filters?: TaskFilters) => [...taskKeys.lists(), filters] as const,
details: () => [...taskKeys.all, "detail"] as const,
detail: (id: string) => [...taskKeys.details(), id] as const,
- subtasks: (parentId: string) => [...taskKeys.all, "subtasks", parentId] as const,
+ subtasks: (parentId: string) =>
+ [...taskKeys.all, "subtasks", parentId] as const,
boardReview: (id: string) => [...taskKeys.all, "board-review", id] as const,
stats: () => [...taskKeys.all, "stats"] as const,
statsByTeam: () => [...taskKeys.all, "stats-by-team"] as const,
@@ -110,7 +111,7 @@ export function useTaskStatsByTeam() {
export function useCreateTask() {
const queryClient = useQueryClient();
-
+
return useMutation({
mutationFn: (task: TaskCreate) => tasksApi.create(task),
onSuccess: () => {
@@ -123,8 +124,13 @@ export function useUpdateTask() {
const queryClient = useQueryClient();
return useMutation({
- mutationFn: ({ taskId, updates }: { taskId: string; updates: TaskUpdate }) =>
- tasksApi.update(taskId, updates),
+ mutationFn: ({
+ taskId,
+ updates,
+ }: {
+ taskId: string;
+ updates: TaskUpdate;
+ }) => tasksApi.update(taskId, updates),
onSuccess: (task) => {
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
queryClient.setQueryData(taskKeys.detail(task.id), task);
@@ -134,7 +140,7 @@ export function useUpdateTask() {
export function useDeleteTask() {
const queryClient = useQueryClient();
-
+
return useMutation({
mutationFn: (taskId: string) => tasksApi.delete(taskId),
onSuccess: () => {
@@ -146,7 +152,7 @@ export function useDeleteTask() {
// Lifecycle action hooks
export function useTaskLifecycle() {
const queryClient = useQueryClient();
-
+
const invalidateTask = (task: Task) => {
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
queryClient.setQueryData(taskKeys.detail(task.id), task);
@@ -163,8 +169,13 @@ export function useTaskLifecycle() {
});
const block = useMutation({
- mutationFn: ({ taskId, blockerId }: { taskId: string; blockerId?: string }) =>
- tasksApi.block(taskId, blockerId),
+ mutationFn: ({
+ taskId,
+ blockerId,
+ }: {
+ taskId: string;
+ blockerId?: string;
+ }) => tasksApi.block(taskId, blockerId),
onSuccess: invalidateTask,
});
@@ -207,8 +218,13 @@ export function useTaskLifecycle() {
});
const complete = useMutation({
- mutationFn: ({ taskId, justification }: { taskId: string; justification: string }) =>
- tasksApi.complete(taskId, justification),
+ mutationFn: ({
+ taskId,
+ justification,
+ }: {
+ taskId: string;
+ justification: string;
+ }) => tasksApi.complete(taskId, justification),
onSuccess: invalidateTask,
});
@@ -242,33 +258,58 @@ export function useTaskLifecycle() {
// Progress tracking
const addProgress = useMutation({
- mutationFn: ({ taskId, request }: { taskId: string; request: ProgressRequest }) =>
- tasksApi.addProgress(taskId, request),
+ mutationFn: ({
+ taskId,
+ request,
+ }: {
+ taskId: string;
+ request: ProgressRequest;
+ }) => tasksApi.addProgress(taskId, request),
onSuccess: invalidateTask,
});
const addCheckpoint = useMutation({
- mutationFn: ({ taskId, request }: { taskId: string; request: CheckpointRequest }) =>
- tasksApi.addCheckpoint(taskId, request),
+ mutationFn: ({
+ taskId,
+ request,
+ }: {
+ taskId: string;
+ request: CheckpointRequest;
+ }) => tasksApi.addCheckpoint(taskId, request),
onSuccess: invalidateTask,
});
const addCommit = useMutation({
- mutationFn: ({ taskId, request }: { taskId: string; request: CommitRequest }) =>
- tasksApi.addCommit(taskId, request),
+ mutationFn: ({
+ taskId,
+ request,
+ }: {
+ taskId: string;
+ request: CommitRequest;
+ }) => tasksApi.addCommit(taskId, request),
onSuccess: invalidateTask,
});
// Soft block and escalation
const softBlock = useMutation({
- mutationFn: ({ taskId, request }: { taskId: string; request: SoftBlockRequest }) =>
- tasksApi.softBlock(taskId, request),
+ mutationFn: ({
+ taskId,
+ request,
+ }: {
+ taskId: string;
+ request: SoftBlockRequest;
+ }) => tasksApi.softBlock(taskId, request),
onSuccess: invalidateTask,
});
const escalate = useMutation({
- mutationFn: ({ taskId, request }: { taskId: string; request: EscalateRequest }) =>
- tasksApi.escalate(taskId, request),
+ mutationFn: ({
+ taskId,
+ request,
+ }: {
+ taskId: string;
+ request: EscalateRequest;
+ }) => tasksApi.escalate(taskId, request),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
},
diff --git a/panel/src/hooks/use-usage.ts b/panel/src/hooks/use-usage.ts
index c0e85fac..28aab588 100644
--- a/panel/src/hooks/use-usage.ts
+++ b/panel/src/hooks/use-usage.ts
@@ -20,13 +20,19 @@ import type {
export const usageKeys = {
all: ["usage"] as const,
- summary: (period: UsagePeriod) => [...usageKeys.all, "summary", period] as const,
- timeSeries: (period: UsagePeriod) => [...usageKeys.all, "time-series", period] as const,
- agentUsage: (period: UsagePeriod) => [...usageKeys.all, "by-agent", period] as const,
- teamUsage: (period: UsagePeriod) => [...usageKeys.all, "by-team", period] as const,
- modelUsage: (period: UsagePeriod) => [...usageKeys.all, "by-model", period] as const,
+ summary: (period: UsagePeriod) =>
+ [...usageKeys.all, "summary", period] as const,
+ timeSeries: (period: UsagePeriod) =>
+ [...usageKeys.all, "time-series", period] as const,
+ agentUsage: (period: UsagePeriod) =>
+ [...usageKeys.all, "by-agent", period] as const,
+ teamUsage: (period: UsagePeriod) =>
+ [...usageKeys.all, "by-team", period] as const,
+ modelUsage: (period: UsagePeriod) =>
+ [...usageKeys.all, "by-model", period] as const,
projection: () => [...usageKeys.all, "projection"] as const,
- cacheEfficiency: (period: UsagePeriod) => [...usageKeys.all, "cache-efficiency", period] as const,
+ cacheEfficiency: (period: UsagePeriod) =>
+ [...usageKeys.all, "cache-efficiency", period] as const,
sessions: (limit: number) => [...usageKeys.all, "sessions", limit] as const,
};
diff --git a/panel/src/hooks/use-websocket.ts b/panel/src/hooks/use-websocket.ts
index 00fae1eb..82e151aa 100644
--- a/panel/src/hooks/use-websocket.ts
+++ b/panel/src/hooks/use-websocket.ts
@@ -1,9 +1,9 @@
"use client";
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
-import {
- WebSocketConnection,
- getWebSocketUrl
+import {
+ WebSocketConnection,
+ getWebSocketUrl,
} from "@/lib/websocket/connection";
import { CEO_AGENT_ID, STREAM_MAX_MESSAGES } from "@/lib/constants";
@@ -51,7 +51,7 @@ export interface NotificationMessage {
export function useWebSocket(
endpoint: string,
queryParams?: Record,
- enabled: boolean = true
+ enabled: boolean = true,
) {
const [state, setState] = useState("disconnected");
const [lastMessage, setLastMessage] = useState(null);
@@ -59,7 +59,9 @@ export function useWebSocket(
const connectionRef = useRef(null);
// Memoize queryParams string to prevent unnecessary reconnects
- const queryString = queryParams ? new URLSearchParams(queryParams).toString() : "";
+ const queryString = queryParams
+ ? new URLSearchParams(queryParams).toString()
+ : "";
useEffect(() => {
// Don't connect if disabled or no endpoint
@@ -77,7 +79,10 @@ export function useWebSocket(
onMessage: (data) => {
const message = data as T;
setLastMessage(message);
- setMessages((prev) => [...prev.slice(-(STREAM_MAX_MESSAGES - 1)), message]);
+ setMessages((prev) => [
+ ...prev.slice(-(STREAM_MAX_MESSAGES - 1)),
+ message,
+ ]);
},
onStateChange: setState,
});
@@ -122,12 +127,18 @@ export function useWebSocket(
* Subscribe to an agent's output stream
*/
export function useAgentStream(agentId: string | null) {
- const { state, lastMessage, messages, clearMessages, isConnected, isConnecting } =
- useWebSocket(
- agentId ? "/agents/" + agentId : "",
- { viewer_id: CEO_AGENT_ID },
- !!agentId
- );
+ const {
+ state,
+ lastMessage,
+ messages,
+ clearMessages,
+ isConnected,
+ isConnecting,
+ } = useWebSocket(
+ agentId ? "/agents/" + agentId : "",
+ { viewer_id: CEO_AGENT_ID },
+ !!agentId,
+ );
// Extract stream chunks
const streamChunks = messages
@@ -153,12 +164,18 @@ export function useAgentStream(agentId: string | null) {
* Subscribe to a channel's message stream
*/
export function useChannelStream(channelId: string | null) {
- const { state, lastMessage, messages, clearMessages, isConnected, isConnecting } =
- useWebSocket(
- channelId ? "/channels/" + channelId : "",
- { agent_id: CEO_AGENT_ID },
- !!channelId
- );
+ const {
+ state,
+ lastMessage,
+ messages,
+ clearMessages,
+ isConnected,
+ isConnecting,
+ } = useWebSocket(
+ channelId ? "/channels/" + channelId : "",
+ { agent_id: CEO_AGENT_ID },
+ !!channelId,
+ );
// Filter to only actual messages
const channelMessages = messages.filter((m) => m.type === "message.new");
@@ -178,12 +195,18 @@ export function useChannelStream(channelId: string | null) {
* Subscribe to notifications for the CEO
*/
export function useNotificationStream() {
- const { state, lastMessage, messages, clearMessages, isConnected, isConnecting } =
- useWebSocket(
- "/notifications/" + CEO_AGENT_ID,
- undefined,
- true
- );
+ const {
+ state,
+ lastMessage,
+ messages,
+ clearMessages,
+ isConnected,
+ isConnecting,
+ } = useWebSocket(
+ "/notifications/" + CEO_AGENT_ID,
+ undefined,
+ true,
+ );
// Filter to notification events, de-duplicated by notification_id so a
// stream replay (e.g. after a websocket reconnect) does not surface — or
@@ -223,7 +246,9 @@ export function useNotificationStream() {
// =============================================================================
export function useConnectionStatus() {
- const [connections, setConnections] = useState>({});
+ const [connections, setConnections] = useState<
+ Record
+ >({});
const updateConnection = useCallback((id: string, state: ConnectionState) => {
setConnections((prev) => ({ ...prev, [id]: state }));
@@ -238,10 +263,12 @@ export function useConnectionStatus() {
}, []);
const hasActiveConnections = Object.values(connections).some(
- (s) => s === "connected" || s === "connecting" || s === "reconnecting"
+ (s) => s === "connected" || s === "connecting" || s === "reconnecting",
);
- const allConnected = Object.values(connections).every((s) => s === "connected");
+ const allConnected = Object.values(connections).every(
+ (s) => s === "connected",
+ );
return {
connections,
diff --git a/panel/src/hooks/use-work-sessions.ts b/panel/src/hooks/use-work-sessions.ts
index 0492c1f6..32c98a9d 100644
--- a/panel/src/hooks/use-work-sessions.ts
+++ b/panel/src/hooks/use-work-sessions.ts
@@ -1,15 +1,20 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
-import { workSessionsApi, type WorkSessionFilters } from "@/lib/api/work-sessions";
+import {
+ workSessionsApi,
+ type WorkSessionFilters,
+} from "@/lib/api/work-sessions";
import type { WorkSession, WorkSessionCreate } from "@/types";
// Query keys
export const workSessionKeys = {
all: ["work-sessions"] as const,
lists: () => [...workSessionKeys.all, "list"] as const,
- list: (filters?: WorkSessionFilters) => [...workSessionKeys.lists(), filters] as const,
+ list: (filters?: WorkSessionFilters) =>
+ [...workSessionKeys.lists(), filters] as const,
details: () => [...workSessionKeys.all, "detail"] as const,
detail: (id: string) => [...workSessionKeys.details(), id] as const,
- forTask: (taskId: string) => [...workSessionKeys.all, "task", taskId] as const,
+ forTask: (taskId: string) =>
+ [...workSessionKeys.all, "task", taskId] as const,
};
// Hooks
@@ -56,19 +61,31 @@ export function useWorkSessionActions() {
queryClient.invalidateQueries({ queryKey: workSessionKeys.lists() });
queryClient.setQueryData(workSessionKeys.detail(session.id), session);
if (session.task_id) {
- queryClient.invalidateQueries({ queryKey: workSessionKeys.forTask(session.task_id) });
+ queryClient.invalidateQueries({
+ queryKey: workSessionKeys.forTask(session.task_id),
+ });
}
};
const addCommit = useMutation({
- mutationFn: ({ sessionId, commitSha }: { sessionId: string; commitSha: string }) =>
- workSessionsApi.addCommit(sessionId, commitSha),
+ mutationFn: ({
+ sessionId,
+ commitSha,
+ }: {
+ sessionId: string;
+ commitSha: string;
+ }) => workSessionsApi.addCommit(sessionId, commitSha),
onSuccess: invalidateSession,
});
const addFiles = useMutation({
- mutationFn: ({ sessionId, filePaths }: { sessionId: string; filePaths: string[] }) =>
- workSessionsApi.addFiles(sessionId, filePaths),
+ mutationFn: ({
+ sessionId,
+ filePaths,
+ }: {
+ sessionId: string;
+ filePaths: string[];
+ }) => workSessionsApi.addFiles(sessionId, filePaths),
onSuccess: invalidateSession,
});
@@ -86,14 +103,24 @@ export function useWorkSessionActions() {
});
const updatePRStatus = useMutation({
- mutationFn: ({ sessionId, prStatus }: { sessionId: string; prStatus: string }) =>
- workSessionsApi.updatePRStatus(sessionId, prStatus),
+ mutationFn: ({
+ sessionId,
+ prStatus,
+ }: {
+ sessionId: string;
+ prStatus: string;
+ }) => workSessionsApi.updatePRStatus(sessionId, prStatus),
onSuccess: invalidateSession,
});
const mergePR = useMutation({
- mutationFn: ({ sessionId, mergedBy }: { sessionId: string; mergedBy: string }) =>
- workSessionsApi.mergePR(sessionId, mergedBy),
+ mutationFn: ({
+ sessionId,
+ mergedBy,
+ }: {
+ sessionId: string;
+ mergedBy: string;
+ }) => workSessionsApi.mergePR(sessionId, mergedBy),
onSuccess: invalidateSession,
});
@@ -103,8 +130,13 @@ export function useWorkSessionActions() {
});
const abandon = useMutation({
- mutationFn: ({ sessionId, reason }: { sessionId: string; reason?: string }) =>
- workSessionsApi.abandon(sessionId, reason),
+ mutationFn: ({
+ sessionId,
+ reason,
+ }: {
+ sessionId: string;
+ reason?: string;
+ }) => workSessionsApi.abandon(sessionId, reason),
onSuccess: invalidateSession,
});
diff --git a/panel/src/lib/__tests__/agent-definitions.test.ts b/panel/src/lib/__tests__/agent-definitions.test.ts
index b818a75d..c3963ef9 100644
--- a/panel/src/lib/__tests__/agent-definitions.test.ts
+++ b/panel/src/lib/__tests__/agent-definitions.test.ts
@@ -18,7 +18,7 @@ import { AgentRole, Team } from "@/types";
const makeAgent = (
id: string,
role: AgentRole | null,
- team: Team | null
+ team: Team | null,
): AgentDefinition => ({ id, name: id, role, team });
// ---------------------------------------------------------------------------
@@ -279,7 +279,7 @@ describe("getSupportAgents", () => {
AgentRole.PROMPTER,
AgentRole.SECRETARY,
AgentRole.PR_REVIEWER,
- ])
+ ]),
);
});
diff --git a/panel/src/lib/__tests__/client.test.ts b/panel/src/lib/__tests__/client.test.ts
index 1f88110c..3b7a1887 100644
--- a/panel/src/lib/__tests__/client.test.ts
+++ b/panel/src/lib/__tests__/client.test.ts
@@ -51,15 +51,16 @@ function makeAxiosError(opts: {
isAxiosError: true as const,
code: opts.code,
message: opts.message ?? "axios error",
- response: opts.status !== undefined
- ? {
- status: opts.status,
- data: { detail: opts.detail } as { detail?: unknown },
- headers: {} as Record,
- config: {} as never,
- statusText: "",
- }
- : undefined,
+ response:
+ opts.status !== undefined
+ ? {
+ status: opts.status,
+ data: { detail: opts.detail } as { detail?: unknown },
+ headers: {} as Record,
+ config: {} as never,
+ statusText: "",
+ }
+ : undefined,
config: {} as never,
name: "AxiosError" as const,
toJSON: () => ({}),
@@ -78,14 +79,14 @@ describe("getErrorMessage — error codes", () => {
it("returns timeout message for ECONNABORTED", () => {
const err = makeAxiosError({ code: "ECONNABORTED" });
expect(getErrorMessage(err)).toBe(
- "Request timed out. The server may be busy."
+ "Request timed out. The server may be busy.",
);
});
it("returns network message for ERR_NETWORK", () => {
const err = makeAxiosError({ code: "ERR_NETWORK" });
expect(getErrorMessage(err)).toBe(
- "Cannot connect to server. Check if the backend is running."
+ "Cannot connect to server. Check if the backend is running.",
);
});
});
@@ -153,7 +154,7 @@ describe("getErrorMessage — HTTP status codes", () => {
it("returns auth message for 401", () => {
const err = makeAxiosError({ status: 401 });
expect(getErrorMessage(err)).toBe(
- "Authentication required. Please refresh the page."
+ "Authentication required. Please refresh the page.",
);
});
@@ -164,9 +165,7 @@ describe("getErrorMessage — HTTP status codes", () => {
it("returns not-found message for 404", () => {
const err = makeAxiosError({ status: 404 });
- expect(getErrorMessage(err)).toBe(
- "The requested resource was not found."
- );
+ expect(getErrorMessage(err)).toBe("The requested resource was not found.");
});
it("returns validation message for 422 without detail", () => {
@@ -209,7 +208,7 @@ describe("getErrorMessage — non-Axios fallbacks", () => {
it("returns generic message for a plain object without isAxiosError", () => {
expect(getErrorMessage({ code: "SOME_CODE" })).toBe(
- "An unexpected error occurred"
+ "An unexpected error occurred",
);
});
});
diff --git a/panel/src/lib/agent-definitions.ts b/panel/src/lib/agent-definitions.ts
index de15e23a..7aeeb6ce 100644
--- a/panel/src/lib/agent-definitions.ts
+++ b/panel/src/lib/agent-definitions.ts
@@ -27,32 +27,37 @@ export const getBoardAgents = (agents: AgentDefinition[] | undefined | null) =>
// (Intake, Secretary, root PR Reviewer) — those are grouped as Support.
a.role === AgentRole.PRODUCT_OWNER ||
a.role === AgentRole.HEAD_MARKETING ||
- a.role === AgentRole.AUDITOR
+ a.role === AgentRole.AUDITOR,
);
export const getMainPm = (agents: AgentDefinition[] | undefined | null) =>
(agents ?? []).filter((a) => a.role === AgentRole.MAIN_PM);
-export const getBackendAgents = (agents: AgentDefinition[] | undefined | null) =>
- (agents ?? []).filter((a) => a.team === Team.BACKEND);
+export const getBackendAgents = (
+ agents: AgentDefinition[] | undefined | null,
+) => (agents ?? []).filter((a) => a.team === Team.BACKEND);
-export const getFrontendAgents = (agents: AgentDefinition[] | undefined | null) =>
- (agents ?? []).filter((a) => a.team === Team.FRONTEND);
+export const getFrontendAgents = (
+ agents: AgentDefinition[] | undefined | null,
+) => (agents ?? []).filter((a) => a.team === Team.FRONTEND);
export const getUxAgents = (agents: AgentDefinition[] | undefined | null) =>
(agents ?? []).filter((a) => a.team === Team.UX_UI);
-export const getMarketingAgents = (agents: AgentDefinition[] | undefined | null) =>
- (agents ?? []).filter((a) => a.team === Team.MARKETING);
+export const getMarketingAgents = (
+ agents: AgentDefinition[] | undefined | null,
+) => (agents ?? []).filter((a) => a.team === Team.MARKETING);
// CEO-direct support roles — Intake (Prompter), Secretary, and the root PR
// Reviewer. Board-adjacent and spawned on demand, but NOT Board members. Cell
// PR reviewers carry their cell's team and stay grouped under that cell; only
// the root reviewer carries team=board, which is how it is distinguished here.
-export const getSupportAgents = (agents: AgentDefinition[] | undefined | null) =>
+export const getSupportAgents = (
+ agents: AgentDefinition[] | undefined | null,
+) =>
(agents ?? []).filter(
(a) =>
a.role === AgentRole.PROMPTER ||
a.role === AgentRole.SECRETARY ||
- (a.role === AgentRole.PR_REVIEWER && a.team === Team.BOARD)
+ (a.role === AgentRole.PR_REVIEWER && a.team === Team.BOARD),
);
diff --git a/panel/src/lib/agent-utils.ts b/panel/src/lib/agent-utils.ts
index 2c2ce4e8..abe606e6 100644
--- a/panel/src/lib/agent-utils.ts
+++ b/panel/src/lib/agent-utils.ts
@@ -85,7 +85,7 @@ const AGENT_NAMES: Record = {
"main-pm": "Main PM",
"product-owner": "Product Owner",
"head-marketing": "Head Marketing",
- "auditor": "Auditor",
+ auditor: "Auditor",
// Backend Cell
"be-pm": "Backend PM",
"be-dev-1": "Backend Dev 1",
@@ -105,8 +105,8 @@ const AGENT_NAMES: Record = {
"ux-qa": "UX/UI QA",
"ux-doc": "UX/UI Doc",
// CEO (human)
- "ceo": "CEO",
- "CEO": "CEO",
+ ceo: "CEO",
+ CEO: "CEO",
// Board-adjacent singletons
"intake-1": "Intake",
"secretary-1": "Secretary",
@@ -136,7 +136,9 @@ export function resolveToSlug(agentId: string | null | undefined): string {
* @param agentId - The agent identifier (slug or UUID)
* @returns The human-readable name, or the slug, or "Unknown Agent" for unrecognized UUIDs
*/
-export function getAgentDisplayName(agentId: string | null | undefined): string {
+export function getAgentDisplayName(
+ agentId: string | null | undefined,
+): string {
if (!agentId) return "Unassigned";
// Live roster first — keyed by both UUID and slug, so a direct hit gives the
@@ -170,7 +172,7 @@ const AGENT_CODES: Record = {
"main-pm": "MPM",
"product-owner": "PO",
"head-marketing": "MKT",
- "auditor": "AUD",
+ auditor: "AUD",
// Backend Cell
"be-pm": "BPM",
"be-dev-1": "BD1",
@@ -190,8 +192,8 @@ const AGENT_CODES: Record = {
"ux-qa": "UQA",
"ux-doc": "UDC",
// CEO
- "ceo": "CEO",
- "CEO": "CEO",
+ ceo: "CEO",
+ CEO: "CEO",
// Board-adjacent singletons
"intake-1": "INT",
"secretary-1": "SEC",
diff --git a/panel/src/lib/api/a2a.ts b/panel/src/lib/api/a2a.ts
index 2437bafc..57c99f81 100644
--- a/panel/src/lib/api/a2a.ts
+++ b/panel/src/lib/api/a2a.ts
@@ -70,7 +70,9 @@ export const a2aApi = {
/**
* Send a message to another agent
*/
- sendMessage: async (request: A2AMessageSendRequest): Promise => {
+ sendMessage: async (
+ request: A2AMessageSendRequest,
+ ): Promise => {
if (isMockMode()) {
return {
message_id: `msg-${Date.now()}`,
@@ -78,21 +80,29 @@ export const a2aApi = {
delivered_at: new Date().toISOString(),
};
}
- const { data } = await api.post("/a2a/message/send", request);
+ const { data } = await api.post(
+ "/a2a/message/send",
+ request,
+ );
return data;
},
/**
* Stream a message to another agent (for long content)
*/
- streamMessage: async (request: A2AMessageSendRequest): Promise => {
+ streamMessage: async (
+ request: A2AMessageSendRequest,
+ ): Promise => {
if (isMockMode()) {
return {
message_id: `msg-${Date.now()}`,
status: "streaming",
};
}
- const { data } = await api.post("/a2a/message/stream", request);
+ const { data } = await api.post(
+ "/a2a/message/stream",
+ request,
+ );
return data;
},
@@ -140,14 +150,18 @@ export const a2aApi = {
/**
* Cancel a task via A2A protocol
*/
- cancelTask: async (taskId: string): Promise<{ status: string; task_id: string }> => {
+ cancelTask: async (
+ taskId: string,
+ ): Promise<{ status: string; task_id: string }> => {
if (isMockMode()) {
return {
status: "cancelled",
task_id: taskId,
};
}
- const { data } = await api.post<{ status: string; task_id: string }>(`/a2a/tasks/${taskId}/cancel`);
+ const { data } = await api.post<{ status: string; task_id: string }>(
+ `/a2a/tasks/${taskId}/cancel`,
+ );
return data;
},
diff --git a/panel/src/lib/api/channels.ts b/panel/src/lib/api/channels.ts
index 2919eb75..c0bf3666 100644
--- a/panel/src/lib/api/channels.ts
+++ b/panel/src/lib/api/channels.ts
@@ -37,7 +37,9 @@ export const channelsApi = {
}
return channels;
}
- const { data } = await api.get>("/channels", { params: filters });
+ const { data } = await api.get>("/channels", {
+ params: filters,
+ });
return data.items;
},
@@ -74,7 +76,9 @@ export const channelsApi = {
if (isMockMode()) {
return mockGroups as Group[];
}
- const { data } = await api.get("/channels/" + channelId + "/groups");
+ const { data } = await api.get(
+ "/channels/" + channelId + "/groups",
+ );
return data;
},
@@ -103,7 +107,10 @@ export const channelsApi = {
},
// Update a channel (PM/CEO only)
- update: async (channelId: string, updates: ChannelUpdate): Promise => {
+ update: async (
+ channelId: string,
+ updates: ChannelUpdate,
+ ): Promise => {
if (isMockMode()) {
const idx = mockChannels.findIndex((c) => c.id === channelId);
if (idx === -1) throw new Error("Channel not found");
@@ -111,7 +118,10 @@ export const channelsApi = {
(mockChannels as Channel[])[idx] = updated;
return updated;
}
- const { data } = await api.patch("/channels/" + channelId, updates);
+ const { data } = await api.patch(
+ "/channels/" + channelId,
+ updates,
+ );
return data;
},
@@ -120,7 +130,9 @@ export const channelsApi = {
if (isMockMode()) {
return;
}
- await api.post("/channels/" + channelId + "/add-member", { agent_id: agentId });
+ await api.post("/channels/" + channelId + "/add-member", {
+ agent_id: agentId,
+ });
},
// Remove a member from a channel (PM/CEO only)
diff --git a/panel/src/lib/api/client.ts b/panel/src/lib/api/client.ts
index 0e187738..ba1e3821 100644
--- a/panel/src/lib/api/client.ts
+++ b/panel/src/lib/api/client.ts
@@ -39,7 +39,7 @@ api.interceptors.request.use(
(error) => {
console.error("[API] Request setup error:", error);
return Promise.reject(error);
- }
+ },
);
// Response interceptor for comprehensive error handling
@@ -47,7 +47,9 @@ api.interceptors.response.use(
(response) => {
// Log successful responses in development
if (process.env.NODE_ENV === "development") {
- console.log(`[API] ✓ ${response.config.method?.toUpperCase()} ${response.config.url}`);
+ console.log(
+ `[API] ✓ ${response.config.method?.toUpperCase()} ${response.config.url}`,
+ );
}
return response;
},
@@ -56,7 +58,9 @@ api.interceptors.response.use(
const status = error.response?.status;
const url = error.config?.url;
const method = error.config?.method?.toUpperCase();
- const errorData = error.response?.data as Record | undefined;
+ const errorData = error.response?.data as
+ | Record
+ | undefined;
const errorDetail = errorData?.detail || error.message;
// -------------------------------------------------------------------------
@@ -64,13 +68,16 @@ api.interceptors.response.use(
// -------------------------------------------------------------------------
if (status === 429) {
const retryAfterHeader = error.response?.headers?.["retry-after"];
- const retryAfterSeconds = retryAfterHeader ? parseInt(String(retryAfterHeader), 10) : 60;
+ const retryAfterSeconds = retryAfterHeader
+ ? parseInt(String(retryAfterHeader), 10)
+ : 60;
const safeRetryAfter = isNaN(retryAfterSeconds) ? 60 : retryAfterSeconds;
// Extract provider from custom header or fall back to URL path heuristics
const providerHeader = error.response?.headers?.["x-provider"];
const urlProvider = url
- ? (["anthropic", "openai", "ollama"].find((p) => url.includes(p)) ?? "unknown")
+ ? (["anthropic", "openai", "ollama"].find((p) => url.includes(p)) ??
+ "unknown")
: "unknown";
const provider = (providerHeader as string | undefined) ?? urlProvider;
@@ -91,14 +98,14 @@ api.interceptors.response.use(
if (retryCount < RATE_LIMIT_MAX_RETRIES) {
// Wait retryAfterSeconds before retrying — interceptor re-runs on each subsequent 429
const delayMs = safeRetryAfter * 1000;
- return new Promise((resolve) => setTimeout(resolve, delayMs)).then(
- () => api(error.config!)
- );
+ return new Promise((resolve) =>
+ setTimeout(resolve, delayMs),
+ ).then(() => api(error.config!));
}
}
// Retries exhausted — notify the user via Sonner toast
toast.warning(
- `Rate limited by ${provider}. The system has paused operations and will resume automatically in ~${safeRetryAfter}s.`
+ `Rate limited by ${provider}. The system has paused operations and will resume automatically in ~${safeRetryAfter}s.`,
);
}
@@ -111,23 +118,35 @@ api.interceptors.response.use(
// Specific error handling with helpful messages
if (error.code === "ECONNABORTED") {
- console.error("[API] Request timed out - backend may be overloaded or unavailable");
+ console.error(
+ "[API] Request timed out - backend may be overloaded or unavailable",
+ );
} else if (error.code === "ERR_NETWORK") {
- console.error("[API] Network error - check if backend is running at", API_URL);
+ console.error(
+ "[API] Network error - check if backend is running at",
+ API_URL,
+ );
} else if (status === 401) {
console.error("[API] Unauthorized - check API authentication headers");
} else if (status === 403) {
- console.error("[API] Forbidden - insufficient permissions for this action");
+ console.error(
+ "[API] Forbidden - insufficient permissions for this action",
+ );
} else if (status === 404) {
console.error("[API] Not found - endpoint may not exist:", url);
} else if (status === 422) {
- console.error("[API] Validation error - request data is invalid:", errorDetail);
+ console.error(
+ "[API] Validation error - request data is invalid:",
+ errorDetail,
+ );
} else if (status && status >= 500) {
- console.error("[API] Server error - backend encountered an internal error");
+ console.error(
+ "[API] Server error - backend encountered an internal error",
+ );
}
return Promise.reject(error);
- }
+ },
);
/**
@@ -187,7 +206,8 @@ export function getErrorMessage(error: unknown): string {
// Check for HTTP status
const status = axiosError.response?.status;
- if (status === 401) return "Authentication required. Please refresh the page.";
+ if (status === 401)
+ return "Authentication required. Please refresh the page.";
if (status === 403) return "Permission denied for this action.";
if (status === 404) return "The requested resource was not found.";
if (status === 422) return "Invalid request data.";
@@ -195,7 +215,9 @@ export function getErrorMessage(error: unknown): string {
}
// Generic error
- return error instanceof Error ? error.message : "An unexpected error occurred";
+ return error instanceof Error
+ ? error.message
+ : "An unexpected error occurred";
}
export { api, API_URL };
diff --git a/panel/src/lib/api/cockpit.ts b/panel/src/lib/api/cockpit.ts
index b20d5906..1b6812f9 100644
--- a/panel/src/lib/api/cockpit.ts
+++ b/panel/src/lib/api/cockpit.ts
@@ -39,7 +39,7 @@ export const cockpitApi = {
// lighter than /summary, which runs the full goals/usage/counts/pitches fan-out.
signals: async (): Promise => {
const { data } = await api.get<{ signals: CockpitSignal[] }>(
- "/cockpit/signals"
+ "/cockpit/signals",
);
return data.signals;
},
diff --git a/panel/src/lib/api/dashboard.ts b/panel/src/lib/api/dashboard.ts
index ca793bdc..6a96cdf3 100644
--- a/panel/src/lib/api/dashboard.ts
+++ b/panel/src/lib/api/dashboard.ts
@@ -88,11 +88,21 @@ export interface CreateReportRequest {
// Helper to create mock kanban board from tasks
function createMockKanbanBoard(tasks: Task[], team?: Team): KanbanBoard {
const filteredTasks = team ? tasks.filter((t) => t.team === team) : tasks;
- const pendingTasks = filteredTasks.filter((t) => t.status === TaskStatus.PENDING);
- const inProgressTasks = filteredTasks.filter((t) => t.status === TaskStatus.IN_PROGRESS);
- const blockedTasks = filteredTasks.filter((t) => t.status === TaskStatus.BLOCKED);
- const awaitingQaTasks = filteredTasks.filter((t) => t.status === TaskStatus.AWAITING_QA);
- const completedTasks = filteredTasks.filter((t) => t.status === TaskStatus.COMPLETED);
+ const pendingTasks = filteredTasks.filter(
+ (t) => t.status === TaskStatus.PENDING,
+ );
+ const inProgressTasks = filteredTasks.filter(
+ (t) => t.status === TaskStatus.IN_PROGRESS,
+ );
+ const blockedTasks = filteredTasks.filter(
+ (t) => t.status === TaskStatus.BLOCKED,
+ );
+ const awaitingQaTasks = filteredTasks.filter(
+ (t) => t.status === TaskStatus.AWAITING_QA,
+ );
+ const completedTasks = filteredTasks.filter(
+ (t) => t.status === TaskStatus.COMPLETED,
+ );
return {
columns: [
@@ -170,25 +180,32 @@ export const dashboardApi = {
average_completion_time_hours: 4.5,
};
}
- const { data } = await api.get("/dashboard/metrics/velocity");
+ const { data } = await api.get(
+ "/dashboard/metrics/velocity",
+ );
return data;
},
getBlockerMetrics: async (): Promise => {
if (isMockMode()) {
- const blockedTasks = mockTasks.filter((t) => t.status === TaskStatus.BLOCKED);
+ const blockedTasks = mockTasks.filter(
+ (t) => t.status === TaskStatus.BLOCKED,
+ );
return {
total_blocked: blockedTasks.length,
blocked_by_team: {
backend: blockedTasks.filter((t) => t.team === Team.BACKEND).length,
frontend: blockedTasks.filter((t) => t.team === Team.FRONTEND).length,
ux_ui: blockedTasks.filter((t) => t.team === Team.UX_UI).length,
- marketing: blockedTasks.filter((t) => t.team === Team.MARKETING).length,
+ marketing: blockedTasks.filter((t) => t.team === Team.MARKETING)
+ .length,
},
longest_blocked_hours: 24,
};
}
- const { data } = await api.get("/dashboard/metrics/blockers");
+ const { data } = await api.get(
+ "/dashboard/metrics/blockers",
+ );
return data;
},
@@ -200,7 +217,9 @@ export const dashboardApi = {
notifications_pending: 3,
};
}
- const { data } = await api.get("/dashboard/metrics/communication");
+ const { data } = await api.get(
+ "/dashboard/metrics/communication",
+ );
return data;
},
@@ -253,7 +272,10 @@ export const dashboardApi = {
if (isMockMode()) {
return getMockRecentActivity().slice(0, limit);
}
- const { data } = await api.get<{ period_hours: number; activity: unknown[] }>("/dashboard/activity/recent", {
+ const { data } = await api.get<{
+ period_hours: number;
+ activity: unknown[];
+ }>("/dashboard/activity/recent", {
params: { hours, limit },
});
// Backend returns { period_hours, activity }, extract the activity array
@@ -284,7 +306,9 @@ export const dashboardApi = {
flags = flags.filter((f) => f.severity === params.severity);
}
if (params?.resolved !== undefined) {
- flags = flags.filter((f) => (params.resolved ? f.resolved_at : !f.resolved_at));
+ flags = flags.filter((f) =>
+ params.resolved ? f.resolved_at : !f.resolved_at,
+ );
}
return flags;
}
@@ -296,7 +320,7 @@ export const dashboardApi = {
// Create an auditor flag
createAuditorFlag: async (
- request: CreateFlagRequest
+ request: CreateFlagRequest,
): Promise => {
if (isMockMode()) {
const newFlag: AuditorFlag = {
@@ -316,7 +340,7 @@ export const dashboardApi = {
}
const { data } = await api.post(
"/dashboard/auditor/flags",
- request
+ request,
);
return data;
},
@@ -324,7 +348,7 @@ export const dashboardApi = {
// Resolve an auditor flag
resolveAuditorFlag: async (
flagId: string,
- notes?: string
+ notes?: string,
): Promise<{ status: string; flag_id: string }> => {
if (isMockMode()) {
const flags = mockAuditorFlags as AuditorFlag[];
@@ -339,9 +363,13 @@ export const dashboardApi = {
}
throw new Error("Flag not found");
}
- const { data } = await api.put(`/dashboard/auditor/flags/${flagId}/resolve`, null, {
- params: { notes },
- });
+ const { data } = await api.put(
+ `/dashboard/auditor/flags/${flagId}/resolve`,
+ null,
+ {
+ params: { notes },
+ },
+ );
return data;
},
@@ -362,14 +390,14 @@ export const dashboardApi = {
}
const { data } = await api.get(
"/dashboard/auditor/reports",
- { params }
+ { params },
);
return data;
},
// Create an auditor report
createAuditorReport: async (
- request: CreateReportRequest
+ request: CreateReportRequest,
): Promise => {
if (isMockMode()) {
const newReport: AuditorReport = {
@@ -386,14 +414,14 @@ export const dashboardApi = {
}
const { data } = await api.post(
"/dashboard/auditor/reports",
- request
+ request,
);
return data;
},
// Send a report to CEO
sendAuditorReport: async (
- reportId: string
+ reportId: string,
): Promise<{ status: string; report_id: string }> => {
if (isMockMode()) {
const reports = mockAuditorReports as AuditorReport[];
@@ -408,7 +436,7 @@ export const dashboardApi = {
throw new Error("Report not found");
}
const { data } = await api.post(
- `/dashboard/auditor/reports/${reportId}/send`
+ `/dashboard/auditor/reports/${reportId}/send`,
);
return data;
},
@@ -434,7 +462,9 @@ export const dashboardApi = {
// Get blocker details for CEO
getCeoBlockerDetails: async () => {
if (isMockMode()) {
- const blockedTasks = mockTasks.filter((t) => t.status === TaskStatus.BLOCKED);
+ const blockedTasks = mockTasks.filter(
+ (t) => t.status === TaskStatus.BLOCKED,
+ );
return {
total_blocked: blockedTasks.length,
blockers: blockedTasks.map((t) => ({
@@ -463,7 +493,9 @@ export const dashboardApi = {
marketing: 0,
},
daily_breakdown: Array.from({ length: days }, (_, i) => ({
- date: new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
+ date: new Date(Date.now() - i * 24 * 60 * 60 * 1000)
+ .toISOString()
+ .split("T")[0],
completed: Math.floor(Math.random() * 5) + 1,
})),
};
@@ -513,10 +545,13 @@ export const dashboardApi = {
total: tasks.length,
by_status: {
pending: tasks.filter((t) => t.status === TaskStatus.PENDING).length,
- in_progress: tasks.filter((t) => t.status === TaskStatus.IN_PROGRESS).length,
+ in_progress: tasks.filter((t) => t.status === TaskStatus.IN_PROGRESS)
+ .length,
blocked: tasks.filter((t) => t.status === TaskStatus.BLOCKED).length,
- awaiting_qa: tasks.filter((t) => t.status === TaskStatus.AWAITING_QA).length,
- completed: tasks.filter((t) => t.status === TaskStatus.COMPLETED).length,
+ awaiting_qa: tasks.filter((t) => t.status === TaskStatus.AWAITING_QA)
+ .length,
+ completed: tasks.filter((t) => t.status === TaskStatus.COMPLETED)
+ .length,
},
};
}
diff --git a/panel/src/lib/api/git.ts b/panel/src/lib/api/git.ts
index 392ea872..9a390f5e 100644
--- a/panel/src/lib/api/git.ts
+++ b/panel/src/lib/api/git.ts
@@ -64,8 +64,18 @@ const mockBranches: GitBranchListResponse = {
project_slug: "roboco",
current_branch: "feature/backend/abc12345",
branches: [
- { name: "main", is_current: false, is_remote: false, last_commit: "def456" },
- { name: "feature/backend/abc12345", is_current: true, is_remote: false, last_commit: "abc123" },
+ {
+ name: "main",
+ is_current: false,
+ is_remote: false,
+ last_commit: "def456",
+ },
+ {
+ name: "feature/backend/abc12345",
+ is_current: true,
+ is_remote: false,
+ last_commit: "abc123",
+ },
],
};
@@ -81,7 +91,10 @@ export const gitApi = {
/**
* Get git status for a project
*/
- getStatus: async (projectSlug: string, taskId?: string): Promise => {
+ getStatus: async (
+ projectSlug: string,
+ taskId?: string,
+ ): Promise => {
if (isMockMode()) {
return { ...mockGitStatus, project_slug: projectSlug };
}
@@ -97,7 +110,7 @@ export const gitApi = {
getLog: async (
projectSlug: string,
limit: number = 10,
- branch?: string
+ branch?: string,
): Promise => {
if (isMockMode()) {
return { ...mockGitLog, project_slug: projectSlug };
@@ -113,7 +126,7 @@ export const gitApi = {
*/
getBranches: async (
projectSlug: string,
- includeRemote: boolean = false
+ includeRemote: boolean = false,
): Promise => {
if (isMockMode()) {
return { ...mockBranches, project_slug: projectSlug };
@@ -130,7 +143,7 @@ export const gitApi = {
getDiff: async (
projectSlug: string,
staged: boolean = false,
- filePath?: string
+ filePath?: string,
): Promise => {
if (isMockMode()) {
return {
@@ -187,7 +200,9 @@ export const gitApi = {
/**
* Create a task branch (PM only)
*/
- createBranch: async (request: GitCreateBranchRequest): Promise => {
+ createBranch: async (
+ request: GitCreateBranchRequest,
+ ): Promise => {
if (isMockMode()) {
return {
branch_name: `${request.branch_type}/backend/${request.task_id.slice(0, 8)}`,
@@ -195,28 +210,38 @@ export const gitApi = {
project_slug: request.project_slug,
};
}
- const { data } = await api.post("/git/branch/create", request);
+ const { data } = await api.post(
+ "/git/branch/create",
+ request,
+ );
return data;
},
/**
* Checkout a branch
*/
- checkout: async (request: GitCheckoutRequest): Promise => {
+ checkout: async (
+ request: GitCheckoutRequest,
+ ): Promise => {
if (isMockMode()) {
return {
branch: request.branch,
project_slug: request.project_slug,
};
}
- const { data } = await api.post("/git/checkout", request);
+ const { data } = await api.post(
+ "/git/checkout",
+ request,
+ );
return data;
},
/**
* Create a pull request
*/
- createPR: async (request: GitCreatePRRequest): Promise => {
+ createPR: async (
+ request: GitCreatePRRequest,
+ ): Promise => {
if (isMockMode()) {
return {
pr_number: 42,
@@ -226,7 +251,10 @@ export const gitApi = {
target_branch: "main",
};
}
- const { data } = await api.post("/git/pr/create", request);
+ const { data } = await api.post(
+ "/git/pr/create",
+ request,
+ );
return data;
},
@@ -242,7 +270,10 @@ export const gitApi = {
target_branch: "main",
};
}
- const { data } = await api.post("/git/pr/merge", request);
+ const { data } = await api.post(
+ "/git/pr/merge",
+ request,
+ );
return data;
},
diff --git a/panel/src/lib/api/journals.ts b/panel/src/lib/api/journals.ts
index 85108503..38d3bd98 100644
--- a/panel/src/lib/api/journals.ts
+++ b/panel/src/lib/api/journals.ts
@@ -14,11 +14,7 @@ import {
JournalStats,
GrowthMetrics,
} from "@/types";
-import {
- isMockMode,
- mockJournals,
- mockJournalEntries,
-} from "@/lib/mock-data";
+import { isMockMode, mockJournals, mockJournalEntries } from "@/lib/mock-data";
// =============================================================================
// JOURNAL ENDPOINTS
@@ -59,7 +55,7 @@ async function listAgentEntries(
task_id?: string;
limit?: number;
offset?: number;
- }
+ },
): Promise {
if (isMockMode()) {
let entries = [...mockJournalEntries] as JournalEntry[];
@@ -75,7 +71,7 @@ async function listAgentEntries(
}
const response = await api.get(
`/journals/${agentIdOrSlug}/entries`,
- { params }
+ { params },
);
return response.data;
}
@@ -195,7 +191,7 @@ async function addTaskReflection(data: {
}
const response = await api.post(
"/journals/me/reflections",
- data
+ data,
);
return response.data;
}
@@ -329,11 +325,18 @@ async function getMyStats(): Promise {
return {
total_entries: entries.length,
entries_by_type: {
- task_reflection: entries.filter((e) => e.type === JournalEntryType.TASK_REFLECTION).length,
- decision_log: entries.filter((e) => e.type === JournalEntryType.DECISION_LOG).length,
- learning: entries.filter((e) => e.type === JournalEntryType.LEARNING).length,
- struggle: entries.filter((e) => e.type === JournalEntryType.STRUGGLE).length,
- general: entries.filter((e) => e.type === JournalEntryType.GENERAL).length,
+ task_reflection: entries.filter(
+ (e) => e.type === JournalEntryType.TASK_REFLECTION,
+ ).length,
+ decision_log: entries.filter(
+ (e) => e.type === JournalEntryType.DECISION_LOG,
+ ).length,
+ learning: entries.filter((e) => e.type === JournalEntryType.LEARNING)
+ .length,
+ struggle: entries.filter((e) => e.type === JournalEntryType.STRUGGLE)
+ .length,
+ general: entries.filter((e) => e.type === JournalEntryType.GENERAL)
+ .length,
},
last_entry_at: entries[0]?.created_at ?? null,
has_summary: false,
@@ -350,10 +353,18 @@ async function getMyGrowthMetrics(): Promise {
if (isMockMode()) {
const entries = mockJournalEntries as JournalEntry[];
return {
- total_reflections: entries.filter((e) => e.type === JournalEntryType.TASK_REFLECTION).length,
- total_learnings: entries.filter((e) => e.type === JournalEntryType.LEARNING).length,
- total_struggles: entries.filter((e) => e.type === JournalEntryType.STRUGGLE).length,
- total_decisions: entries.filter((e) => e.type === JournalEntryType.DECISION_LOG).length,
+ total_reflections: entries.filter(
+ (e) => e.type === JournalEntryType.TASK_REFLECTION,
+ ).length,
+ total_learnings: entries.filter(
+ (e) => e.type === JournalEntryType.LEARNING,
+ ).length,
+ total_struggles: entries.filter(
+ (e) => e.type === JournalEntryType.STRUGGLE,
+ ).length,
+ total_decisions: entries.filter(
+ (e) => e.type === JournalEntryType.DECISION_LOG,
+ ).length,
struggle_resolution_rate: 0.7,
learning_frequency: 3.5,
sentiment_trend: "improving",
@@ -372,7 +383,7 @@ async function getMyGrowthMetrics(): Promise {
*/
async function searchEntries(
query: string,
- topK: number = 10
+ topK: number = 10,
): Promise {
if (isMockMode()) {
// Simple text search for mock mode
@@ -381,7 +392,7 @@ async function searchEntries(
.filter(
(e) =>
e.title.toLowerCase().includes(queryLower) ||
- e.content.toLowerCase().includes(queryLower)
+ e.content.toLowerCase().includes(queryLower),
)
.slice(0, topK);
}
diff --git a/panel/src/lib/api/knowledge-base.ts b/panel/src/lib/api/knowledge-base.ts
index 57fcb6f8..f0bab314 100644
--- a/panel/src/lib/api/knowledge-base.ts
+++ b/panel/src/lib/api/knowledge-base.ts
@@ -54,21 +54,24 @@ import { isMockMode } from "@/lib/mock-data";
const mockSearchResults: KBSearchResult[] = [
{
- content: "## Task Lifecycle\n\nTasks follow a strict state machine from PENDING through IN_PROGRESS to COMPLETED. Each transition requires specific conditions...",
+ content:
+ "## Task Lifecycle\n\nTasks follow a strict state machine from PENDING through IN_PROGRESS to COMPLETED. Each transition requires specific conditions...",
source: "docs/architecture/task-lifecycle.md",
score: 0.88,
index_type: KBIndexType.DOCUMENTATION,
metadata: { section: "Architecture" },
},
{
- content: "BE-DEV-1: I've completed the task validation logic. The acceptance criteria now require at least one item before a task can be created.",
+ content:
+ "BE-DEV-1: I've completed the task validation logic. The acceptance criteria now require at least one item before a task can be created.",
source: "channel:backend/session:abc123",
score: 0.75,
index_type: KBIndexType.CONVERSATIONS,
metadata: { agent: "be-dev-1", timestamp: "2024-01-15T10:30:00Z" },
},
{
- content: "Learned that proper error boundaries in the task form prevent cascading failures. Applied this pattern to all form components.",
+ content:
+ "Learned that proper error boundaries in the task form prevent cascading failures. Applied this pattern to all form components.",
source: "journal:be-dev-1/entry:xyz789",
score: 0.71,
index_type: KBIndexType.JOURNALS,
@@ -78,14 +81,54 @@ const mockSearchResults: KBSearchResult[] = [
const mockStats: KBStats = {
indexes: [
- { index_type: KBIndexType.DOCUMENTATION, document_count: 45, chunk_count: 320, last_updated: "2024-01-15T11:30:00Z" },
- { index_type: KBIndexType.CONVERSATIONS, document_count: 890, chunk_count: 4200, last_updated: "2024-01-15T12:15:00Z" },
- { index_type: KBIndexType.JOURNALS, document_count: 156, chunk_count: 780, last_updated: "2024-01-15T10:00:00Z" },
- { index_type: KBIndexType.ERRORS, document_count: 45, chunk_count: 180, last_updated: "2024-01-15T10:00:00Z" },
- { index_type: KBIndexType.STANDARDS, document_count: 12, chunk_count: 60, last_updated: "2024-01-15T10:00:00Z" },
- { index_type: KBIndexType.DECISIONS, document_count: 78, chunk_count: 390, last_updated: "2024-01-15T10:00:00Z" },
- { index_type: KBIndexType.REVIEWS, document_count: 234, chunk_count: 1170, last_updated: "2024-01-15T10:00:00Z" },
- { index_type: KBIndexType.LEARNINGS, document_count: 89, chunk_count: 445, last_updated: "2024-01-15T10:00:00Z" },
+ {
+ index_type: KBIndexType.DOCUMENTATION,
+ document_count: 45,
+ chunk_count: 320,
+ last_updated: "2024-01-15T11:30:00Z",
+ },
+ {
+ index_type: KBIndexType.CONVERSATIONS,
+ document_count: 890,
+ chunk_count: 4200,
+ last_updated: "2024-01-15T12:15:00Z",
+ },
+ {
+ index_type: KBIndexType.JOURNALS,
+ document_count: 156,
+ chunk_count: 780,
+ last_updated: "2024-01-15T10:00:00Z",
+ },
+ {
+ index_type: KBIndexType.ERRORS,
+ document_count: 45,
+ chunk_count: 180,
+ last_updated: "2024-01-15T10:00:00Z",
+ },
+ {
+ index_type: KBIndexType.STANDARDS,
+ document_count: 12,
+ chunk_count: 60,
+ last_updated: "2024-01-15T10:00:00Z",
+ },
+ {
+ index_type: KBIndexType.DECISIONS,
+ document_count: 78,
+ chunk_count: 390,
+ last_updated: "2024-01-15T10:00:00Z",
+ },
+ {
+ index_type: KBIndexType.REVIEWS,
+ document_count: 234,
+ chunk_count: 1170,
+ last_updated: "2024-01-15T10:00:00Z",
+ },
+ {
+ index_type: KBIndexType.LEARNINGS,
+ document_count: 89,
+ chunk_count: 445,
+ last_updated: "2024-01-15T10:00:00Z",
+ },
],
total_documents: 1549,
total_chunks: 6745,
@@ -94,18 +137,26 @@ const mockStats: KBStats = {
// Backend returns stats as dict, we need to transform to array
interface BackendIndexStats {
initialized: boolean;
- indexes: Record;
+ indexes: Record<
+ string,
+ { document_count: number; chunk_count: number; last_updated: string | null }
+ >;
}
function transformStatsResponse(backendStats: BackendIndexStats): KBStats {
- const indexes: KBIndexStats[] = Object.entries(backendStats.indexes || {}).map(([indexType, stats]) => ({
+ const indexes: KBIndexStats[] = Object.entries(
+ backendStats.indexes || {},
+ ).map(([indexType, stats]) => ({
index_type: indexType as KBIndexType,
document_count: stats.document_count ?? 0,
chunk_count: stats.chunk_count ?? 0,
last_updated: stats.last_updated ?? null,
}));
- const total_documents = indexes.reduce((sum, idx) => sum + idx.document_count, 0);
+ const total_documents = indexes.reduce(
+ (sum, idx) => sum + idx.document_count,
+ 0,
+ );
const total_chunks = indexes.reduce((sum, idx) => sum + idx.chunk_count, 0);
return { indexes, total_documents, total_chunks };
@@ -123,7 +174,9 @@ async function search(params: KBSearchRequest): Promise {
// Filter by index types if specified
let results = [...mockSearchResults];
if (params.index_types && params.index_types.length > 0) {
- results = results.filter((r) => params.index_types!.includes(r.index_type));
+ results = results.filter((r) =>
+ params.index_types!.includes(r.index_type),
+ );
}
// Filter by min score
if (params.min_score) {
@@ -140,7 +193,10 @@ async function search(params: KBSearchRequest): Promise {
};
}
- const response = await api.post("/optimal/kb/search", params);
+ const response = await api.post(
+ "/optimal/kb/search",
+ params,
+ );
return response.data;
}
@@ -175,7 +231,10 @@ async function ragQuery(params: RAGQueryRequest): Promise {
top_k: params.max_context_chunks ?? 5,
};
- const response = await api.post("/optimal/rag/query", backendParams);
+ const response = await api.post(
+ "/optimal/rag/query",
+ backendParams,
+ );
return response.data;
}
@@ -194,7 +253,10 @@ async function getContext(params: RAGQueryRequest): Promise {
top_k: params.max_context_chunks ?? 5,
};
- const response = await api.post("/optimal/rag/context", backendParams);
+ const response = await api.post(
+ "/optimal/rag/context",
+ backendParams,
+ );
return response.data.results;
}
@@ -242,8 +304,16 @@ async function getIndexStats(indexType: KBIndexType): Promise {
*/
async function listDocuments(
indexType: KBIndexType,
- params?: { limit?: number; offset?: number }
-): Promise<{ documents: Array<{ id: string; source: string; indexed_at: string; metadata?: Record }>; total: number }> {
+ params?: { limit?: number; offset?: number },
+): Promise<{
+ documents: Array<{
+ id: string;
+ source: string;
+ indexed_at: string;
+ metadata?: Record;
+ }>;
+ total: number;
+}> {
if (isMockMode()) {
// Generate mock document list
const docs = mockSearchResults
@@ -256,10 +326,16 @@ async function listDocuments(
return { documents: docs, total: docs.length };
}
- const response = await api.get<{ documents: Array<{ id: string; source: string; indexed_at: string; metadata?: Record }>; total: number; index_type: string }>(
- `/optimal/kb/${indexType}/documents`,
- { params }
- );
+ const response = await api.get<{
+ documents: Array<{
+ id: string;
+ source: string;
+ indexed_at: string;
+ metadata?: Record;
+ }>;
+ total: number;
+ index_type: string;
+ }>(`/optimal/kb/${indexType}/documents`, { params });
return { documents: response.data.documents, total: response.data.total };
}
@@ -291,22 +367,35 @@ async function getHealth(): Promise {
/**
* Delete/clear an index
*/
-async function deleteIndex(indexType: KBIndexType): Promise {
+async function deleteIndex(
+ indexType: KBIndexType,
+): Promise {
if (isMockMode()) {
return { status: "cleared", index_type: indexType };
}
- const response = await api.delete(`/optimal/kb/${indexType}`);
+ const response = await api.delete(
+ `/optimal/kb/${indexType}`,
+ );
return response.data;
}
/**
* Refresh an index with updated sources
*/
-async function refreshIndex(request: RefreshIndexRequest): Promise {
+async function refreshIndex(
+ request: RefreshIndexRequest,
+): Promise {
if (isMockMode()) {
- return { status: "refreshed", index_type: request.index_type, sources: request.sources };
+ return {
+ status: "refreshed",
+ index_type: request.index_type,
+ sources: request.sources,
+ };
}
- const response = await api.post("/optimal/kb/refresh", request);
+ const response = await api.post(
+ "/optimal/kb/refresh",
+ request,
+ );
return response.data;
}
@@ -354,12 +443,16 @@ async function reindexAll(request?: ReindexRequest): Promise {
docs_count: 50,
};
}
- const response = await api.post("/optimal/kb/reindex", null, {
- params: {
- force: request?.force ?? false,
- timeout_seconds: request?.timeout_seconds ?? 300,
+ const response = await api.post(
+ "/optimal/kb/reindex",
+ null,
+ {
+ params: {
+ force: request?.force ?? false,
+ timeout_seconds: request?.timeout_seconds ?? 300,
+ },
},
- });
+ );
return response.data;
}
@@ -393,7 +486,7 @@ async function checkStaleness(): Promise {
};
}
const response = await api.get(
- "/optimal/stats/staleness"
+ "/optimal/stats/staleness",
);
return response.data;
}
@@ -405,7 +498,9 @@ async function checkStaleness(): Promise {
/**
* Ask the mentor for help
*/
-async function askMentor(request: MentorAskRequest): Promise {
+async function askMentor(
+ request: MentorAskRequest,
+): Promise {
if (isMockMode()) {
return {
answer: `Here's what I found about "${request.question}":\n\nBased on our organizational knowledge, I recommend following the established patterns and consulting the relevant documentation.`,
@@ -414,7 +509,10 @@ async function askMentor(request: MentorAskRequest): Promise
suggested_followups: ["Can you elaborate?", "What are the alternatives?"],
};
}
- const response = await api.post("/optimal/mentor/ask", request);
+ const response = await api.post(
+ "/optimal/mentor/ask",
+ request,
+ );
return response.data;
}
@@ -425,22 +523,32 @@ async function askMentor(request: MentorAskRequest): Promise
/**
* Search for known error solutions
*/
-async function searchErrors(request: ErrorSearchRequest): Promise {
+async function searchErrors(
+ request: ErrorSearchRequest,
+): Promise