From 517bee7d285fdfc996f68099db5f8db79dbf057b Mon Sep 17 00:00:00 2001 From: Renn F Date: Sat, 27 Jun 2026 01:12:05 +0200 Subject: [PATCH] [chore] panel: prettier reformat across the codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply `pnpm format` (prettier 3.8.5, 80-col / double-quote / semi / trailing-comma-all) to the 223 pre-existing panel files that predated the prettier infra added in cb5365a4. Pure formatting — no semantic changes: multi-line arrays/objects collapsed where they fit, trailing newlines added (.prettierrc.json), import grouping unchanged. Verified: `pnpm format:check` clean, `pnpm lint` clean, `pnpm typecheck` clean, `pnpm test` 113/113 pass (7 files). --- panel/.prettierrc.json | 2 +- panel/lib/lifecycle.json | 384 ++-------- .../app/(dashboard)/agents/[agentId]/page.tsx | 61 +- panel/src/app/(dashboard)/agents/page.tsx | 14 +- panel/src/app/(dashboard)/business/page.tsx | 4 +- .../communications/[sessionId]/page.tsx | 53 +- .../app/(dashboard)/communications/page.tsx | 121 +-- .../(dashboard)/journals/[entryId]/page.tsx | 5 +- panel/src/app/(dashboard)/journals/page.tsx | 125 ++-- panel/src/app/(dashboard)/kanban/page.tsx | 27 +- panel/src/app/(dashboard)/metrics/page.tsx | 202 +++-- .../app/(dashboard)/notifications/page.tsx | 156 ++-- panel/src/app/(dashboard)/projects/page.tsx | 33 +- panel/src/app/(dashboard)/settings/page.tsx | 41 +- .../app/(dashboard)/tasks/[taskId]/page.tsx | 45 +- .../app/(dashboard)/work-sessions/page.tsx | 16 +- panel/src/components/agents/agent-card.tsx | 16 +- panel/src/components/agents/agent-grid.tsx | 48 +- .../src/components/agents/agent-selector.tsx | 34 +- .../components/agents/agent-state-badge.tsx | 6 +- .../components/agents/agent-status-cards.tsx | 11 +- .../components/agents/orchestrator-status.tsx | 13 +- .../components/agents/spawn-agent-dialog.tsx | 10 +- panel/src/components/agents/stream-viewer.tsx | 35 +- .../agents/waiting-agents-alert.tsx | 13 +- .../components/auditor/auditor-dashboard.tsx | 11 +- .../components/auditor/create-flag-dialog.tsx | 10 +- panel/src/components/auditor/flagged-item.tsx | 10 +- .../auditor/flagged-items-panel.tsx | 16 +- .../components/auditor/live-feeds-panel.tsx | 2 +- .../auditor/quality-metrics-panel.tsx | 16 +- .../src/components/auditor/reports-panel.tsx | 19 +- .../__tests__/company-scorecard-card.test.tsx | 6 +- .../business/company-scorecard-card.tsx | 15 +- panel/src/components/business/goals-tab.tsx | 34 +- panel/src/components/business/pitches-tab.tsx | 11 +- .../src/components/business/secretary-tab.tsx | 7 +- .../communications/channel-item.tsx | 2 +- .../communications/channel-sidebar.tsx | 5 +- .../communications/communications-view.tsx | 36 +- .../communications/message-item.tsx | 4 +- .../company-goals/company-goals-card.tsx | 20 +- .../dashboard/active-blockers-panel.tsx | 8 +- .../components/dashboard/activity-item.tsx | 14 +- .../dashboard/auditor-alerts-panel.tsx | 22 +- .../dashboard/ceo-approval-queue.tsx | 56 +- .../components/dashboard/command-center.tsx | 38 +- .../dashboard/key-metrics-panel.tsx | 13 +- .../dashboard/playbook-review-queue.tsx | 4 +- .../components/dashboard/pr-review-queue.tsx | 24 +- .../dashboard/quick-actions-bar.tsx | 9 +- .../dashboard/recent-activity-feed.tsx | 5 +- .../components/dashboard/team-health-card.tsx | 12 +- .../dashboard/usage-overview-panel.tsx | 10 +- .../src/components/git/git-actions-panel.tsx | 42 +- panel/src/components/git/git-branch-panel.tsx | 16 +- panel/src/components/git/git-browser.tsx | 86 ++- panel/src/components/git/git-diff-viewer.tsx | 13 +- panel/src/components/git/git-log-panel.tsx | 4 +- panel/src/components/git/git-status-panel.tsx | 15 +- panel/src/components/journals/agent-item.tsx | 7 +- panel/src/components/journals/agent-list.tsx | 5 +- .../src/components/journals/entry-filter.tsx | 5 +- .../components/journals/growth-summary.tsx | 15 +- .../src/components/journals/journal-view.tsx | 33 +- .../components/kanban/core/kanban-board.tsx | 32 +- .../components/kanban/core/kanban-card.tsx | 45 +- .../components/kanban/core/kanban-column.tsx | 15 +- .../components/kanban/views/dev-kanban.tsx | 56 +- .../src/components/kanban/views/pm-kanban.tsx | 98 ++- .../kanban/views/pr-review-kanban.tsx | 21 +- .../src/components/kanban/views/qa-kanban.tsx | 28 +- panel/src/components/knowledge-base/index.ts | 6 +- .../knowledge-base/kb-category-nav.tsx | 35 +- .../knowledge-base/kb-category-view.tsx | 19 +- .../components/knowledge-base/kb-filters.tsx | 59 +- .../knowledge-base/kb-index-type-badge.tsx | 31 +- .../knowledge-base/kb-result-card.tsx | 11 +- .../knowledge-base/kb-result-list.tsx | 16 +- .../knowledge-base/kb-search-bar.tsx | 5 +- .../knowledge-base/kb-stats-card.tsx | 33 +- .../knowledge-base/knowledge-base-browser.tsx | 148 ++-- .../knowledge-base/mentor-answer-display.tsx | 128 ++-- .../components/knowledge-base/mentor-chat.tsx | 42 +- .../knowledge-base/mentor-query-input.tsx | 14 +- .../knowledge-base/rag-answer-display.tsx | 20 +- .../knowledge-base/rag-citation-card.tsx | 16 +- .../components/layout/connection-status.tsx | 10 +- panel/src/components/layout/header.tsx | 4 +- panel/src/components/layout/sidebar.tsx | 8 +- .../components/metrics/agent-usage-chart.tsx | 6 +- panel/src/components/metrics/delivery-tab.tsx | 31 +- .../src/components/metrics/sessions-table.tsx | 45 +- .../components/metrics/team-usage-chart.tsx | 6 +- .../metrics/usage-time-series-chart.tsx | 34 +- .../notifications/notification-bell.tsx | 45 +- .../products/create-product-dialog.tsx | 26 +- .../products/edit-product-dialog.tsx | 43 +- .../src/components/products/product-table.tsx | 4 +- .../projects/create-project-dialog.tsx | 74 +- .../projects/edit-project-dialog.tsx | 4 +- .../components/projects/project-selector.tsx | 3 +- .../src/components/projects/project-table.tsx | 15 +- .../src/components/prompter/chat-messages.tsx | 12 +- .../prompter/draft-proposal-card.tsx | 22 +- .../src/components/prompter/success-card.tsx | 18 +- panel/src/components/providers.tsx | 2 +- .../rate-limit/rate-limit-banner.tsx | 8 +- .../components/settings/ai-routing-card.tsx | 46 +- .../settings/feature-flags-card.tsx | 20 +- .../settings/self-hosted-section.tsx | 8 +- .../settings/transcript-retention-card.tsx | 9 +- .../tasks/acceptance-criteria-editor.tsx | 15 +- .../tasks/approve-and-start-button.tsx | 13 +- .../components/tasks/create-task-dialog.tsx | 69 +- .../components/tasks/dependency-selector.tsx | 30 +- .../components/tasks/docs-status-badge.tsx | 4 +- .../src/components/tasks/edit-task-dialog.tsx | 67 +- .../src/components/tasks/markdown-editor.tsx | 9 +- panel/src/components/tasks/task-actions.tsx | 64 +- .../tasks/task-detail/acceptance-criteria.tsx | 11 +- .../tasks/task-detail/progress-timeline.tsx | 11 +- .../tasks/task-detail/subtasks-list.tsx | 50 +- .../tasks/task-detail/tab-commits.tsx | 74 +- .../tasks/task-detail/tab-dependencies.tsx | 48 +- .../tasks/task-detail/tab-notes.tsx | 3 +- .../tasks/task-detail/tab-overview.tsx | 16 +- .../components/tasks/task-detail/tab-plan.tsx | 294 ++++++-- .../tasks/task-detail/tab-progress.tsx | 84 ++- .../tasks/task-detail/tab-sessions.tsx | 30 +- .../tasks/task-detail/task-action-dialogs.tsx | 18 +- .../tasks/task-detail/task-description.tsx | 18 +- .../tasks/task-detail/work-session-card.tsx | 30 +- panel/src/components/tasks/task-filters.tsx | 55 +- panel/src/components/tasks/task-selector.tsx | 35 +- .../src/components/tasks/task-type-badge.tsx | 17 +- panel/src/components/ui/alert-dialog.tsx | 40 +- panel/src/components/ui/avatar.tsx | 20 +- panel/src/components/ui/badge.tsx | 18 +- panel/src/components/ui/button.tsx | 20 +- panel/src/components/ui/card.tsx | 26 +- panel/src/components/ui/checkbox.tsx | 16 +- panel/src/components/ui/collapsible.tsx | 12 +- panel/src/components/ui/copy-button.tsx | 2 +- panel/src/components/ui/dialog.tsx | 40 +- panel/src/components/ui/dropdown-menu.tsx | 66 +- panel/src/components/ui/form.tsx | 81 +- panel/src/components/ui/input.tsx | 10 +- panel/src/components/ui/label.tsx | 14 +- panel/src/components/ui/markdown.tsx | 16 +- panel/src/components/ui/offline-state.tsx | 10 +- panel/src/components/ui/popover.tsx | 20 +- panel/src/components/ui/progress.tsx | 14 +- .../components/ui/required-notes-dialog.tsx | 4 +- panel/src/components/ui/scroll-area.tsx | 16 +- panel/src/components/ui/select.tsx | 46 +- panel/src/components/ui/separator.tsx | 14 +- panel/src/components/ui/sheet.tsx | 38 +- panel/src/components/ui/skeleton.tsx | 6 +- panel/src/components/ui/sonner.tsx | 16 +- panel/src/components/ui/switch.tsx | 16 +- panel/src/components/ui/table.tsx | 32 +- panel/src/components/ui/tabs.tsx | 22 +- panel/src/components/ui/textarea.tsx | 10 +- panel/src/components/ui/tooltip.tsx | 22 +- .../work-sessions/work-session-table.tsx | 13 +- panel/src/hooks/use-agents.ts | 258 ++++++- panel/src/hooks/use-channels.ts | 8 +- panel/src/hooks/use-dashboard.ts | 16 +- panel/src/hooks/use-git.ts | 51 +- panel/src/hooks/use-journals.ts | 2 +- panel/src/hooks/use-knowledge-base.ts | 30 +- panel/src/hooks/use-notifications.ts | 14 +- panel/src/hooks/use-products.ts | 15 +- panel/src/hooks/use-projects.ts | 21 +- panel/src/hooks/use-rate-limit-sync.ts | 12 +- panel/src/hooks/use-rate-limit-websocket.ts | 11 +- panel/src/hooks/use-scroll-restoration.ts | 9 +- panel/src/hooks/use-secretary.ts | 12 +- panel/src/hooks/use-tasks.ts | 81 +- panel/src/hooks/use-usage.ts | 18 +- panel/src/hooks/use-websocket.ts | 81 +- panel/src/hooks/use-work-sessions.ts | 60 +- .../lib/__tests__/agent-definitions.test.ts | 4 +- panel/src/lib/__tests__/client.test.ts | 31 +- panel/src/lib/agent-definitions.ts | 23 +- panel/src/lib/agent-utils.ts | 16 +- panel/src/lib/api/a2a.ts | 26 +- panel/src/lib/api/channels.ts | 22 +- panel/src/lib/api/client.ts | 56 +- panel/src/lib/api/cockpit.ts | 2 +- panel/src/lib/api/dashboard.ts | 91 ++- panel/src/lib/api/git.ts | 57 +- panel/src/lib/api/journals.ts | 49 +- panel/src/lib/api/knowledge-base.ts | 284 +++++-- panel/src/lib/api/messages.ts | 36 +- panel/src/lib/api/notifications.ts | 53 +- panel/src/lib/api/observability.ts | 9 +- panel/src/lib/api/orchestrator.ts | 48 +- panel/src/lib/api/playbooks.ts | 4 +- panel/src/lib/api/products.ts | 7 +- panel/src/lib/api/projects.ts | 65 +- panel/src/lib/api/providers.ts | 5 +- panel/src/lib/api/rate-limits.ts | 4 +- panel/src/lib/api/release.ts | 9 +- panel/src/lib/api/secretary.ts | 25 +- panel/src/lib/api/sessions.ts | 57 +- panel/src/lib/api/settings.ts | 17 +- panel/src/lib/api/stream.ts | 31 +- panel/src/lib/api/tasks.ts | 248 +++++-- panel/src/lib/api/usage.ts | 68 +- panel/src/lib/api/work-sessions.ts | 87 ++- panel/src/lib/constants.ts | 4 +- panel/src/lib/mock-data.ts | 691 +++++++++++++++--- panel/src/lib/stores/ui-store.ts | 12 +- panel/src/lib/utils.ts | 6 +- panel/src/lib/websocket/connection.ts | 25 +- .../__tests__/notifications-store.test.ts | 32 +- .../store/__tests__/rate-limit-store.test.ts | 10 +- panel/src/store/notifications-store.ts | 11 +- panel/src/store/rate-limit-store.ts | 2 +- panel/src/store/ui-store.ts | 13 +- panel/vitest.config.ts | 6 +- 223 files changed, 5721 insertions(+), 2505 deletions(-) diff --git a/panel/.prettierrc.json b/panel/.prettierrc.json index 791565d5..d7173d71 100644 --- a/panel/.prettierrc.json +++ b/panel/.prettierrc.json @@ -7,4 +7,4 @@ "arrowParens": "always", "bracketSpacing": true, "endOfLine": "lf" -} \ No newline at end of file +} diff --git a/panel/lib/lifecycle.json b/panel/lib/lifecycle.json index d8987d3e..dcc15f93 100644 --- a/panel/lib/lifecycle.json +++ b/panel/lib/lifecycle.json @@ -1,38 +1,19 @@ { "claim_rules": { "auditor": [], - "cell_pm": [ - "needs_revision", - "pending" - ], + "cell_pm": ["needs_revision", "pending"], "ceo": [], - "developer": [ - "needs_revision", - "pending" - ], - "documenter": [ - "awaiting_documentation", - "pending" - ], + "developer": ["needs_revision", "pending"], + "documenter": ["awaiting_documentation", "pending"], "head_marketing": [], - "main_pm": [ - "needs_revision", - "pending" - ], - "pr_reviewer": [ - "awaiting_pr_review", - "pending" - ], + "main_pm": ["needs_revision", "pending"], + "pr_reviewer": ["awaiting_pr_review", "pending"], "product_owner": [], - "qa": [ - "awaiting_qa" - ] + "qa": ["awaiting_qa"] }, "intents": [ { - "allowed_roles": [ - "documenter" - ], + "allowed_roles": ["documenter"], "composes": [], "description": "Claim awaiting_documentation. Returns evidence inline.", "name": "claim_doc_task", @@ -40,9 +21,7 @@ "side_effects": [] }, { - "allowed_roles": [ - "pr_reviewer" - ], + "allowed_roles": ["pr_reviewer"], "composes": [], "description": "Claim an assembled-PR review task (awaiting_pr_review) WITHOUT transitioning it \u2014 mirrors QA's claim_review. The assembled diff and the parent task's acceptance criteria are returned inline.", "name": "claim_gate_review", @@ -50,22 +29,15 @@ "side_effects": [] }, { - "allowed_roles": [ - "pr_reviewer" - ], - "composes": [ - "claim", - "start" - ], + "allowed_roles": ["pr_reviewer"], + "composes": ["claim", "start"], "description": "Claim an inbound external-PR review task and start work. pending -> claimed -> in_progress.", "name": "claim_pr_review", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "qa" - ], + "allowed_roles": ["qa"], "composes": [], "description": "Claim a task in awaiting_qa for review. Returns evidence inline.", "name": "claim_review", @@ -73,52 +45,31 @@ "side_effects": [] }, { - "allowed_roles": [ - "cell_pm", - "main_pm" - ], - "composes": [ - "complete" - ], + "allowed_roles": ["cell_pm", "main_pm"], + "composes": ["complete"], "description": "Cell PM merges the PR (leaf into the cell branch, or the gated cell\u2192root PR into the root branch) + transitions to completed; Main PM escalates the root to the CEO (who merges root\u2192master).", "name": "complete", "pre_side_effects": [], - "side_effects": [ - "pr_merge" - ] + "side_effects": ["pr_merge"] }, { - "allowed_roles": [ - "cell_pm", - "main_pm" - ], - "composes": [ - "create_subtask" - ], + "allowed_roles": ["cell_pm", "main_pm"], + "composes": ["create_subtask"], "description": "Create a subtask under the current task. Validates the delegation chain (main_pm->cell_pm; cell_pm->its team's devs) and the assignee-vs-task_type rule (Cell PMs get planning-typed tasks; devs get code/research, UX devs also design). documentation is NOT delegatable \u2014 the lifecycle auto-creates the doc phase after the code subtask passes QA.", "name": "delegate", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "head_marketing", - "main_pm", - "product_owner" - ], - "composes": [ - "escalate_to_ceo" - ], + "allowed_roles": ["head_marketing", "main_pm", "product_owner"], + "composes": ["escalate_to_ceo"], "description": "Escalate to CEO with reason. Transitions to awaiting_ceo_approval.", "name": "escalate_to_ceo", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "cell_pm", - "main_pm" - ], + "allowed_roles": ["cell_pm", "main_pm"], "composes": [], "description": "Escalate to your role's escalation_target.", "name": "escalate_up", @@ -126,12 +77,8 @@ "side_effects": [] }, { - "allowed_roles": [ - "qa" - ], - "composes": [ - "qa_fail" - ], + "allowed_roles": ["qa"], + "composes": ["qa_fail"], "description": "Fail QA with concrete issues. Transitions to needs_revision.", "name": "fail_review", "pre_side_effects": [], @@ -153,27 +100,16 @@ "side_effects": [] }, { - "allowed_roles": [ - "developer", - "documenter", - "qa" - ], - "composes": [ - "block" - ], + "allowed_roles": ["developer", "documenter", "qa"], + "composes": ["block"], "description": "Escalate to PM. Logs a struggle journal entry.", "name": "i_am_blocked", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "developer" - ], - "composes": [ - "submit_verification", - "submit_qa" - ], + "allowed_roles": ["developer"], + "composes": ["submit_verification", "submit_qa"], "description": "Submit work for QA. Auto-runs in_progress->verifying then verifying->awaiting_qa. Strict - PR must be open (call open_pr first) and >=1 commit.", "name": "i_am_done", "pre_side_effects": [], @@ -200,111 +136,71 @@ "side_effects": [] }, { - "allowed_roles": [ - "documenter" - ], - "composes": [ - "docs_complete" - ], + "allowed_roles": ["documenter"], + "composes": ["docs_complete"], "description": "Signal docs complete. Transitions to awaiting_pm_review.", "name": "i_documented", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "cell_pm", - "main_pm" - ], - "composes": [ - "claim", - "set_plan", - "start" - ], + "allowed_roles": ["cell_pm", "main_pm"], + "composes": ["claim", "set_plan", "start"], "description": "PM mirror of i_will_work_on for parent tasks. Claim, plan, transition to in_progress; from there delegate subtasks.", "name": "i_will_plan", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "developer" - ], - "composes": [ - "claim", - "set_plan", - "start" - ], + "allowed_roles": ["developer"], + "composes": ["claim", "set_plan", "start"], "description": "Claim a task, set the plan, and transition to in_progress. Atomic - preconditions checked before any state mutation.", "name": "i_will_work_on", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "developer" - ], + "allowed_roles": ["developer"], "composes": [], "description": "Push the branch and open a PR. Atomic - preconditions (assignee, >=1 commit, no prior PR) checked BEFORE any git operation. After success, call i_am_done.", "name": "open_pr", "pre_side_effects": [], - "side_effects": [ - "push_branch", - "create_pr" - ] + "side_effects": ["push_branch", "create_pr"] }, { - "allowed_roles": [ - "qa" - ], - "composes": [ - "qa_pass" - ], + "allowed_roles": ["qa"], + "composes": ["qa_pass"], "description": "Pass QA. Transitions awaiting_qa -> awaiting_documentation.", "name": "pass_review", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "pr_reviewer" - ], - "composes": [ - "pr_review_done" - ], + "allowed_roles": ["pr_reviewer"], + "composes": ["pr_review_done"], "description": "Post one complete change-request to the external PR and finish the review task. in_progress -> completed.", "name": "post_pr_review", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "pr_reviewer" - ], - "composes": [ - "pr_fail" - ], + "allowed_roles": ["pr_reviewer"], + "composes": ["pr_fail"], "description": "Fail the assembled-PR review with concrete issues. Transitions awaiting_pr_review -> needs_revision, routed back like a QA fail.", "name": "pr_fail", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "pr_reviewer" - ], - "composes": [ - "pr_pass" - ], + "allowed_roles": ["pr_reviewer"], + "composes": ["pr_pass"], "description": "Pass the assembled-PR review. Transitions awaiting_pr_review -> awaiting_pm_review so the PM can merge.", "name": "pr_pass", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "cell_pm" - ], + "allowed_roles": ["cell_pm"], "composes": [], "description": "Hand a claimed/in_progress task to another developer in your own cell. The branch is keyed to the task (not the agent), so it is preserved \u2014 the new developer continues the work-in-progress. No status change.", "name": "reassign", @@ -312,47 +208,27 @@ "side_effects": [] }, { - "allowed_roles": [ - "cell_pm", - "developer", - "documenter", - "main_pm", - "qa" - ], - "composes": [ - "resume" - ], + "allowed_roles": ["cell_pm", "developer", "documenter", "main_pm", "qa"], + "composes": ["resume"], "description": "Resume a paused task you own. paused -> in_progress.", "name": "resume", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "main_pm" - ], - "composes": [ - "submit_for_review" - ], + "allowed_roles": ["main_pm"], + "composes": ["submit_for_review"], "description": "Main PM opens the root\u2192master PR and moves the root task to awaiting_pr_review for the main reviewer (the root analogue of the cell PM's submit_up). After pr_pass, call complete to escalate to the CEO. Only for code roots; branchless coordination roots skip the gate and complete directly.", "name": "submit_root", - "pre_side_effects": [ - "create_root_pr" - ], + "pre_side_effects": ["create_root_pr"], "side_effects": [] }, { - "allowed_roles": [ - "cell_pm" - ], - "composes": [ - "submit_for_review" - ], + "allowed_roles": ["cell_pm"], + "composes": ["submit_for_review"], "description": "Cell PM opens the cell\u2192root PR and moves the cell task into the PR-review gate (awaiting_pr_review). The cell reviewer reviews the assembled diff; after pr_pass the same Cell PM completes it.", "name": "submit_up", - "pre_side_effects": [ - "create_pr" - ], + "pre_side_effects": ["create_pr"], "side_effects": [] }, { @@ -370,9 +246,7 @@ "side_effects": [] }, { - "allowed_roles": [ - "main_pm" - ], + "allowed_roles": ["main_pm"], "composes": [], "description": "List actionable tasks across all teams (Main PM only).", "name": "triage_all", @@ -380,26 +254,15 @@ "side_effects": [] }, { - "allowed_roles": [ - "cell_pm", - "main_pm" - ], - "composes": [ - "unblock" - ], + "allowed_roles": ["cell_pm", "main_pm"], + "composes": ["unblock"], "description": "PM unblocks a blocked task; restores pre-block state.", "name": "unblock", "pre_side_effects": [], "side_effects": [] }, { - "allowed_roles": [ - "cell_pm", - "developer", - "documenter", - "main_pm", - "qa" - ], + "allowed_roles": ["cell_pm", "developer", "documenter", "main_pm", "qa"], "composes": [], "description": "Voluntarily release a claim back to pending. The work-in-progress branch is preserved.", "name": "unclaim", @@ -410,160 +273,109 @@ "transitions": [ { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "awaiting_ceo_approval", "target": "cancelled" }, { "action": "ceo_approve", - "roles": [ - "ceo" - ], + "roles": ["ceo"], "source": "awaiting_ceo_approval", "target": "completed" }, { "action": "ceo_reject", - "roles": [ - "ceo" - ], + "roles": ["ceo"], "source": "awaiting_ceo_approval", "target": "needs_revision" }, { "action": "docs_complete", - "roles": [ - "documenter" - ], + "roles": ["documenter"], "source": "awaiting_documentation", "target": "awaiting_pm_review" }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "awaiting_documentation", "target": "cancelled" }, { "action": "claim", - "roles": [ - "documenter" - ], + "roles": ["documenter"], "source": "awaiting_documentation", "target": "claimed" }, { "action": "escalate_to_ceo", - "roles": [ - "head_marketing", - "main_pm", - "product_owner" - ], + "roles": ["head_marketing", "main_pm", "product_owner"], "source": "awaiting_pm_review", "target": "awaiting_ceo_approval" }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "awaiting_pm_review", "target": "cancelled" }, { "action": "complete", - "roles": [ - "cell_pm", - "main_pm" - ], + "roles": ["cell_pm", "main_pm"], "source": "awaiting_pm_review", "target": "completed" }, { "action": "pr_pass", - "roles": [ - "pr_reviewer" - ], + "roles": ["pr_reviewer"], "source": "awaiting_pr_review", "target": "awaiting_pm_review" }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "awaiting_pr_review", "target": "cancelled" }, { "action": "claim", - "roles": [ - "pr_reviewer" - ], + "roles": ["pr_reviewer"], "source": "awaiting_pr_review", "target": "claimed" }, { "action": "pr_fail", - "roles": [ - "pr_reviewer" - ], + "roles": ["pr_reviewer"], "source": "awaiting_pr_review", "target": "needs_revision" }, { "action": "qa_pass", - "roles": [ - "qa" - ], + "roles": ["qa"], "source": "awaiting_qa", "target": "awaiting_documentation" }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "awaiting_qa", "target": "cancelled" }, { "action": "claim", - "roles": [ - "qa" - ], + "roles": ["qa"], "source": "awaiting_qa", "target": "claimed" }, { "action": "qa_fail", - "roles": [ - "qa" - ], + "roles": ["qa"], "source": "awaiting_qa", "target": "needs_revision" }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "backlog", "target": "cancelled" }, @@ -575,21 +387,13 @@ }, { "action": "escalate_to_ceo", - "roles": [ - "head_marketing", - "main_pm", - "product_owner" - ], + "roles": ["head_marketing", "main_pm", "product_owner"], "source": "blocked", "target": "awaiting_ceo_approval" }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "blocked", "target": "cancelled" }, @@ -607,11 +411,7 @@ }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "claimed", "target": "cancelled" }, @@ -641,19 +441,13 @@ }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "in_progress", "target": "cancelled" }, { "action": "pr_review_done", - "roles": [ - "pr_reviewer" - ], + "roles": ["pr_reviewer"], "source": "in_progress", "target": "completed" }, @@ -671,11 +465,7 @@ }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "needs_revision", "target": "cancelled" }, @@ -687,11 +477,7 @@ }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "paused", "target": "cancelled" }, @@ -703,11 +489,7 @@ }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "pending", "target": "cancelled" }, @@ -725,11 +507,7 @@ }, { "action": "cancel", - "roles": [ - "cell_pm", - "ceo", - "main_pm" - ], + "roles": ["cell_pm", "ceo", "main_pm"], "source": "verifying", "target": "cancelled" } diff --git a/panel/src/app/(dashboard)/agents/[agentId]/page.tsx b/panel/src/app/(dashboard)/agents/[agentId]/page.tsx index 908521ed..e4257025 100644 --- a/panel/src/app/(dashboard)/agents/[agentId]/page.tsx +++ b/panel/src/app/(dashboard)/agents/[agentId]/page.tsx @@ -2,12 +2,32 @@ import { useParams, useRouter } from "next/navigation"; import { formatDistanceToNow } from "date-fns"; -import { useAgentStatus, useStopAgent, useSpawnAgent, useAgentDefinition } from "@/hooks/use-agents"; +import { + useAgentStatus, + useStopAgent, + useSpawnAgent, + useAgentDefinition, +} from "@/hooks/use-agents"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; -import { ArrowLeft, Play, Square, AlertTriangle, Clock, RefreshCw, User, Users } from "lucide-react"; +import { + ArrowLeft, + Play, + Square, + AlertTriangle, + Clock, + RefreshCw, + User, + Users, +} from "lucide-react"; import { toast } from "sonner"; import { AgentStatusCards, @@ -50,13 +70,19 @@ export default function AgentDetailPage() { // Get display values from definition or fallback const displayName = definition?.name || agentId; - const roleLabel = definition?.role ? ROLE_LABELS[definition.role] || definition.role : null; - const teamLabel = definition?.team ? TEAM_LABELS[definition.team] || definition.team : null; + const roleLabel = definition?.role + ? ROLE_LABELS[definition.role] || definition.role + : null; + const teamLabel = definition?.team + ? TEAM_LABELS[definition.team] || definition.team + : null; const handleStop = async (graceful: boolean) => { try { await stopAgent.mutateAsync({ agentId, graceful }); - toast.success(graceful ? "Agent stopping gracefully" : "Agent force stopped"); + toast.success( + graceful ? "Agent stopping gracefully" : "Agent force stopped", + ); } catch { toast.error("Failed to stop agent"); } @@ -97,7 +123,9 @@ export default function AgentDetailPage() { ); } - const isActive = agent && ["running", "ready", "starting", "waiting_long"].includes(agent.state); + const isActive = + agent && + ["running", "ready", "starting", "waiting_long"].includes(agent.state); const isWaiting = agent?.state === "waiting_long"; return ( @@ -111,7 +139,9 @@ export default function AgentDetailPage() {
-

{displayName}

+

+ {displayName} +

{roleLabel && ( @@ -199,7 +229,9 @@ export default function AgentDetailPage() { -

{agent.error_count} error(s)

+

+ {agent.error_count} error(s) +

)} @@ -213,20 +245,23 @@ export default function AgentDetailPage() { Agent Waiting for Input - This agent is blocked and waiting for human input or external resolution. + This agent is blocked and waiting for human input or external + resolution.

- Use the "Resolve Wait" button above to provide the information or decision - the agent needs to continue execution. + Use the "Resolve Wait" button above to provide the + information or decision the agent needs to continue execution.

)} {/* Agent Stream Viewer */} - {isActive && } + {isActive && ( + + )} ) : null}
diff --git a/panel/src/app/(dashboard)/agents/page.tsx b/panel/src/app/(dashboard)/agents/page.tsx index 40716ac1..7a708d03 100644 --- a/panel/src/app/(dashboard)/agents/page.tsx +++ b/panel/src/app/(dashboard)/agents/page.tsx @@ -32,11 +32,11 @@ export default function AgentsPage() { const { data: usageRows } = useAgentUsage(); // Check if it's a connection error (backend not running) - const isOffline = error && ( - error.message?.includes("Network Error") || - error.message?.includes("ECONNREFUSED") || - (error as { code?: string })?.code === "ERR_NETWORK" - ); + const isOffline = + error && + (error.message?.includes("Network Error") || + error.message?.includes("ECONNREFUSED") || + (error as { code?: string })?.code === "ERR_NETWORK"); // Convert agents array to a record keyed by agent_id for easy lookup const agentStatuses = useMemo(() => { @@ -86,7 +86,9 @@ export default function AgentsPage() { {/* Waiting Agents Alert */} - {waitingAgents && } + {waitingAgents && ( + + )} )} diff --git a/panel/src/app/(dashboard)/business/page.tsx b/panel/src/app/(dashboard)/business/page.tsx index b559d604..a88cb54b 100644 --- a/panel/src/app/(dashboard)/business/page.tsx +++ b/panel/src/app/(dashboard)/business/page.tsx @@ -42,8 +42,8 @@ function BusinessPageContent() {

Business

- Company goals, your chief-of-staff Secretary, and Board pitches — all in - one place. + Company goals, your chief-of-staff Secretary, and Board pitches — all + in one place.

diff --git a/panel/src/app/(dashboard)/communications/[sessionId]/page.tsx b/panel/src/app/(dashboard)/communications/[sessionId]/page.tsx index 8bf4520a..4346e0f6 100644 --- a/panel/src/app/(dashboard)/communications/[sessionId]/page.tsx +++ b/panel/src/app/(dashboard)/communications/[sessionId]/page.tsx @@ -37,9 +37,10 @@ function SessionDetailContent() { const groupId = searchParams.get("group"); // Build back URL preserving context - const backUrl = channelId && groupId - ? `/communications?channel=${channelId}&group=${groupId}` - : "/communications"; + const backUrl = + channelId && groupId + ? `/communications?channel=${channelId}&group=${groupId}` + : "/communications"; const queryClient = useQueryClient(); const scrollRef = useRef(null); @@ -52,12 +53,20 @@ function SessionDetailContent() { // is what stops the panel from accumulating a 404 storm across every dead // session it has opened. `refetchMessages` (the manual Refresh button) stays // available for live sessions. - const { data: session, isLoading: loadingSession, refetch: refetchSession } = useSession(sessionId); - const { data: messagesData, isLoading: loadingMessages, refetch: refetchMessages } = useSessionMessages(sessionId); + const { + data: session, + isLoading: loadingSession, + refetch: refetchSession, + } = useSession(sessionId); + const { + data: messagesData, + isLoading: loadingMessages, + refetch: refetchMessages, + } = useSessionMessages(sessionId); // Sort messages chronologically (oldest first for chat UI) const messages = [...(messagesData?.items || [])].sort( - (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), ); // Track if we've done the initial scroll @@ -73,11 +82,19 @@ function SessionDetailContent() { // Send message mutation const sendMessage = useMutation({ - mutationFn: async ({ content, type }: { content: string; type: string }) => { + mutationFn: async ({ + content, + type, + }: { + content: string; + type: string; + }) => { return messagesApi.send(sessionId, content, type); }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["messages", "list", sessionId] }); + queryClient.invalidateQueries({ + queryKey: ["messages", "list", sessionId], + }); toast.success("Message sent"); }, onError: (error: Error) => { @@ -95,7 +112,8 @@ function SessionDetailContent() { }; // Get primary task - const primaryTask = session?.task_links?.find(t => t.is_primary) || session?.task_links?.[0]; + const primaryTask = + session?.task_links?.find((t) => t.is_primary) || session?.task_links?.[0]; if (loadingSession) { return ( @@ -122,7 +140,8 @@ function SessionDetailContent() {

Session Not Found

- The session you're looking for doesn't exist or has been deleted. + The session you're looking for doesn't exist or has + been deleted.

@@ -162,7 +181,9 @@ function SessionDetailContent() {
- + {session.status}
@@ -175,7 +196,8 @@ function SessionDetailContent() {
{session.closed_at && (
- Closed: {format(new Date(session.closed_at), "MMM d, yyyy h:mm a")} + Closed:{" "} + {format(new Date(session.closed_at), "MMM d, yyyy h:mm a")}
)} @@ -190,7 +212,8 @@ function SessionDetailContent() { href={`/tasks/${primaryTask.task_id}`} className="text-sm text-primary hover:underline" > - {primaryTask.task_title || `Task ${primaryTask.task_id.slice(0, 8)}`} + {primaryTask.task_title || + `Task ${primaryTask.task_id.slice(0, 8)}`} )} {session.task_links.length > 1 && ( @@ -232,7 +255,9 @@ function SessionDetailContent() {

No messages in this session

-

Use the composer below to start the conversation

+

+ Use the composer below to start the conversation +

) : (
diff --git a/panel/src/app/(dashboard)/communications/page.tsx b/panel/src/app/(dashboard)/communications/page.tsx index 3bc05f34..02bb741c 100644 --- a/panel/src/app/(dashboard)/communications/page.tsx +++ b/panel/src/app/(dashboard)/communications/page.tsx @@ -37,12 +37,17 @@ interface ChannelListProps { isLoading: boolean; } -function ChannelList({ channels, selectedId, onSelect, isLoading }: ChannelListProps) { +function ChannelList({ + channels, + selectedId, + onSelect, + isLoading, +}: ChannelListProps) { const cellChannels = channels.filter((c) => c.type === "cell"); const crossCellChannels = channels.filter((c) => c.type === "cross_cell"); const managementChannels = channels.filter((c) => c.type === "management"); const otherChannels = channels.filter( - (c) => !["cell", "cross_cell", "management"].includes(c.type) + (c) => !["cell", "cross_cell", "management"].includes(c.type), ); const renderGroup = (title: string, items: Channel[]) => { @@ -208,9 +213,10 @@ function SessionList({ channelId, groupId }: SessionListProps) {
{session.task_links?.length > 0 ? ( <> - {session.task_links.find(l => l.is_primary)?.task_title || - session.task_links[0]?.task_title || - `Task ${session.task_links[0]?.task_id.slice(0, 8)}`} + {session.task_links.find((l) => l.is_primary) + ?.task_title || + session.task_links[0]?.task_title || + `Task ${session.task_links[0]?.task_id.slice(0, 8)}`} ) : ( `Session ${session.id.slice(0, 8)}` @@ -222,7 +228,9 @@ function SessionList({ channelId, groupId }: SessionListProps) {
{session.status} @@ -243,7 +251,13 @@ function SessionList({ channelId, groupId }: SessionListProps) { // Empty State Components // ============================================================================= -function EmptyPanel({ icon: Icon, message }: { icon: typeof MessageSquare; message: string }) { +function EmptyPanel({ + icon: Icon, + message, +}: { + icon: typeof MessageSquare; + message: string; +}) { return (
@@ -267,10 +281,10 @@ function CommunicationsPageContent() { const { data: channels, isLoading, error, refetch } = useChannels(); - const isOffline = error && ( - error.message?.includes("Network Error") || - (error as { code?: string })?.code === "ERR_NETWORK" - ); + const isOffline = + error && + (error.message?.includes("Network Error") || + (error as { code?: string })?.code === "ERR_NETWORK"); const updateParams = useCallback( (updates: Record) => { @@ -285,18 +299,24 @@ function CommunicationsPageContent() { const query = params.toString(); router.push(query ? `/communications?${query}` : "/communications"); }, - [router, searchParams] + [router, searchParams], ); - const handleSelectChannel = useCallback((id: string) => { - updateParams({ channel: id, group: null }); - }, [updateParams]); + const handleSelectChannel = useCallback( + (id: string) => { + updateParams({ channel: id, group: null }); + }, + [updateParams], + ); - const handleSelectGroup = useCallback((id: string) => { - updateParams({ group: id }); - }, [updateParams]); + const handleSelectGroup = useCallback( + (id: string) => { + updateParams({ group: id }); + }, + [updateParams], + ); - const selectedChannel = channels?.find(c => c.id === channelId); + const selectedChannel = channels?.find((c) => c.id === channelId); return (
@@ -347,7 +367,10 @@ function CommunicationsPageContent() { Groups {selectedChannel && ( - + {selectedChannel.name} )} @@ -379,7 +402,11 @@ function CommunicationsPageContent() { ) : ( )}
@@ -394,33 +421,35 @@ function CommunicationsPageContent() { // Wrap in Suspense for useSearchParams export default function CommunicationsPage() { return ( - -
-
- - + +
+
+ + +
+
+
+ + + {Array.from({ length: 6 }).map((_, i) => ( + + ))} + + + + + {Array.from({ length: 4 }).map((_, i) => ( + + ))} + + +
-
- - - {Array.from({ length: 6 }).map((_, i) => ( - - ))} - - - - - {Array.from({ length: 4 }).map((_, i) => ( - - ))} - - - -
-
- }> + } + >
); diff --git a/panel/src/app/(dashboard)/journals/[entryId]/page.tsx b/panel/src/app/(dashboard)/journals/[entryId]/page.tsx index a3e46218..d98acd41 100644 --- a/panel/src/app/(dashboard)/journals/[entryId]/page.tsx +++ b/panel/src/app/(dashboard)/journals/[entryId]/page.tsx @@ -144,7 +144,10 @@ export default function JournalEntryPage({ params }: JournalEntryPageProps) { Related Task
- + Task #{entry.task_id.slice(0, 8)} diff --git a/panel/src/app/(dashboard)/journals/page.tsx b/panel/src/app/(dashboard)/journals/page.tsx index 90e48680..2aa11d8a 100644 --- a/panel/src/app/(dashboard)/journals/page.tsx +++ b/panel/src/app/(dashboard)/journals/page.tsx @@ -89,37 +89,52 @@ function JournalsPageContent() { }, [selectedAgentId, agentSearch, urlType, taskFilter]); // Update URL params - const updateParams = useCallback((updates: Record) => { - const params = new URLSearchParams(searchParams.toString()); - Object.entries(updates).forEach(([key, value]) => { - if (value) { - params.set(key, value); - } else { - params.delete(key); + const updateParams = useCallback( + (updates: Record) => { + const params = new URLSearchParams(searchParams.toString()); + Object.entries(updates).forEach(([key, value]) => { + if (value) { + params.set(key, value); + } else { + params.delete(key); + } + }); + const query = params.toString(); + router.push(query ? `/journals?${query}` : "/journals"); + }, + [router, searchParams], + ); + + const handleSelectAgent = useCallback( + (agentId: string | null) => { + // Only reset filters when changing to a different agent + if (agentId !== selectedAgentId) { + updateParams({ agent: agentId, type: null, task: null }); } - }); - const query = params.toString(); - router.push(query ? `/journals?${query}` : "/journals"); - }, [router, searchParams]); + }, + [updateParams, selectedAgentId], + ); - const handleSelectAgent = useCallback((agentId: string | null) => { - // Only reset filters when changing to a different agent - if (agentId !== selectedAgentId) { - updateParams({ agent: agentId, type: null, task: null }); - } - }, [updateParams, selectedAgentId]); + const handleAgentSearch = useCallback( + (value: string) => { + updateParams({ q: value || null }); + }, + [updateParams], + ); - const handleAgentSearch = useCallback((value: string) => { - updateParams({ q: value || null }); - }, [updateParams]); + const handleTypeChange = useCallback( + (value: JournalEntryType | "all") => { + updateParams({ type: value === "all" ? null : value }); + }, + [updateParams], + ); - const handleTypeChange = useCallback((value: JournalEntryType | "all") => { - updateParams({ type: value === "all" ? null : value }); - }, [updateParams]); - - const handleTaskChange = useCallback((value: string | null) => { - updateParams({ task: value }); - }, [updateParams]); + const handleTaskChange = useCallback( + (value: string | null) => { + updateParams({ task: value }); + }, + [updateParams], + ); // Filter agents by search const filteredAgents = (agents ?? []).filter((agent) => { @@ -211,35 +226,37 @@ function JournalsPageContent() { // Wrap in Suspense for useSearchParams export default function JournalsPage() { return ( - -
-
- - + +
+
+ + +
+
+
+
+ + + + {Array.from({ length: 5 }).map((_, i) => ( + + ))} + + +
+
+ + + + + +
-
-
- - - - {Array.from({ length: 5 }).map((_, i) => ( - - ))} - - -
-
- - - - - -
-
-
- }> + } + >
); diff --git a/panel/src/app/(dashboard)/kanban/page.tsx b/panel/src/app/(dashboard)/kanban/page.tsx index 7daa40e2..a5c4c08e 100644 --- a/panel/src/app/(dashboard)/kanban/page.tsx +++ b/panel/src/app/(dashboard)/kanban/page.tsx @@ -2,7 +2,12 @@ import { Suspense } from "react"; import { useSearchParams, useRouter } from "next/navigation"; -import { DevKanban, QaKanban, PrReviewKanban, PmKanban } from "@/components/kanban"; +import { + DevKanban, + QaKanban, + PrReviewKanban, + PmKanban, +} from "@/components/kanban"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Skeleton } from "@/components/ui/skeleton"; import { Code, TestTube, GitPullRequest, ClipboardList } from "lucide-react"; @@ -66,16 +71,18 @@ function KanbanPageContent() { // Wrap in Suspense for useSearchParams export default function KanbanPage() { return ( - - -
- {Array.from({ length: 4 }).map((_, i) => ( - - ))} + + +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
-
- }> + } + > ); diff --git a/panel/src/app/(dashboard)/metrics/page.tsx b/panel/src/app/(dashboard)/metrics/page.tsx index c797bfff..2a8ea419 100644 --- a/panel/src/app/(dashboard)/metrics/page.tsx +++ b/panel/src/app/(dashboard)/metrics/page.tsx @@ -42,7 +42,10 @@ import { Coins, Sparkles, } from "lucide-react"; -import type { UsageProjection as UP, CacheEfficiencyResponse as CER } from "@/types"; +import type { + UsageProjection as UP, + CacheEfficiencyResponse as CER, +} from "@/types"; // ─── Humanized number formatting ───────────────────────────────────────────── @@ -71,7 +74,14 @@ interface MetricCardProps { trendValue?: string; } -function MetricCard({ title, value, subtitle, icon, trend, trendValue }: MetricCardProps) { +function MetricCard({ + title, + value, + subtitle, + icon, + trend, + trendValue, +}: MetricCardProps) { const displayValue = typeof value === "number" ? humanizeCount(value) : value; return ( @@ -87,10 +97,19 @@ function MetricCard({ title, value, subtitle, icon, trend, trendValue }: MetricC

{subtitle}

)} {trend && trendValue && ( -
- +
+ {trendValue}
)} @@ -106,8 +125,13 @@ interface TeamHealthCardProps { completedToday: number; } -function TeamHealthCard({ team, activeTasks, blockedTasks, completedToday }: TeamHealthCardProps) { - const healthScore = Math.max(0, 100 - (blockedTasks * 20)); +function TeamHealthCard({ + team, + activeTasks, + blockedTasks, + completedToday, +}: TeamHealthCardProps) { + const healthScore = Math.max(0, 100 - blockedTasks * 20); return ( @@ -144,12 +168,16 @@ function TeamHealthCard({ team, activeTasks, blockedTasks, completedToday }: Tea function PerformanceTabContent() { const { data: tasks, error: tasksError, refetch: refetchTasks } = useTasks(); - const { data: status, error: statusError, refetch: refetchStatus } = useOrchestratorStatus(); + const { + data: status, + error: statusError, + refetch: refetchStatus, + } = useOrchestratorStatus(); - const isOffline = (tasksError || statusError) && ( - tasksError?.message?.includes("Network Error") || - statusError?.message?.includes("Network Error") - ); + const isOffline = + (tasksError || statusError) && + (tasksError?.message?.includes("Network Error") || + statusError?.message?.includes("Network Error")); const refetch = () => { refetchTasks(); @@ -176,17 +204,35 @@ function PerformanceTabContent() { }).length; // Task status counts - const pending = taskList.filter((t) => t.status === TaskStatus.PENDING).length; - const inProgress = taskList.filter((t) => t.status === TaskStatus.IN_PROGRESS).length; - const blocked = taskList.filter((t) => t.status === TaskStatus.BLOCKED).length; - const awaitingQa = taskList.filter((t) => t.status === TaskStatus.AWAITING_QA).length; - const completed = taskList.filter((t) => t.status === TaskStatus.COMPLETED).length; + const pending = taskList.filter( + (t) => t.status === TaskStatus.PENDING, + ).length; + const inProgress = taskList.filter( + (t) => t.status === TaskStatus.IN_PROGRESS, + ).length; + const blocked = taskList.filter( + (t) => t.status === TaskStatus.BLOCKED, + ).length; + const awaitingQa = taskList.filter( + (t) => t.status === TaskStatus.AWAITING_QA, + ).length; + const completed = taskList.filter( + (t) => t.status === TaskStatus.COMPLETED, + ).length; // Agent counts - const runningAgents = status?.by_state?.running || agentList.filter((a) => a.state === "running").length; - const idleAgents = status?.by_state?.idle || agentList.filter((a) => a.state === "idle" || a.state === "stopped").length; - const waitingAgents = status?.waiting_count || agentList.filter((a) => a.state === "waiting_long").length; - const errorAgents = status?.by_state?.error || agentList.filter((a) => a.state === "error").length; + const runningAgents = + status?.by_state?.running || + agentList.filter((a) => a.state === "running").length; + const idleAgents = + status?.by_state?.idle || + agentList.filter((a) => a.state === "idle" || a.state === "stopped").length; + const waitingAgents = + status?.waiting_count || + agentList.filter((a) => a.state === "waiting_long").length; + const errorAgents = + status?.by_state?.error || + agentList.filter((a) => a.state === "error").length; // Team metrics const teamMetrics = Object.values(Team).map((team) => { @@ -194,9 +240,10 @@ function PerformanceTabContent() { return { team, activeTasks: teamTasks.filter((t) => - [TaskStatus.IN_PROGRESS, TaskStatus.CLAIMED].includes(t.status) + [TaskStatus.IN_PROGRESS, TaskStatus.CLAIMED].includes(t.status), ).length, - blockedTasks: teamTasks.filter((t) => t.status === TaskStatus.BLOCKED).length, + blockedTasks: teamTasks.filter((t) => t.status === TaskStatus.BLOCKED) + .length, completedToday: teamTasks.filter((t) => { if (!t.completed_at) return false; const c = new Date(t.completed_at); @@ -242,7 +289,11 @@ function PerformanceTabContent() { /> 0 ? Math.round((completed / taskList.length) * 100) + "%" : "0%"} + value={ + taskList.length > 0 + ? Math.round((completed / taskList.length) * 100) + "%" + : "0%" + } subtitle="Of all tasks" icon={} /> @@ -341,7 +392,8 @@ function TokenUsageCostsSection() { const { data: sessions, isLoading: loadingSessions } = useUsageSessions(100); const { data: modelUsage, isLoading: loadingModels } = useModelUsage("24h"); const { data: projection, isLoading: loadingProj } = useUsageProjection(); - const { data: cacheStats, isLoading: loadingCache } = useCacheEfficiency("24h"); + const { data: cacheStats, isLoading: loadingCache } = + useCacheEfficiency("24h"); const trendUp = (summary?.trend_pct ?? 0) >= 0; @@ -369,7 +421,11 @@ function TokenUsageCostsSection() { /> @@ -387,7 +443,11 @@ function TokenUsageCostsSection() { /> } isLoading={loadingCache} /> @@ -429,11 +489,19 @@ interface SummaryCardProps { isLoading: boolean; } -function SummaryCard({ title, value, icon, trend, isLoading }: SummaryCardProps) { +function SummaryCard({ + title, + value, + icon, + trend, + isLoading, +}: SummaryCardProps) { return ( - {title} + + {title} + {icon} @@ -479,7 +547,9 @@ function ProjectionCard({ projection, isLoading }: ProjectionCardProps) { ) : (
- {projection != null ? "$" + projection.projected_monthly_cost_usd.toFixed(2) : "—"} + {projection != null + ? "$" + projection.projected_monthly_cost_usd.toFixed(2) + : "—"}

Based on {projection?.basis_days ?? 7}-day rolling average ($ @@ -497,7 +567,10 @@ interface CacheEfficiencyCardProps { isLoading: boolean; } -function CacheEfficiencyCard({ cacheStats, isLoading }: CacheEfficiencyCardProps) { +function CacheEfficiencyCard({ + cacheStats, + isLoading, +}: CacheEfficiencyCardProps) { const pct = cacheStats ? cacheStats.cache_hit_rate * 100 : 0; return ( @@ -515,8 +588,9 @@ function CacheEfficiencyCard({ cacheStats, isLoading }: CacheEfficiencyCardProps

{pct.toFixed(1)}%

- {cacheStats ? fmtTokens(cacheStats.tokens_cache_read) : "—"} cache reads · - saved ${cacheStats?.cost_saved_by_cache_usd.toFixed(4) ?? "—"} + {cacheStats ? fmtTokens(cacheStats.tokens_cache_read) : "—"} cache + reads · saved $ + {cacheStats?.cost_saved_by_cache_usd.toFixed(4) ?? "—"}

@@ -530,7 +604,11 @@ function CacheEfficiencyCard({ cacheStats, isLoading }: CacheEfficiencyCardProps type MetricsTab = "performance" | "token-usage" | "delivery"; -const VALID_METRICS_TABS: MetricsTab[] = ["performance", "token-usage", "delivery"]; +const VALID_METRICS_TABS: MetricsTab[] = [ + "performance", + "token-usage", + "delivery", +]; function isValidMetricsTab(value: string | null): value is MetricsTab { return VALID_METRICS_TABS.includes(value as MetricsTab); @@ -544,7 +622,9 @@ function MetricsPageContent() { // Read ?tab= from URL, default to "performance" const rawTab = searchParams.get("tab"); - const activeTab: MetricsTab = isValidMetricsTab(rawTab) ? rawTab : "performance"; + const activeTab: MetricsTab = isValidMetricsTab(rawTab) + ? rawTab + : "performance"; function handleTabChange(value: string) { const params = new URLSearchParams(searchParams.toString()); @@ -590,32 +670,34 @@ function MetricsPageContent() { // Wrap in Suspense for useSearchParams export default function MetricsPage() { return ( - -
-
- - + +
+
+ + +
+
+
+ + +
+
+ {Array.from({ length: 4 }).map((_, i) => ( + + + + + + + + + ))}
-
- - -
-
- {Array.from({ length: 4 }).map((_, i) => ( - - - - - - - - - ))} -
-
- }> + } + >
); diff --git a/panel/src/app/(dashboard)/notifications/page.tsx b/panel/src/app/(dashboard)/notifications/page.tsx index 687d893f..b838e395 100644 --- a/panel/src/app/(dashboard)/notifications/page.tsx +++ b/panel/src/app/(dashboard)/notifications/page.tsx @@ -34,21 +34,38 @@ import { toast } from "sonner"; import { Markdown } from "@/components/ui/markdown"; const typeIcons: Record = { - [NotificationType.TASK_ASSIGNMENT]: , - [NotificationType.PRIORITY_CHANGE]: , - [NotificationType.BLOCKER_ESCALATION]: , - [NotificationType.REVIEW_REQUEST]: , - [NotificationType.DOCUMENTATION_REQUEST]: , - [NotificationType.ALERT]: , + [NotificationType.TASK_ASSIGNMENT]: ( + + ), + [NotificationType.PRIORITY_CHANGE]: ( + + ), + [NotificationType.BLOCKER_ESCALATION]: ( + + ), + [NotificationType.REVIEW_REQUEST]: ( + + ), + [NotificationType.DOCUMENTATION_REQUEST]: ( + + ), + [NotificationType.ALERT]: ( + + ), [NotificationType.BROADCAST]: , - [NotificationType.KNOWLEDGE_SHARE]: , + [NotificationType.KNOWLEDGE_SHARE]: ( + + ), [NotificationType.MENTION]: , }; const priorityColors: Record = { - [NotificationPriority.NORMAL]: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300", - [NotificationPriority.HIGH]: "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300", - [NotificationPriority.URGENT]: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300", + [NotificationPriority.NORMAL]: + "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300", + [NotificationPriority.HIGH]: + "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300", + [NotificationPriority.URGENT]: + "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300", }; interface NotificationCardProps { @@ -57,23 +74,37 @@ interface NotificationCardProps { onAcknowledge: () => void; } -function NotificationCard({ notification, onMarkRead, onAcknowledge }: NotificationCardProps) { +function NotificationCard({ + notification, + onMarkRead, + onAcknowledge, +}: NotificationCardProps) { return ( - +
{typeIcons[notification.type]}
{notification.subject} - + {notification.priority} {!notification.is_read && ( - New + + New + )} {notification.requires_ack && !notification.is_acknowledged && ( - Needs Ack + + Needs Ack + )}
@@ -129,19 +160,21 @@ function NotificationsPageContent() { } const { data, isLoading, error, refetch } = useNotifications( - activeTab === "unread" ? { unread_only: true } : - activeTab === "pending" ? { pending_ack_only: true } : - undefined + activeTab === "unread" + ? { unread_only: true } + : activeTab === "pending" + ? { pending_ack_only: true } + : undefined, ); const markRead = useMarkNotificationRead(); const acknowledge = useAcknowledgeNotification(); const markAllRead = useMarkAllNotificationsRead(); - const isOffline = error && ( - error.message?.includes("Network Error") || - (error as { code?: string })?.code === "ERR_NETWORK" - ); + const isOffline = + error && + (error.message?.includes("Network Error") || + (error as { code?: string })?.code === "ERR_NETWORK"); const handleMarkRead = async (id: string) => { try { @@ -213,7 +246,9 @@ function NotificationsPageContent() { -
{data.unread_count}
+
+ {data.unread_count} +
@@ -224,7 +259,9 @@ function NotificationsPageContent() { -
{data.pending_ack_count}
+
+ {data.pending_ack_count} +
@@ -244,7 +281,10 @@ function NotificationsPageContent() { Unread {data && data.unread_count > 0 && `(${data.unread_count})`} - Pending {data && data.pending_ack_count > 0 && `(${data.pending_ack_count})`} + Pending{" "} + {data && + data.pending_ack_count > 0 && + `(${data.pending_ack_count})`} @@ -284,41 +324,43 @@ function NotificationsPageContent() { // Wrap in Suspense for useSearchParams export default function NotificationsPage() { return ( - -
-
- - + +
+
+ + +
+
+ + +
-
- - +
+ {Array.from({ length: 3 }).map((_, i) => ( + + + + + + + + + ))} +
+
+ {Array.from({ length: 5 }).map((_, i) => ( + + + + + + ))}
-
- {Array.from({ length: 3 }).map((_, i) => ( - - - - - - - - - ))} -
-
- {Array.from({ length: 5 }).map((_, i) => ( - - - - - - ))} -
-
- }> + } + > ); diff --git a/panel/src/app/(dashboard)/projects/page.tsx b/panel/src/app/(dashboard)/projects/page.tsx index ebd18202..ae80c981 100644 --- a/panel/src/app/(dashboard)/projects/page.tsx +++ b/panel/src/app/(dashboard)/projects/page.tsx @@ -5,7 +5,11 @@ import { useSearchParams, useRouter } from "next/navigation"; import { useProjects } from "@/hooks/use-projects"; import { Team } from "@/types"; import { OfflineState } from "@/components/ui/offline-state"; -import { CreateProjectDialog, ProjectFilters, ProjectTable } from "@/components/projects"; +import { + CreateProjectDialog, + ProjectFilters, + ProjectTable, +} from "@/components/projects"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { RefreshCw } from "lucide-react"; @@ -19,7 +23,7 @@ function ProjectsPageContent() { const cellFilterParam = searchParams.get("cell"); const cellFilter = useMemo( () => (cellFilterParam?.split(",").filter(Boolean) as Team[]) || [], - [cellFilterParam] + [cellFilterParam], ); const showInactive = searchParams.get("inactive") === "true"; @@ -37,32 +41,37 @@ function ProjectsPageContent() { const query = params.toString(); router.push(query ? `/projects?${query}` : "/projects"); }, - [router, searchParams] + [router, searchParams], ); const handleSearchChange = useCallback( (value: string) => { updateParams({ q: value || null }); }, - [updateParams] + [updateParams], ); const handleCellChange = useCallback( (value: Team[]) => { updateParams({ cell: value.length > 0 ? value.join(",") : null }); }, - [updateParams] + [updateParams], ); const handleShowInactiveChange = useCallback( (value: boolean) => { updateParams({ inactive: value ? "true" : null }); }, - [updateParams] + [updateParams], ); // Fetch projects - const { data: projects, isLoading, error, refetch } = useProjects({ + const { + data: projects, + isLoading, + error, + refetch, + } = useProjects({ active_only: !showInactive, }); @@ -72,12 +81,18 @@ function ProjectsPageContent() { return projects.filter((project) => { // Search filter - if (searchQuery && !project.name.toLowerCase().includes(searchQuery.toLowerCase())) { + if ( + searchQuery && + !project.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) { return false; } // Cell filter (if any selected, project must match one of them) - if (cellFilter.length > 0 && !cellFilter.includes(project.assigned_cell)) { + if ( + cellFilter.length > 0 && + !cellFilter.includes(project.assigned_cell) + ) { return false; } diff --git a/panel/src/app/(dashboard)/settings/page.tsx b/panel/src/app/(dashboard)/settings/page.tsx index fd31fd77..35bb9af6 100644 --- a/panel/src/app/(dashboard)/settings/page.tsx +++ b/panel/src/app/(dashboard)/settings/page.tsx @@ -3,7 +3,13 @@ import { useState } from "react"; import { useTheme } from "next-themes"; import { useUIStore } from "@/store"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -16,14 +22,7 @@ import { SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; -import { - Settings, - Palette, - Bell, - Server, - User, - Save, -} from "lucide-react"; +import { Settings, Palette, Bell, Server, User, Save } from "lucide-react"; import { toast } from "sonner"; import { API_URL, WS_URL } from "@/lib/constants"; import { TranscriptRetentionCard } from "@/components/settings/transcript-retention-card"; @@ -69,11 +68,15 @@ export default function SettingsPage() {
- CEO + + CEO +

Renzo

-

Chief Executive Officer

+

+ Chief Executive Officer +

Agent ID: 00000000-0000-0000-0000-000000000001

@@ -89,7 +92,9 @@ export default function SettingsPage() { Appearance - Customize the look and feel of the panel + + Customize the look and feel of the panel +
@@ -143,10 +148,7 @@ export default function SettingsPage() { Automatically refresh data periodically

- +
@@ -224,7 +226,9 @@ export default function SettingsPage() { Connection Info - Backend API configuration (read-only) + + Backend API configuration (read-only) +
@@ -236,7 +240,8 @@ export default function SettingsPage() {

- These values are configured via environment variables (NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WS_URL) + These values are configured via environment variables + (NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WS_URL)

diff --git a/panel/src/app/(dashboard)/tasks/[taskId]/page.tsx b/panel/src/app/(dashboard)/tasks/[taskId]/page.tsx index 3a9bbb81..000ab632 100644 --- a/panel/src/app/(dashboard)/tasks/[taskId]/page.tsx +++ b/panel/src/app/(dashboard)/tasks/[taskId]/page.tsx @@ -6,7 +6,11 @@ import { useTask, useTaskLifecycle, useUpdateTask } from "@/hooks/use-tasks"; import { useProject } from "@/hooks/use-projects"; import { useCreateBranch, useCreatePR, useMergePR } from "@/hooks/use-git"; import { Team, TaskStatus } from "@/types"; -import { TaskHeader, TaskMetadata, TaskTabs } from "@/components/tasks/task-detail"; +import { + TaskHeader, + TaskMetadata, + TaskTabs, +} from "@/components/tasks/task-detail"; import { ApproveAndStartButton } from "@/components/tasks/approve-and-start-button"; import { EscalateToCeoDialog, @@ -42,7 +46,8 @@ export default function TaskDetailPage({ params }: TaskDetailPageProps) { // Dialog states const [escalateDialogOpen, setEscalateDialogOpen] = useState(false); - const [approveAndMergeDialogOpen, setApproveAndMergeDialogOpen] = useState(false); + const [approveAndMergeDialogOpen, setApproveAndMergeDialogOpen] = + useState(false); const [approveDialogOpen, setApproveDialogOpen] = useState(false); const [rejectDialogOpen, setRejectDialogOpen] = useState(false); const [branchDialogOpen, setBranchDialogOpen] = useState(false); @@ -51,7 +56,8 @@ export default function TaskDetailPage({ params }: TaskDetailPageProps) { const [passQaDialogOpen, setPassQaDialogOpen] = useState(false); const [failQaDialogOpen, setFailQaDialogOpen] = useState(false); const [docsCompleteDialogOpen, setDocsCompleteDialogOpen] = useState(false); - const [submitPmReviewDialogOpen, setSubmitPmReviewDialogOpen] = useState(false); + const [submitPmReviewDialogOpen, setSubmitPmReviewDialogOpen] = + useState(false); const [completeDialogOpen, setCompleteDialogOpen] = useState(false); const handleAction = async (action: string) => { @@ -141,7 +147,10 @@ export default function TaskDetailPage({ params }: TaskDetailPageProps) { setEscalateDialogOpen(true); return; // Don't refetch yet, dialog will handle it case "request-changes": - await lifecycle.failQa.mutateAsync({ taskId: task.id, qaNotes: "Changes requested by PM" }); + await lifecycle.failQa.mutateAsync({ + taskId: task.id, + qaNotes: "Changes requested by PM", + }); toast.success("Changes requested"); break; case "create-branch": @@ -221,11 +230,21 @@ export default function TaskDetailPage({ params }: TaskDetailPageProps) { refetch(); } catch (err) { if (axios.isAxiosError(err)) { - const detail = (err.response?.data as { detail?: string } | undefined)?.detail ?? ""; + const detail = + (err.response?.data as { detail?: string } | undefined)?.detail ?? ""; if (typeof detail === "string" && detail.startsWith("NO_PR")) { - toast.error("No PR found for this task. Create a pull request before merging."); - } else if (typeof detail === "string" && detail.startsWith("Merge failed")) { - toast.error("Merge failed: " + (detail.slice("Merge failed".length).replace(/^[: ]+/, "") || "the merge could not be completed")); + toast.error( + "No PR found for this task. Create a pull request before merging.", + ); + } else if ( + typeof detail === "string" && + detail.startsWith("Merge failed") + ) { + toast.error( + "Merge failed: " + + (detail.slice("Merge failed".length).replace(/^[: ]+/, "") || + "the merge could not be completed"), + ); } else { toast.error("Failed to approve and merge task"); } @@ -333,7 +352,12 @@ export default function TaskDetailPage({ params }: TaskDetailPageProps) { await createBranch.mutateAsync({ project_slug: project.slug, task_id: task.id, - branch_type: branchType as "feature" | "bug" | "chore" | "docs" | "hotfix", + branch_type: branchType as + | "feature" + | "bug" + | "chore" + | "docs" + | "hotfix", agent_id: "ceo", // CEO is creating the branch from the panel }); toast.success("Branch created successfully"); @@ -403,7 +427,8 @@ export default function TaskDetailPage({ params }: TaskDetailPageProps) {

Task Not Found

- {error?.message ?? "The task you're looking for doesn't exist or has been deleted."} + {error?.message ?? + "The task you're looking for doesn't exist or has been deleted."}

- diff --git a/panel/src/components/auditor/create-flag-dialog.tsx b/panel/src/components/auditor/create-flag-dialog.tsx index f83667a6..8df0d292 100644 --- a/panel/src/components/auditor/create-flag-dialog.tsx +++ b/panel/src/components/auditor/create-flag-dialog.tsx @@ -44,7 +44,10 @@ const CATEGORY_OPTIONS = [ "other", ]; -export function CreateFlagDialog({ open, onOpenChange }: CreateFlagDialogProps) { +export function CreateFlagDialog({ + open, + onOpenChange, +}: CreateFlagDialogProps) { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [severity, setSeverity] = useState(FlagSeverity.INFO); @@ -124,7 +127,10 @@ export function CreateFlagDialog({ open, onOpenChange }: CreateFlagDialogProps)
- setSeverity(v as FlagSeverity)} + > diff --git a/panel/src/components/auditor/flagged-item.tsx b/panel/src/components/auditor/flagged-item.tsx index 771288f7..ba353ce4 100644 --- a/panel/src/components/auditor/flagged-item.tsx +++ b/panel/src/components/auditor/flagged-item.tsx @@ -36,7 +36,11 @@ function formatTime(timestamp: string): string { return `${diffDays}d ago`; } -export function FlaggedItem({ flag, onResolve, onReportToCeo }: FlaggedItemProps) { +export function FlaggedItem({ + flag, + onResolve, + onReportToCeo, +}: FlaggedItemProps) { const isResolved = !!flag.resolved_at; return ( @@ -63,7 +67,9 @@ export function FlaggedItem({ flag, onResolve, onReportToCeo }: FlaggedItemProps )}
-

{flag.description}

+

+ {flag.description} +

diff --git a/panel/src/components/auditor/flagged-items-panel.tsx b/panel/src/components/auditor/flagged-items-panel.tsx index fbe042e8..dceda2ea 100644 --- a/panel/src/components/auditor/flagged-items-panel.tsx +++ b/panel/src/components/auditor/flagged-items-panel.tsx @@ -25,8 +25,13 @@ interface FlaggedItemsPanelProps { isLoading: boolean; } -export function FlaggedItemsPanel({ flags, isLoading }: FlaggedItemsPanelProps) { - const [filter, setFilter] = useState<"all" | "unresolved" | "resolved">("unresolved"); +export function FlaggedItemsPanel({ + flags, + isLoading, +}: FlaggedItemsPanelProps) { + const [filter, setFilter] = useState<"all" | "unresolved" | "resolved">( + "unresolved", + ); const [createDialogOpen, setCreateDialogOpen] = useState(false); const resolveFlag = useResolveAuditorFlag(); @@ -74,7 +79,12 @@ export function FlaggedItemsPanel({ flags, isLoading }: FlaggedItemsPanelProps) )}
- + setFilter(v as "all" | "unresolved" | "resolved") + } + > diff --git a/panel/src/components/auditor/live-feeds-panel.tsx b/panel/src/components/auditor/live-feeds-panel.tsx index 4a54a73f..2c824a97 100644 --- a/panel/src/components/auditor/live-feeds-panel.tsx +++ b/panel/src/components/auditor/live-feeds-panel.tsx @@ -13,7 +13,7 @@ interface LiveFeedsPanelProps { export function LiveFeedsPanel({ feeds, isLoading }: LiveFeedsPanelProps) { const activeCount = (feeds ?? []).filter( - (f) => f.status === "active" || f.message_count_24h > 0 + (f) => f.status === "active" || f.message_count_24h > 0, ).length; return ( diff --git a/panel/src/components/auditor/quality-metrics-panel.tsx b/panel/src/components/auditor/quality-metrics-panel.tsx index 359f25a1..8b320aa2 100644 --- a/panel/src/components/auditor/quality-metrics-panel.tsx +++ b/panel/src/components/auditor/quality-metrics-panel.tsx @@ -3,7 +3,13 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Progress } from "@/components/ui/progress"; -import { BarChart3, CheckCircle, Clock, FileText, AlertTriangle } from "lucide-react"; +import { + BarChart3, + CheckCircle, + Clock, + FileText, + AlertTriangle, +} from "lucide-react"; interface QualityMetricsPanelProps { metrics: Record | undefined; @@ -36,7 +42,8 @@ const METRICS: MetricDisplay[] = [ key: "avg_completion_time", label: "Avg Completion Time", icon: , - format: (v) => `${(typeof v === "number" ? v : parseFloat(v) || 0).toFixed(1)}h`, + format: (v) => + `${(typeof v === "number" ? v : parseFloat(v) || 0).toFixed(1)}h`, }, { key: "documentation_rate", @@ -59,7 +66,10 @@ const METRICS: MetricDisplay[] = [ }, ]; -export function QualityMetricsPanel({ metrics, isLoading }: QualityMetricsPanelProps) { +export function QualityMetricsPanel({ + metrics, + isLoading, +}: QualityMetricsPanelProps) { return ( diff --git a/panel/src/components/auditor/reports-panel.tsx b/panel/src/components/auditor/reports-panel.tsx index 9448f831..c3407f69 100644 --- a/panel/src/components/auditor/reports-panel.tsx +++ b/panel/src/components/auditor/reports-panel.tsx @@ -1,7 +1,10 @@ "use client"; import { AuditorReport } from "@/types"; -import { useSendAuditorReport, useCreateAuditorReport } from "@/hooks/use-dashboard"; +import { + useSendAuditorReport, + useCreateAuditorReport, +} from "@/hooks/use-dashboard"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -24,7 +27,11 @@ function formatDate(timestamp: string): string { }); } -export function ReportsPanel({ reports, isLoading, onCreateReport }: ReportsPanelProps) { +export function ReportsPanel({ + reports, + isLoading, + onCreateReport, +}: ReportsPanelProps) { const sendReport = useSendAuditorReport(); const createReport = useCreateAuditorReport(); @@ -44,7 +51,7 @@ export function ReportsPanel({ reports, isLoading, onCreateReport }: ReportsPane { onSuccess: () => toast.success("Draft report created"), onError: () => toast.error("Failed to create report"), - } + }, ); }; @@ -65,7 +72,11 @@ export function ReportsPanel({ reports, isLoading, onCreateReport }: ReportsPane Reports - diff --git a/panel/src/components/business/__tests__/company-scorecard-card.test.tsx b/panel/src/components/business/__tests__/company-scorecard-card.test.tsx index 008e3edb..da0e4db2 100644 --- a/panel/src/components/business/__tests__/company-scorecard-card.test.tsx +++ b/panel/src/components/business/__tests__/company-scorecard-card.test.tsx @@ -97,7 +97,7 @@ describe("CompanyScorecardCard", () => { // Should NOT show error or data content expect( - screen.queryByText("Could not load scorecard data") + screen.queryByText("Could not load scorecard data"), ).not.toBeInTheDocument(); expect(screen.queryByText("Company Scorecard")).not.toBeInTheDocument(); }); @@ -111,7 +111,7 @@ describe("CompanyScorecardCard", () => { render(); expect( - screen.getByText("Could not load scorecard data") + screen.getByText("Could not load scorecard data"), ).toBeInTheDocument(); // Skeleton and scorecard body should not appear @@ -125,7 +125,7 @@ describe("CompanyScorecardCard", () => { // When data is falsy the component falls through to the OfflineState branch expect( - screen.getByText("Could not load scorecard data") + screen.getByText("Could not load scorecard data"), ).toBeInTheDocument(); }); diff --git a/panel/src/components/business/company-scorecard-card.tsx b/panel/src/components/business/company-scorecard-card.tsx index 3a908bc1..216f0d12 100644 --- a/panel/src/components/business/company-scorecard-card.tsx +++ b/panel/src/components/business/company-scorecard-card.tsx @@ -112,8 +112,12 @@ interface SpendSectionProps { } function SpendSection({ spend }: SpendSectionProps) { - const { monthly_budget_cap_usd, spend_30d_usd, projected_monthly_usd, over_budget } = - spend; + const { + monthly_budget_cap_usd, + spend_30d_usd, + projected_monthly_usd, + over_budget, + } = spend; // Red/destructive only when cap is a non-null number AND over_budget is true const isOverBudget = monthly_budget_cap_usd !== null && over_budget; @@ -139,7 +143,9 @@ function SpendSection({ spend }: SpendSectionProps) {
Monthly cap {monthly_budget_cap_usd === null ? ( - No budget cap set + + No budget cap set + ) : ( Median lead time {hasData ? ( - {medianLeadTimeHours.toFixed(1)}h median — target: < 24h + {medianLeadTimeHours.toFixed(1)}h median — target: + < 24h ) : ( No data yet diff --git a/panel/src/components/business/goals-tab.tsx b/panel/src/components/business/goals-tab.tsx index f8b78d4d..49086863 100644 --- a/panel/src/components/business/goals-tab.tsx +++ b/panel/src/components/business/goals-tab.tsx @@ -53,20 +53,18 @@ interface ObjectivesEditorProps { disabled: boolean; } -function ObjectivesEditor({ items, onChange, disabled }: ObjectivesEditorProps) { +function ObjectivesEditor({ + items, + onChange, + disabled, +}: ObjectivesEditorProps) { // Derive keys from the first item; fall back to generic keys. const keys = - items.length > 0 - ? Object.keys(items[0]) - : ["metric", "target", "status"]; + items.length > 0 ? Object.keys(items[0]) : ["metric", "target", "status"]; - const handleChange = ( - rowIdx: number, - key: string, - value: string - ) => { + const handleChange = (rowIdx: number, key: string, value: string) => { const next = items.map((item, i) => - i === rowIdx ? { ...item, [key]: value } : item + i === rowIdx ? { ...item, [key]: value } : item, ); onChange(next); }; @@ -102,7 +100,10 @@ function ObjectivesEditor({ items, onChange, disabled }: ObjectivesEditorProps)
{keys.map((key) => (
-
{proposed ? (
diff --git a/panel/src/components/business/secretary-tab.tsx b/panel/src/components/business/secretary-tab.tsx index 5b132661..cbbce0da 100644 --- a/panel/src/components/business/secretary-tab.tsx +++ b/panel/src/components/business/secretary-tab.tsx @@ -26,7 +26,12 @@ interface DirectiveCardProps { busy: boolean; } -function DirectiveCard({ directive, onConfirm, onReject, busy }: DirectiveCardProps) { +function DirectiveCard({ + directive, + onConfirm, + onReject, + busy, +}: DirectiveCardProps) { const [rejectOpen, setRejectOpen] = useState(false); const payloadKeys = Object.keys(directive.payload); diff --git a/panel/src/components/communications/channel-item.tsx b/panel/src/components/communications/channel-item.tsx index 53bd36a8..6ef25b67 100644 --- a/panel/src/components/communications/channel-item.tsx +++ b/panel/src/components/communications/channel-item.tsx @@ -27,7 +27,7 @@ export function ChannelItem({ "w-full h-auto justify-start gap-2 px-2 py-1.5 font-normal whitespace-normal", isSelected ? "bg-primary/10 text-primary hover:bg-primary/10 hover:text-primary" - : "text-muted-foreground hover:bg-muted hover:text-foreground" + : "text-muted-foreground hover:bg-muted hover:text-foreground", )} > {channel.is_private ? ( diff --git a/panel/src/components/communications/channel-sidebar.tsx b/panel/src/components/communications/channel-sidebar.tsx index 6891b32c..b28b1b5e 100644 --- a/panel/src/components/communications/channel-sidebar.tsx +++ b/panel/src/components/communications/channel-sidebar.tsx @@ -26,10 +26,7 @@ function groupChannels(channels: Channel[]): Record { groups["Cell Channels"].push(channel); } else if (channel.name.includes("-all")) { groups["Cross-Cell"].push(channel); - } else if ( - channel.name.includes("pm") || - channel.name.includes("board") - ) { + } else if (channel.name.includes("pm") || channel.name.includes("board")) { groups["Management"].push(channel); } else { groups["Special"].push(channel); diff --git a/panel/src/components/communications/communications-view.tsx b/panel/src/components/communications/communications-view.tsx index 61ed8671..3d4f6f2c 100644 --- a/panel/src/components/communications/communications-view.tsx +++ b/panel/src/components/communications/communications-view.tsx @@ -10,7 +10,9 @@ import { RefreshCw, Hash, Users, ExternalLink } from "lucide-react"; import Link from "next/link"; export function CommunicationsView() { - const [selectedChannelId, setSelectedChannelId] = useState(null); + const [selectedChannelId, setSelectedChannelId] = useState( + null, + ); const { data: channels, isLoading: loadingChannels, refetch } = useChannels(); // Get selected channel @@ -69,13 +71,17 @@ export function CommunicationsView() {
- {selectedChannel.name} + + {selectedChannel.name} + {selectedChannel.member_count}
- + + +
@@ -118,7 +130,9 @@ export function CommunicationsView() {

Select a Channel

-

Choose a channel from the sidebar to view details

+

+ Choose a channel from the sidebar to view details +

)} diff --git a/panel/src/components/communications/message-item.tsx b/panel/src/components/communications/message-item.tsx index 256e1dbc..6fa9b874 100644 --- a/panel/src/components/communications/message-item.tsx +++ b/panel/src/components/communications/message-item.tsx @@ -32,7 +32,9 @@ export function MessageItem({ message }: MessageItemProps) {
- {getAgentDisplayName(message.agent_id)} + + {getAgentDisplayName(message.agent_id)} + {formatTime(message.timestamp)} diff --git a/panel/src/components/company-goals/company-goals-card.tsx b/panel/src/components/company-goals/company-goals-card.tsx index b0311bc7..45834338 100644 --- a/panel/src/components/company-goals/company-goals-card.tsx +++ b/panel/src/components/company-goals/company-goals-card.tsx @@ -2,7 +2,10 @@ import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { companyGoalsApi, type CompanyGoalsUpdate } from "@/lib/api/company-goals"; +import { + companyGoalsApi, + type CompanyGoalsUpdate, +} from "@/lib/api/company-goals"; import { Card, CardContent, @@ -34,11 +37,12 @@ export function CompanyGoalsCard() { queryFn: companyGoalsApi.get, }); - const northStarVal = northStar ?? (goals?.north_star ?? ""); + const northStarVal = northStar ?? goals?.north_star ?? ""; const constraintsVal = constraints ?? (goals?.constraints ?? []).join("\n"); const objectivesVal = objectives ?? JSON.stringify(goals?.objectives ?? [], null, 2); - const policyVal = policy ?? JSON.stringify(goals?.operating_policy ?? {}, null, 2); + const policyVal = + policy ?? JSON.stringify(goals?.operating_policy ?? {}, null, 2); const saveMutation = useMutation({ mutationFn: (update: CompanyGoalsUpdate) => companyGoalsApi.update(update), @@ -99,8 +103,9 @@ export function CompanyGoalsCard() { Company Charter - The CEO-owned north star, objectives, constraints, and operating policy. - Injected into every agent's briefing so all work stays goal-aware. + The CEO-owned north star, objectives, constraints, and operating + policy. Injected into every agent's briefing so all work stays + goal-aware. @@ -150,7 +155,10 @@ export function CompanyGoalsCard() { placeholder='{"autonomy_level": "assisted", "monthly_budget_cap": 500}' />
- diff --git a/panel/src/components/dashboard/active-blockers-panel.tsx b/panel/src/components/dashboard/active-blockers-panel.tsx index 44fe8742..5907d994 100644 --- a/panel/src/components/dashboard/active-blockers-panel.tsx +++ b/panel/src/components/dashboard/active-blockers-panel.tsx @@ -25,7 +25,10 @@ function formatDuration(date: string): string { return `${diffDays}d`; } -export function ActiveBlockersPanel({ tasks, isLoading }: ActiveBlockersPanelProps) { +export function ActiveBlockersPanel({ + tasks, + isLoading, +}: ActiveBlockersPanelProps) { // Filter blocked tasks and sort by how long they've been blocked const blockedTasks = (tasks ?? []) .filter((t) => t.status === TaskStatus.BLOCKED) @@ -78,7 +81,8 @@ export function ActiveBlockersPanel({ tasks, isLoading }: ActiveBlockersPanelPro

{task.title}

- Blocked for {formatDuration(task.updated_at ?? task.created_at)} + Blocked for{" "} + {formatDuration(task.updated_at ?? task.created_at)}
diff --git a/panel/src/components/dashboard/activity-item.tsx b/panel/src/components/dashboard/activity-item.tsx index 211db451..d2cad854 100644 --- a/panel/src/components/dashboard/activity-item.tsx +++ b/panel/src/components/dashboard/activity-item.tsx @@ -1,6 +1,13 @@ "use client"; -import { CheckCircle, Play, Pause, AlertTriangle, User, Clock } from "lucide-react"; +import { + CheckCircle, + Play, + Pause, + AlertTriangle, + User, + Clock, +} from "lucide-react"; import { getAgentDisplayName } from "@/lib/agent-utils"; export interface Activity { @@ -59,8 +66,9 @@ export function ActivityItem({ activity }: ActivityItemProps) {
{icon}

- {getAgentDisplayName(activity.agent_id)} - {" "} + + {getAgentDisplayName(activity.agent_id)} + {" "} {label} {activity.task_title && ( <> diff --git a/panel/src/components/dashboard/auditor-alerts-panel.tsx b/panel/src/components/dashboard/auditor-alerts-panel.tsx index 5afc1a0e..f2dab707 100644 --- a/panel/src/components/dashboard/auditor-alerts-panel.tsx +++ b/panel/src/components/dashboard/auditor-alerts-panel.tsx @@ -14,9 +14,12 @@ interface AuditorAlertsPanelProps { } const severityColors: Record = { - [FlagSeverity.INFO]: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200", - [FlagSeverity.WARNING]: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200", - [FlagSeverity.URGENT]: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200", + [FlagSeverity.INFO]: + "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200", + [FlagSeverity.WARNING]: + "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200", + [FlagSeverity.URGENT]: + "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200", }; const severityEmoji: Record = { @@ -25,7 +28,10 @@ const severityEmoji: Record = { [FlagSeverity.URGENT]: "\uD83D\uDD34", }; -export function AuditorAlertsPanel({ alerts, isLoading }: AuditorAlertsPanelProps) { +export function AuditorAlertsPanel({ + alerts, + isLoading, +}: AuditorAlertsPanelProps) { // Filter to show only unresolved, sorted by severity const unresolvedAlerts = (alerts ?? []) .filter((a) => !a.resolved_at) @@ -74,8 +80,12 @@ export function AuditorAlertsPanel({ alerts, isLoading }: AuditorAlertsPanelProp {severityEmoji[alert.severity]}

- {alert.title} - + + {alert.title} + + {alert.severity}
diff --git a/panel/src/components/dashboard/ceo-approval-queue.tsx b/panel/src/components/dashboard/ceo-approval-queue.tsx index 16aac384..9c39ff71 100644 --- a/panel/src/components/dashboard/ceo-approval-queue.tsx +++ b/panel/src/components/dashboard/ceo-approval-queue.tsx @@ -3,7 +3,13 @@ import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { tasksApi } from "@/lib/api"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; @@ -17,7 +23,14 @@ import { } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; -import { CheckCircle2, XCircle, Clock, FileText, ExternalLink, Rocket } from "lucide-react"; +import { + CheckCircle2, + XCircle, + Clock, + FileText, + ExternalLink, + Rocket, +} from "lucide-react"; import Link from "next/link"; import { TaskStatus, Team, type Task } from "@/types"; import { toast } from "sonner"; @@ -29,7 +42,9 @@ interface CeoApprovalQueueProps { export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) { const queryClient = useQueryClient(); const [selectedTask, setSelectedTask] = useState(null); - const [actionType, setActionType] = useState<"approve" | "reject" | "start" | null>(null); + const [actionType, setActionType] = useState< + "approve" | "reject" | "start" | null + >(null); const [notes, setNotes] = useState(""); // Fetch tasks awaiting CEO approval (the end-of-work, pre-merge gate) @@ -68,7 +83,9 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) { closeDialog(); }, onError: (error) => { - toast.error(`Failed to approve: ${error instanceof Error ? error.message : "Unknown error"}`); + toast.error( + `Failed to approve: ${error instanceof Error ? error.message : "Unknown error"}`, + ); }, }); @@ -82,7 +99,9 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) { closeDialog(); }, onError: (error) => { - toast.error(`Failed to reject: ${error instanceof Error ? error.message : "Unknown error"}`); + toast.error( + `Failed to reject: ${error instanceof Error ? error.message : "Unknown error"}`, + ); }, }); @@ -96,7 +115,9 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) { closeDialog(); }, onError: (error) => { - toast.error(`Failed to approve & start: ${error instanceof Error ? error.message : "Unknown error"}`); + toast.error( + `Failed to approve & start: ${error instanceof Error ? error.message : "Unknown error"}`, + ); }, }); @@ -129,7 +150,10 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) { toast.error("Approval notes are required (>= 20 characters)"); return; } - approveStartMutation.mutate({ taskId: selectedTask.id, notes: notes.trim() }); + approveStartMutation.mutate({ + taskId: selectedTask.id, + notes: notes.trim(), + }); } else if (actionType === "reject") { if (!notes.trim()) { toast.error("Rejection reason is required"); @@ -140,13 +164,22 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) { }; const getPriorityBadge = (priority: number) => { - const variants: Record = { + const variants: Record< + number, + { + label: string; + variant: "default" | "secondary" | "destructive" | "outline"; + } + > = { 0: { label: "P0", variant: "destructive" }, 1: { label: "P1", variant: "destructive" }, 2: { label: "P2", variant: "secondary" }, 3: { label: "P3", variant: "outline" }, }; - const { label, variant } = variants[priority] || { label: `P${priority}`, variant: "outline" as const }; + const { label, variant } = variants[priority] || { + label: `P${priority}`, + variant: "outline" as const, + }; return {label}; }; @@ -280,7 +313,10 @@ export function CeoApprovalQueue({ className }: CeoApprovalQueueProps) { {/* Confirmation Dialog */} - closeDialog()}> + closeDialog()} + > diff --git a/panel/src/components/dashboard/command-center.tsx b/panel/src/components/dashboard/command-center.tsx index 4548bd1f..82767f48 100644 --- a/panel/src/components/dashboard/command-center.tsx +++ b/panel/src/components/dashboard/command-center.tsx @@ -1,6 +1,10 @@ "use client"; -import { useCeoOverview, useAuditorFlags, useRecentActivity } from "@/hooks/use-dashboard"; +import { + useCeoOverview, + useAuditorFlags, + useRecentActivity, +} from "@/hooks/use-dashboard"; import { useTasks } from "@/hooks/use-tasks"; import { TeamHealthCards } from "./team-health-cards"; import { KeyMetricsPanel } from "./key-metrics-panel"; @@ -20,10 +24,30 @@ import { RefreshCw, Settings, AlertCircle } from "lucide-react"; import Link from "next/link"; export function CommandCenter() { - const { data: overview, isLoading: loadingOverview, isError: errorOverview, refetch: refetchOverview } = useCeoOverview(); - const { data: flags, isLoading: loadingFlags, isError: errorFlags, refetch: refetchFlags } = useAuditorFlags({ resolved: false }); - const { data: tasks, isLoading: loadingTasks, isError: errorTasks, refetch: refetchTasks } = useTasks(); - const { data: activity, isLoading: loadingActivity, isError: errorActivity, refetch: refetchActivity } = useRecentActivity(24); + const { + data: overview, + isLoading: loadingOverview, + isError: errorOverview, + refetch: refetchOverview, + } = useCeoOverview(); + const { + data: flags, + isLoading: loadingFlags, + isError: errorFlags, + refetch: refetchFlags, + } = useAuditorFlags({ resolved: false }); + const { + data: tasks, + isLoading: loadingTasks, + isError: errorTasks, + refetch: refetchTasks, + } = useTasks(); + const { + data: activity, + isLoading: loadingActivity, + isError: errorActivity, + refetch: refetchActivity, + } = useRecentActivity(24); const hasError = errorOverview || errorFlags || errorTasks || errorActivity; @@ -39,7 +63,9 @@ export function CommandCenter() { {/* Header */}
-

RoboCo Command Center

+

+ RoboCo Command Center +

Complete visibility into all operations

diff --git a/panel/src/components/dashboard/key-metrics-panel.tsx b/panel/src/components/dashboard/key-metrics-panel.tsx index c215c712..2073ea00 100644 --- a/panel/src/components/dashboard/key-metrics-panel.tsx +++ b/panel/src/components/dashboard/key-metrics-panel.tsx @@ -2,7 +2,12 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; -import { TrendingUp, CheckCircle, BarChart3, AlertTriangle } from "lucide-react"; +import { + TrendingUp, + CheckCircle, + BarChart3, + AlertTriangle, +} from "lucide-react"; interface KeyMetricsProps { metrics: Record | undefined; @@ -71,11 +76,7 @@ export function KeyMetricsPanel({ metrics, isLoading }: KeyMetricsProps) { {m.label}
- {value != null - ? m.format - ? m.format(value) - : value - : "-"} + {value != null ? (m.format ? m.format(value) : value) : "-"}
); diff --git a/panel/src/components/dashboard/playbook-review-queue.tsx b/panel/src/components/dashboard/playbook-review-queue.tsx index 01170fa3..bd7d4570 100644 --- a/panel/src/components/dashboard/playbook-review-queue.tsx +++ b/panel/src/components/dashboard/playbook-review-queue.tsx @@ -51,7 +51,9 @@ export function PlaybookReviewQueue({ className }: { className?: string }) { toast.success("Playbook approved and indexed"); }, onError: (e) => - toast.error(`Approve failed: ${e instanceof Error ? e.message : "error"}`), + toast.error( + `Approve failed: ${e instanceof Error ? e.message : "error"}`, + ), }); const rejectMutation = useMutation({ diff --git a/panel/src/components/dashboard/pr-review-queue.tsx b/panel/src/components/dashboard/pr-review-queue.tsx index 53a57e2a..a1de8a99 100644 --- a/panel/src/components/dashboard/pr-review-queue.tsx +++ b/panel/src/components/dashboard/pr-review-queue.tsx @@ -21,7 +21,14 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { GitPullRequest, ExternalLink, Rocket, XCircle, FileText, Loader2 } from "lucide-react"; +import { + GitPullRequest, + ExternalLink, + Rocket, + XCircle, + FileText, + Loader2, +} from "lucide-react"; import Link from "next/link"; import { type Task } from "@/types"; import { toast } from "sonner"; @@ -142,14 +149,15 @@ export function PrReviewQueue({ className }: PrReviewQueueProps) {

No external PRs under review right now.

- Inbound external/fork PRs the reviewer has looked at show up here - for your supersede / dismiss call. + Inbound external/fork PRs the reviewer has looked at show up + here for your supersede / dismiss call.

)}
{items.map((task) => { - const awaiting = (task.status || "").toLowerCase() === "completed"; + const awaiting = + (task.status || "").toLowerCase() === "completed"; return (
- {action === "supersede" ? "Supersede this PR" : "Dismiss this review"} + {action === "supersede" + ? "Supersede this PR" + : "Dismiss this review"} {action === "supersede" @@ -263,7 +273,9 @@ export function PrReviewQueue({ className }: PrReviewQueueProps) { onClick={confirm} disabled={isPending} variant={action === "dismiss" ? "destructive" : "default"} - className={action === "supersede" ? "bg-blue-600 hover:bg-blue-700" : ""} + className={ + action === "supersede" ? "bg-blue-600 hover:bg-blue-700" : "" + } > {isPending ? "Processing..." diff --git a/panel/src/components/dashboard/quick-actions-bar.tsx b/panel/src/components/dashboard/quick-actions-bar.tsx index aad17eaf..73472edf 100644 --- a/panel/src/components/dashboard/quick-actions-bar.tsx +++ b/panel/src/components/dashboard/quick-actions-bar.tsx @@ -2,7 +2,14 @@ import { Button } from "@/components/ui/button"; import { CreateTaskDialog } from "@/components/tasks/create-task-dialog"; -import { Users, Megaphone, BookOpen, Shield, Sparkles, Bot } from "lucide-react"; +import { + Users, + Megaphone, + BookOpen, + Shield, + Sparkles, + Bot, +} from "lucide-react"; import Link from "next/link"; export function QuickActionsBar() { diff --git a/panel/src/components/dashboard/recent-activity-feed.tsx b/panel/src/components/dashboard/recent-activity-feed.tsx index a6c8c88a..45eaedd2 100644 --- a/panel/src/components/dashboard/recent-activity-feed.tsx +++ b/panel/src/components/dashboard/recent-activity-feed.tsx @@ -13,7 +13,10 @@ interface RecentActivityFeedProps { isLoading: boolean; } -export function RecentActivityFeed({ activities, isLoading }: RecentActivityFeedProps) { +export function RecentActivityFeed({ + activities, + isLoading, +}: RecentActivityFeedProps) { return ( diff --git a/panel/src/components/dashboard/team-health-card.tsx b/panel/src/components/dashboard/team-health-card.tsx index a82efe5c..54e5bf81 100644 --- a/panel/src/components/dashboard/team-health-card.tsx +++ b/panel/src/components/dashboard/team-health-card.tsx @@ -37,7 +37,9 @@ export function TeamHealthCard({ health }: TeamHealthCardProps) { Blocked
- 0 ? "text-red-600" : ""}`}> + 0 ? "text-red-600" : ""}`} + > {health.blocked_tasks}
@@ -55,7 +57,13 @@ export function TeamHealthCard({ health }: TeamHealthCardProps) { {health.blocked_ratio > 0 && (
0.3 ? "destructive" : health.blocked_ratio > 0.1 ? "secondary" : "outline"} + variant={ + health.blocked_ratio > 0.3 + ? "destructive" + : health.blocked_ratio > 0.1 + ? "secondary" + : "outline" + } > {Math.round(health.blocked_ratio * 100)}% blocked diff --git a/panel/src/components/dashboard/usage-overview-panel.tsx b/panel/src/components/dashboard/usage-overview-panel.tsx index 5cb77dd6..484f0dd4 100644 --- a/panel/src/components/dashboard/usage-overview-panel.tsx +++ b/panel/src/components/dashboard/usage-overview-panel.tsx @@ -152,10 +152,16 @@ export function UsageOverviewPanel() { ) } label="Trend vs prior period" - value={summary ? (trendUp ? "+" : "") + trendPct.toFixed(1) + "%" : "—"} + value={ + summary ? (trendUp ? "+" : "") + trendPct.toFixed(1) + "%" : "—" + } sub={ summary ? ( - + {trendUp ? "▲" : "▼"} ) : undefined diff --git a/panel/src/components/git/git-actions-panel.tsx b/panel/src/components/git/git-actions-panel.tsx index 1f2107d3..ad3eed3b 100644 --- a/panel/src/components/git/git-actions-panel.tsx +++ b/panel/src/components/git/git-actions-panel.tsx @@ -145,7 +145,10 @@ export function GitActionsPanel({ {/* Commit Action */} - +
@@ -200,7 +205,9 @@ export function GitActionsPanel({ onClick={handleCommit} disabled={!commitMessage.trim() || isCommitting} > - {isCommitting && } + {isCommitting && ( + + )} Commit @@ -276,7 +283,9 @@ export function GitActionsPanel({ onClick={handleCreatePR} disabled={!prTitle.trim() || isCreatingPR} > - {isCreatingPR && } + {isCreatingPR && ( + + )} Create PR @@ -284,12 +293,12 @@ export function GitActionsPanel({ {/* Merge PR Action */} - + -
- diff --git a/panel/src/components/git/git-branch-panel.tsx b/panel/src/components/git/git-branch-panel.tsx index 0ee5cc70..45e4cdd6 100644 --- a/panel/src/components/git/git-branch-panel.tsx +++ b/panel/src/components/git/git-branch-panel.tsx @@ -22,13 +22,7 @@ import { DialogFooter, DialogTrigger, } from "@/components/ui/dialog"; -import { - GitBranch, - Check, - Cloud, - Plus, - RefreshCw, -} from "lucide-react"; +import { GitBranch, Check, Cloud, Plus, RefreshCw } from "lucide-react"; interface GitBranchPanelProps { branches: GitBranchListResponse | undefined; @@ -146,7 +140,9 @@ export function GitBranchPanel({ onClick={handleCreateBranch} disabled={!taskId.trim() || isCreating} > - {isCreating && } + {isCreating && ( + + )} Create Branch @@ -166,7 +162,9 @@ export function GitBranchPanel({ {localBranches.map((branch) => (
))}
@@ -148,7 +153,9 @@ export function GitStatusPanel({ status, isLoading }: GitStatusPanelProps) { className="flex items-center gap-2 text-sm py-0.5 px-2 rounded hover:bg-muted" > - {file} + + {file} +
))}
diff --git a/panel/src/components/journals/agent-item.tsx b/panel/src/components/journals/agent-item.tsx index cc367987..d03a8b1c 100644 --- a/panel/src/components/journals/agent-item.tsx +++ b/panel/src/components/journals/agent-item.tsx @@ -28,7 +28,12 @@ function initialsFor(name: string): string { return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); } -export function AgentItem({ agent, isSelected, onClick, hasEntries }: AgentItemProps) { +export function AgentItem({ + agent, + isSelected, + onClick, + hasEntries, +}: AgentItemProps) { const name = getAgentDisplayName(agent.agent_id); const avatarTint = TEAM_AVATAR[agent.team ?? ""] ?? "bg-muted text-muted-foreground"; diff --git a/panel/src/components/journals/agent-list.tsx b/panel/src/components/journals/agent-list.tsx index ddfe5a41..b9f2cfd7 100644 --- a/panel/src/components/journals/agent-list.tsx +++ b/panel/src/components/journals/agent-list.tsx @@ -44,7 +44,10 @@ function groupByTeam(agents: Agent[]): Record { groups.marketing.push(agent); } else if (agent.team === Team.BOARD) { groups.board.push(agent); - } else if (agent.team === Team.MAIN_PM || agent.role === AgentRole.MAIN_PM) { + } else if ( + agent.team === Team.MAIN_PM || + agent.role === AgentRole.MAIN_PM + ) { groups.management.push(agent); } else if ( agent.role === AgentRole.PRODUCT_OWNER || diff --git a/panel/src/components/journals/entry-filter.tsx b/panel/src/components/journals/entry-filter.tsx index 71400ec4..0233638b 100644 --- a/panel/src/components/journals/entry-filter.tsx +++ b/panel/src/components/journals/entry-filter.tsx @@ -52,7 +52,10 @@ export function EntryFilter({ className="pl-9" />
- onTypeChange(v as JournalEntryType | "all")} + > diff --git a/panel/src/components/journals/growth-summary.tsx b/panel/src/components/journals/growth-summary.tsx index e61c4ae9..23937188 100644 --- a/panel/src/components/journals/growth-summary.tsx +++ b/panel/src/components/journals/growth-summary.tsx @@ -18,7 +18,11 @@ interface GrowthSummaryProps { isLoading: boolean; } -export function GrowthSummary({ journal, growth, isLoading }: GrowthSummaryProps) { +export function GrowthSummary({ + journal, + growth, + isLoading, +}: GrowthSummaryProps) { if (isLoading) { return ( @@ -107,7 +111,10 @@ export function GrowthSummary({ journal, growth, isLoading }: GrowthSummaryProps {Math.round(growth.struggle_resolution_rate * 100)}%
- +
)} @@ -116,7 +123,9 @@ export function GrowthSummary({ journal, growth, isLoading }: GrowthSummaryProps
Sentiment Trend - {growth.sentiment_trend} + + {growth.sentiment_trend} +
)} diff --git a/panel/src/components/journals/journal-view.tsx b/panel/src/components/journals/journal-view.tsx index 5705beb1..72ca6b42 100644 --- a/panel/src/components/journals/journal-view.tsx +++ b/panel/src/components/journals/journal-view.tsx @@ -2,7 +2,11 @@ import { useState, useMemo } from "react"; import { JournalEntryType, Agent } from "@/types"; -import { useJournalByAgent, useAgentJournalEntries, useMyGrowthMetrics } from "@/hooks/use-journals"; +import { + useJournalByAgent, + useAgentJournalEntries, + useMyGrowthMetrics, +} from "@/hooks/use-journals"; import { useTasks } from "@/hooks/use-tasks"; import { GrowthSummary } from "./growth-summary"; import { EntryCard } from "./entry-card"; @@ -28,8 +32,12 @@ export function JournalView({ onTaskChange, }: JournalViewProps) { // Use internal state if no external control provided - const [internalTypeFilter, setInternalTypeFilter] = useState("all"); - const [internalTaskFilter, setInternalTaskFilter] = useState(null); + const [internalTypeFilter, setInternalTypeFilter] = useState< + JournalEntryType | "all" + >("all"); + const [internalTaskFilter, setInternalTaskFilter] = useState( + null, + ); const [searchQuery, setSearchQuery] = useState(""); // Use external or internal state @@ -39,12 +47,14 @@ export function JournalView({ const handleTaskChange = onTaskChange ?? setInternalTaskFilter; // Fetch journal and entries for the selected agent using their slug - const { data: journal, isLoading: loadingJournal } = useJournalByAgent(agent.agent_id); + const { data: journal, isLoading: loadingJournal } = useJournalByAgent( + agent.agent_id, + ); // Fetch ALL entries (without task filter) to build the task dropdown options const { data: allEntries } = useAgentJournalEntries( agent.agent_id, - typeFilter !== "all" ? { entry_type: typeFilter } : {} + typeFilter !== "all" ? { entry_type: typeFilter } : {}, ); // Fetch filtered entries for display @@ -53,7 +63,7 @@ export function JournalView({ { ...(typeFilter !== "all" ? { entry_type: typeFilter } : {}), ...(taskFilter ? { task_id: taskFilter } : {}), - } + }, ); const { data: growth, isLoading: loadingGrowth } = useMyGrowthMetrics(); const { data: allTasks, isLoading: loadingTasks } = useTasks(); @@ -62,9 +72,7 @@ export function JournalView({ // This shows only tasks that the agent actually has journal entries about const tasks = useMemo(() => { const entryTaskIds = new Set( - (allEntries ?? []) - .filter((e) => e.task_id) - .map((e) => e.task_id!) + (allEntries ?? []).filter((e) => e.task_id).map((e) => e.task_id!), ); return (allTasks ?? []).filter((task) => entryTaskIds.has(task.id)); }, [allEntries, allTasks]); @@ -88,9 +96,12 @@ export function JournalView({
-

{getAgentDisplayName(agent.agent_id)}

+

+ {getAgentDisplayName(agent.agent_id)} +

- {agent.role.replace(/_/g, " ")} - {agent.team?.replace(/_/g, " ") || "N/A"} + {agent.role.replace(/_/g, " ")} -{" "} + {agent.team?.replace(/_/g, " ") || "N/A"}

diff --git a/panel/src/components/kanban/core/kanban-board.tsx b/panel/src/components/kanban/core/kanban-board.tsx index 777790b9..0b229a6c 100644 --- a/panel/src/components/kanban/core/kanban-board.tsx +++ b/panel/src/components/kanban/core/kanban-board.tsx @@ -57,13 +57,16 @@ export function KanbanBoard({ onTeamChange, showQaActions, }: KanbanBoardProps) { - const { data: tasks, isLoading, refetch } = useTasks( - teamFilter ? { team: teamFilter } : undefined - ); + const { + data: tasks, + isLoading, + refetch, + } = useTasks(teamFilter ? { team: teamFilter } : undefined); const lifecycle = useTaskLifecycle(); const updateTask = useUpdateTask(); const [activeTask, setActiveTask] = useState(null); - const [pendingNotesAction, setPendingNotesAction] = useState(null); + const [pendingNotesAction, setPendingNotesAction] = + useState(null); const [activeColumnIndex, setActiveColumnIndex] = useState(0); const sensors = useSensors( @@ -71,14 +74,17 @@ export function KanbanBoard({ activationConstraint: { distance: 8, }, - }) + }), ); // Group tasks by status - const tasksByStatus = columns.reduce((acc, col) => { - acc[col.status] = (tasks || []).filter((t) => t.status === col.status); - return acc; - }, {} as Record); + const tasksByStatus = columns.reduce( + (acc, col) => { + acc[col.status] = (tasks || []).filter((t) => t.status === col.status); + return acc; + }, + {} as Record, + ); const handleDragStart = (event: DragStartEvent) => { const taskId = event.active.id as string; @@ -142,7 +148,9 @@ export function KanbanBoard({ switch (task.status) { case TaskStatus.BACKLOG: // BACKLOG tasks need session before activation - PM only - toast.info("Backlog tasks must be activated by PM with a session"); + toast.info( + "Backlog tasks must be activated by PM with a session", + ); break; case TaskStatus.PENDING: await lifecycle.claim.mutateAsync(taskId); @@ -359,9 +367,7 @@ export function KanbanBoard({ ))}
- {activeTask ? ( - - ) : null} + {activeTask ? : null} diff --git a/panel/src/components/kanban/core/kanban-card.tsx b/panel/src/components/kanban/core/kanban-card.tsx index e78c3160..13478468 100644 --- a/panel/src/components/kanban/core/kanban-card.tsx +++ b/panel/src/components/kanban/core/kanban-card.tsx @@ -22,7 +22,16 @@ import { BlockedBadge } from "../shared/blocked-badge"; import { AssigneeAvatar } from "../shared/assignee-avatar"; import { AgentSelector } from "@/components/agents/agent-selector"; import { TaskTypeBadge } from "@/components/tasks/task-type-badge"; -import { GripVertical, ArrowRight, CheckCircle, XCircle, UserPlus, MessageSquare, Clock, Hash } from "lucide-react"; +import { + GripVertical, + ArrowRight, + CheckCircle, + XCircle, + UserPlus, + MessageSquare, + Clock, + Hash, +} from "lucide-react"; import { toast } from "sonner"; import Link from "next/link"; import { useDraggable } from "@dnd-kit/core"; @@ -35,13 +44,18 @@ interface KanbanCardProps { isDragging?: boolean; } -export function KanbanCard({ task, onAction, showQaActions, isDragging: isDraggingProp }: KanbanCardProps) { +export function KanbanCard({ + task, + onAction, + showQaActions, + isDragging: isDraggingProp, +}: KanbanCardProps) { const isBlocked = task.status === TaskStatus.BLOCKED; const isBacklog = task.status === TaskStatus.BACKLOG; const [assignOpen, setAssignOpen] = useState(false); const updateTask = useUpdateTask(); const hasSessions = task.sessions && task.sessions.length > 0; - const primarySession = task.sessions?.find(s => s.is_primary); + const primarySession = task.sessions?.find((s) => s.is_primary); const { attributes, @@ -83,7 +97,9 @@ export function KanbanCard({ task, onAction, showQaActions, isDragging: isDraggi className={`mb-2 hover:shadow-md transition-all ${ isDragging ? "opacity-50 rotate-2 scale-105 shadow-lg" : "" } ${isBlocked ? "border-red-300 bg-red-50/50 dark:bg-red-950/20" : ""} ${ - isBacklog ? "border-slate-300 bg-slate-50/50 dark:bg-slate-950/20 opacity-80" : "" + isBacklog + ? "border-slate-300 bg-slate-50/50 dark:bg-slate-950/20 opacity-80" + : "" }`} > @@ -91,7 +107,10 @@ export function KanbanCard({ task, onAction, showQaActions, isDragging: isDraggi

- + #{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 && ( - )} 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() { - - 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) => ( + + ))} +
-
- {response.suggested_followups.map((followup, index) => ( - - ))} -
-
- )} + )} {/* 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 */}
- CEO + + CEO +
Renzo
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) {
- + {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)}> -

{product.slug}

+

+ {product.slug} +

diff --git a/panel/src/components/projects/create-project-dialog.tsx b/panel/src/components/projects/create-project-dialog.tsx index 4d0a9db8..1b0adb79 100644 --- a/panel/src/components/projects/create-project-dialog.tsx +++ b/panel/src/components/projects/create-project-dialog.tsx @@ -64,7 +64,12 @@ export function CreateProjectDialog() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!formData.name || !formData.slug || !formData.git_url || !formData.assigned_cell) { + if ( + !formData.name || + !formData.slug || + !formData.git_url || + !formData.assigned_cell + ) { toast.error("Please fill in all required fields"); return; } @@ -97,7 +102,7 @@ export function CreateProjectDialog() { setShowAdvanced(false); } catch (error) { toast.error( - `Failed to create project: ${error instanceof Error ? error.message : "Unknown error"}` + `Failed to create project: ${error instanceof Error ? error.message : "Unknown error"}`, ); } }; @@ -136,7 +141,9 @@ export function CreateProjectDialog() { setFormData({ ...formData, slug: e.target.value })} + onChange={(e) => + setFormData({ ...formData, slug: e.target.value }) + } placeholder="roboco-api" pattern="^[a-z0-9-]+$" /> @@ -151,7 +158,9 @@ export function CreateProjectDialog() { setFormData({ ...formData, git_url: e.target.value })} + onChange={(e) => + setFormData({ ...formData, git_url: e.target.value }) + } placeholder="https://github.com/org/repo.git" />

@@ -169,11 +178,14 @@ export function CreateProjectDialog() { id="git_token" type="password" value={formData.git_token || ""} - onChange={(e) => setFormData({ ...formData, git_token: e.target.value })} + onChange={(e) => + setFormData({ ...formData, git_token: e.target.value }) + } placeholder="ghp_xxxxxxxxxxxx..." />

- Personal access token with repo access for clone, push, and PR operations + Personal access token with repo access for clone, push, and PR + operations

@@ -182,7 +194,9 @@ export function CreateProjectDialog() { setFormData({ ...formData, default_branch: e.target.value })} + onChange={(e) => + setFormData({ ...formData, default_branch: e.target.value }) + } placeholder="main" />
@@ -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.

)}
- - {/* Approve & Start → PENDING, straight to Main PM (skip the board) */} -

- 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}
- 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" /> - @@ -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) {
- +