# Panel — RoboCo Control Panel Map ## Purpose The Next.js 16 control panel (`panel/`, package `roboco-panel` v0.25.0) is the single operator UI for the human CEO: it drives intake/prompter chats, task/kanban management, agent observability, metrics, release approval, playbook curation, and feature-flag arming. It is served internally on port 3000 behind the nginx reverse proxy and talks to the orchestrator exclusively over relative `/api` + `/ws` URLs. ## Files / Structure | Path | Role | |---|---| | `panel/package.json` | Deps: Next 16.1.7 (held family pin), React 19.2, TanStack Query 5.90, Radix UI, Tailwind 4, zustand 5, recharts 3, dnd-kit 6/10, `@tanstack/react-virtual` 3.14.6 (kanban column windowing), axios, react-hook-form, zod 4 | | `panel/src/app/layout.tsx` | Root layout (providers, theme, fonts) | | `panel/src/app/(dashboard)/layout.tsx` | Dashboard shell: sidebar + header + connection status | | `panel/src/app/(dashboard)/overview/page.tsx` | Overview = `` | | `panel/src/app/(dashboard)/metrics/page.tsx` | Metrics page: Usage + Delivery tabs, summary/donut/charts | | `panel/src/app/(dashboard)/tasks/page.tsx` + `tasks/[taskId]/page.tsx` | Task list, now with top-level **List\|Kanban tabs** (#694/#688) sharing the page's URL filter state — List renders the existing `TaskFilters`+`TaskTable` unchanged, Kanban embeds the four kanban views (dev/qa/pr-review/pm) as nested sub-tabs, a controlled team filter shared bidirectionally with the List tab; + task detail (tabbed) | | `panel/src/app/(dashboard)/kanban/page.tsx` | **Redirects to the Tasks page's Kanban tab** (#694: `feat(panel): redirect /kanban to Tasks kanban tab, remove sidebar entry, swap bottom tab bar to Agents`) — the standalone kanban surface is gone from the sidebar; `KanbanBoard`'s dnd-kit drag-and-drop and mobile single-column nav are unchanged, just reached from `tasks?tab=kanban` now | | `panel/src/app/(dashboard)/projects/[projectId]/settings/page.tsx` | **Project settings full page** (Wave C, replacing the ~30-field `edit-project-dialog.tsx`, which is deleted): a card-per-concern grid — `identity-card` (name/slug/Forge/GitHub App), `git-auth-card` (PAT), `placement-card` (cell/team, protected branches), `environments-card` (env ladder), `cicd-commands-card`, `sandbox-card` (services + extensions), `budget-ops-card` (monthly budget, video engine toggle, CI-watch/dep-update/ci-watch toggles) — each with its own per-card save (`save-bar.tsx`), plus Conventions as a page-level tab at natural width instead of a cramped dialog tab | | `panel/src/app/(dashboard)/prompter/page.tsx` | Intake chat (single + MegaTask batch scope) | | `panel/src/app/(dashboard)/agents/page.tsx` | Agents hub: Fleet (roster/spawn/status, default) + Conversations + Journals tabs via `?tab=fleet\|conversations\|journals` — `AgentsFleetView` (fleet roster/spawn/activity), `A2AView` (org-wide switchboard/list + transcript + CEO reply composer + "New DM" dialog, live via `/ws/system` `a2a.message` frames — a pure lift of the old standalone `/a2a` page body), and `JournalsView` (per-agent reflection log — a pure lift of the old standalone `/journals` page body, wave 11) | | `panel/src/app/(dashboard)/a2a/page.tsx` | Redirect shim → `/agents?tab=conversations` (A2A folded into the Agents hub's Conversations tab, wave 9) — kept so old bookmarks/links still resolve | | `panel/src/app/(dashboard)/journals/page.tsx` | Redirect shim → `/agents?tab=journals` (Journals folded into the Agents hub's Journals tab, wave 11) — kept so old bookmarks/links still resolve; `journals/[entryId]/page.tsx` (entry detail) is unchanged at its own route, only its back-links now point at `/agents?tab=journals` | | `panel/src/app/(dashboard)/settings/page.tsx` + `settings/ai-providers/page.tsx` | Settings: feature flags, AI routing, transcript retention, self-hosted | | `panel/src/app/(dashboard)/workstation/page.tsx` | Workstation: Products + Projects merged as URL-param tabs (`?tab=products\|projects`, Products first); `products/page.tsx` and `projects/page.tsx` are now server-component redirects to it | | `panel/src/app/(dashboard)/{business,git,knowledge-base,auditor,work-sessions,notifications}/page.tsx` | Per-domain pages | | `panel/src/app/(auth)/login/page.tsx` | Cloud-auth login form (email/password → `useLogin` → `/auth/login`); only reachable/relevant once `proxy.ts` starts gating the `(dashboard)` group | | `panel/src/app/(tg)/layout.tsx` + `(tg)/tg/page.tsx` | Telegram Mini App cockpit at `/tg`: slim shell (`id="tg-shell"` theme-scoping hook, `--tg-viewport-stable-height` sizing, `next/font/local` Share Tech Mono loaded as `--font-share-tech`, `next/script` loads the Telegram WebApp bridge `afterInteractive`) + bootstrap page that resolves `window.Telegram.WebApp`, POSTs its `initData` to `/telegram/webapp-auth` unconditionally, then renders the tabbed cockpit (or an "Open from Telegram" wall for ANY non-dev-mock bridge with empty `initData` — closes a prod-only bug where a plain-browser visit still gets the WebApp bridge object but empty `initData`, 422ing into "Couldn't sign in") | | `panel/src/proxy.ts` | Next 16's rename of `middleware.ts`: probes `/auth/status` (docker-internal orchestrator URL, fails open to "off" on any error/timeout) and redirects to `/login` when cloud auth is on and no session cookie is present; matcher excludes `tg(?:/|$)` — the Mini App authenticates via Telegram `initData`, not the password cookie, so it must never be redirected to `/login` | | `panel/src/components/dashboard/` | Overview cards: command-center, key-metrics, release-proposal, playbook-review-queue, ceo-approval-queue, pr-review-queue, usage-overview, team-health, active-blockers, auditor-alerts, strategy-signals, quick-actions-card, quick-actions-registry, recent-activity, `x-post-queue.tsx`, `video-post-queue.tsx`, `roadmap-review-queue.tsx`, `project-badge.tsx` (shared project/repo badge — renders nothing when neither slug nor name is set; used by both the X and video post-queue rows so a multi-project CEO can tell drafts apart) | | `panel/src/components/metrics/` | delivery-tab, usage-time-series-chart, agent/team-usage-chart, model-usage-donut, sessions-table, `scorecards-tab.tsx` (calls `useAllMemberScorecards()` once instead of per-row `useMemberScorecard` — see `docs/map/metrics-observability.md`) | | `panel/src/components/kanban/{core,shared,views}/` | core: `kanban-board.tsx` (`tasksByStatus` grouping memoized, `handleAction` stabilized via `useCallback` + a `tasksRef`), `kanban-card.tsx` (wrapped in `memo`), `kanban-column.tsx` (windowed via `@tanstack/react-virtual`'s `useVirtualizer`, also `memo`-wrapped — the column itself stays the dnd-kit droppable target so windowing doesn't break drag targeting) + bypass-preconditions; views: dev/qa/pm/pr-review kanban | | `panel/src/components/prompter/` | intake-form, chat-messages, chat-composer, draft-proposal-card, batch-review-card, success-card, board-review-sent-card | | `panel/src/components/a2a/` | a2a-view.tsx (`A2AView` — the Conversations tab's full body, a pure lift of the old standalone `/a2a` page; owns the `?dm=` quick-action latch, see Gotchas), a2a-switchboard (org-chart pair cards, 45s pulse fade) + a2a-switchboard-utils (pairKey/grouping/pulse), a2a-pair-card, a2a-conversation-list (classic fallback), a2a-transcript, a2a-reply-composer (CEO chime-in on a watched conversation), a2a-new-dm-dialog (CEO opens a fresh 1:1, or preselects a validated `?dm=` deep-link target; exports `EXCLUDE_NON_DM_ROLES` so `tg-chat-tab.tsx`'s compose picker shares the same non-DM-capable-role exclusion instead of drifting out of sync), a2a-direct-composer (CEO's own thread, no task link required), a2a-utils | | `panel/src/components/tasks/` + `tasks/task-detail/` | task-table (row/card extraction into `memo`-wrapped `TaskTableRow`/`TaskTableCard`, `toggleExpand` stabilized via `useCallback` — pagination-based, not virtualized), create/edit-task-dialog, task-filters, acceptance-criteria-editor, dependency-selector, task-detail tabs (overview/plan/progress/commits/sessions/notes/dependencies/**findings**), `mobile-task-board.tsx` (read-only, grouped-by-status phone board for the `/tg` cockpit; gained `tasks`/`onTaskPress` props for demo-mode + the task detail sheet) | | `panel/src/components/tg/` | The `/tg` cockpit's tabs — now **5-tab** bottom nav (`today` default + `approvals`/`inbox`/`board`/`chat`): `tg-today-tab.tsx` (the default-opening "Today" brief — spend hero with 7-day sparkline, quick-action ring, needs-you banner, live fleet avatars, velocity bars), `tg-approvals-tab.tsx` (native card stack over `approvals/` — 7-file subdirectory: `use-approval-queue.ts`, `primary-action.tsx`, `reject-form.tsx`, `release-detail.tsx`, `x-post-detail.tsx`, `video-post-detail.tsx`, `roadmap-item-detail.tsx`), `tg-inbox-tab.tsx` (notifications + ack, `TgAvatar` sender tokens), `tg-board-tab.tsx` (wraps `mobile-task-board.tsx` + the new `tg-task-sheet.tsx` detail sheet), `tg-chat-tab.tsx` (WS-live via `/ws/system`, 10s poll fallback only while the socket is down), `ui.tsx` (shared visual-language primitives: `TgCircleAction`/`TgAvatar`/`TgSection`/`TgRow`/`TgRowIcon`/`TgStat`), `charts.tsx` (hand-rolled inline-SVG `Sparkline`/`DayBars`, no charting lib in the Mini App bundle), `tg-icons.tsx` (9-icon hand-drawn duotone set for hero surfaces, utility chrome stays lucide-react), `motion.tsx` (`useCountUp` numeral count-up hook + `TgSheet` bottom-sheet dialog wired to Telegram's native `BackButton`), `tg-task-sheet.tsx` (read-only task-detail bottom sheet: status/bounced-chip/ACs/up-to-5-open-findings/PR link) | | `panel/src/lib/telegram/webapp.ts` | Typed wrapper over the global `window.Telegram.WebApp` (`ready`/`expand`/`initData`, `TelegramThemeParams`/`MainButton`/`BackButton`/`HapticFeedback` interfaces); `waitForTelegramWebApp` polls (100ms, 1.5s timeout) for the CDN script since it loads `afterInteractive`; `createDevMockWebApp`/`isDevMockWebApp` back the `/tg?demo=1` dev-browser fallback; null-safe `haptics` const (no-ops outside Telegram) | | `panel/src/lib/telegram/{hooks.tsx,theme.ts,demo.ts,demo-data.ts}` | `hooks.tsx`: `TgWebAppProvider`/`useTgWebApp` context + `useMainButton`/`useBackButton` (declarative wrappers over Telegram's native buttons — no consumer touches `window.Telegram` directly). `theme.ts`: `applyTelegramTheme` maps `themeParams` → shadcn CSS vars, hex-only trust boundary, scoped to `#tg-shell` only (the desktop dashboard is untouched); `startTelegramThemeSync` re-applies on the bridge's `themeChanged` event. `demo.ts`/`demo-data.ts`: `isTgDemoMode()` (dev-only, `?demo=1`, dead-code-eliminated in prod) gates dynamically-imported `DEMO_TASKS`/`DEMO_NOTIFICATIONS`/`DEMO_RELEASE`/`DEMO_X_POSTS`/`DEMO_VIDEO_POSTS`/`DEMO_ROADMAP`/`DEMO_TODAY` fixtures | | `panel/src/components/settings/` | feature-flags-card, ai-routing-card, transcript-retention-card, self-hosted-section, `x-credentials-card.tsx` (write-only OAuth 1.0a secrets, mounted in `settings/page.tsx`) | | `panel/src/components/conventions/conventions-tab.tsx` | Per-project architecture map + health — now a page-level tab on `/projects/[id]/settings` (Wave C; was a cramped tab inside the deleted edit-project dialog) | | `panel/src/components/projects/settings/*.tsx` | Wave C card-per-concern settings surfaces (`identity-card`, `git-auth-card`, `placement-card`, `environments-card`, `cicd-commands-card`, `sandbox-card`, `budget-ops-card`, `save-bar.tsx`) backing `/projects/[id]/settings`, replacing the single ~1061-line `edit-project-dialog.tsx` (deleted) | | `panel/src/components/projects/quick-edit-project-dialog.tsx` | Wave C: slim replacement for the old kitchen-sink edit dialog — name/cell/active only; the project list's Edit action now routes to the full settings page instead | | `panel/src/components/layout/command-palette.tsx` + `hooks/use-command-palette.ts` | Wave C (#694/#670): global Cmd+K palette — Radix Dialog + combobox searching tasks/agents/projects/pages, `localStorage` recents (`lib/command-palette-recents.ts`, key `roboco-cmd-recents`), fuzzy match (`lib/fuzzy-match.ts`), keyboard nav; mounted globally in the dashboard layout, wired to the header search trigger via `useUIStore.setCommandPaletteOpen` (`components/layout/header.tsx`) | | `panel/src/components/ui/collapsible-section.tsx` | Wave C dialog-standardization: the single sectioned-disclosure primitive, moved from `tasks/task-detail/` to `ui/` — task dialogs' raw `Collapsible` and create-project's ad-hoc `showAdvanced` both converge onto it. Every hand-rolled dialog footer across the panel became `DialogFooter`; dialog widths collapsed from ten ad-hoc classes to three named sizes (deliberate outliers annotated). No behavioral change. | | `panel/src/components/projects/`, `products/`, `agents/`, `business/`, `auditor/`, `knowledge-base/`, `git/`, `journals/`, `work-sessions/`, `notifications/`, `rate-limit/`, `layout/`, `ui/` | Per-domain component groups (`projects/` and `products/` each export a `*-view.tsx` consumed by `workstation/page.tsx`, plus a `*-card-grid.tsx` reusing the sibling table's exported badge renderers; `agents/` similarly exports `agents-fleet-view.tsx` (`AgentsFleetView`) consumed by `agents/page.tsx`'s Fleet tab, plus `agent-card.tsx`'s DM quick-action button; `journals/` similarly exports `journals-view.tsx` (`JournalsView`) consumed by `agents/page.tsx`'s Journals tab); `ui/` = Radix-based primitives (dialog, table, tabs, select, switch, required-notes-dialog, sonner toaster, markdown, `collapsible-section`) | | `panel/src/hooks/use-websocket.ts` | Shared `useWebSocket(path, handlers?, isSystem?)` hook (auto-reconnect, heartbeat) | | `panel/src/hooks/use-{tasks,agents,projects,products,usage,prompter,secretary,dashboard,git,journals,notifications,knowledge-base,observability,work-sessions,providers,rate-limit-{sync,websocket}}.ts` | TanStack Query + zustand data hooks | | `panel/src/hooks/use-a2a-live.ts` | `useA2AConversations` / `useA2AAdminPairs` / `useA2AMessages` (TanStack Query over `a2aApi`) + `useReplyAsCeo` / `useCreateCeoConversation` / `useSendCeoMessage` mutations; `a2aLiveKeys` query-key namespace | | `panel/src/lib/api/*.ts` | Per-domain axios clients (`client.ts` shared instance; `release.ts`, `playbooks.ts`, `prompter-live.ts`, `tasks.ts`, `settings.ts`, `usage.ts`, `cockpit.ts`, `a2a.ts`, `auth.ts` (status/login/logout), `x.ts` (post queue + credentials), `roadmap.ts` (cycles + item approve/reject), …) | | `panel/src/lib/websocket/connection.ts` | `WebSocketConnection` class + `getWebSocketUrl` | | `panel/src/store/{rate-limit-store,notifications-store,usage-store,ui-store}.ts` + `lib/stores/` | zustand stores (`lib/stores/` now exports `scroll-restoration-store` only; `ui-store` is sole-canonical under `src/store/`) | | `panel/src/types/` | Shared TS types (index, rate-limits, git) | | `panel/src/lib/{constants,utils,agent-definitions,agent-utils,repo-url,mock-data}.ts` | `API_URL="/api"`, `WS_URL="/ws"`, `CEO_AGENT_ID/ROLE`, helpers | | `panel/vitest.config.ts`, `panel/src/test/setup.ts`, `**/__tests__/` | Vitest + jsdom; coverage via @vitest/coverage-v8 | ## Key Surfaces | Surface | File | What it does | |---|---|---| | Overview / Command Center | `components/dashboard/command-center.tsx` | Composes key-metrics, usage-overview, ceo-approval-queue, pr-review-queue, release-proposal, playbook-review-queue, active-blockers, auditor-alerts, strategy-signals, team-health, recent-activity, quick-actions | | Metrics — Usage | `app/(dashboard)/metrics/page.tsx` + `components/metrics/*` | Summary, time-series, agent/team/model donut, sessions table, projection, cache efficiency; live via `/ws/system` USAGE_SNAPSHOT | | Metrics — Delivery | `components/metrics/delivery-tab.tsx` | Cycle-time, bottlenecks, rework (now incl. "PM rejects"/"CEO rejects" columns alongside QA/PR-fail counts — see `docs/map/review-findings.md`), scorecards (read-only `/dashboard/metrics/*`) | | Findings tab | `components/tasks/task-detail/tab-findings.tsx` | Per-round revision-findings ledger view: origin/status summary badges, severity/status badges, file:line, collapsible evidence, truncation footer; backed by `GET /api/tasks/{id}/findings` via `useTaskFindings`. A `bounced xN` chip (`task.revision_count`) surfaces on the task header alongside it. | | Release Proposal | `components/dashboard/release-proposal-card.tsx` | CEO approve/reject-with-changes on held `release_manager` proposal; fail-closed executor; hidden on 404, retry on real error | | Playbook Review Queue | `components/dashboard/playbook-review-queue.tsx` | Auditor/CEO approve/reject drafted playbooks; hidden when no drafts | | CEO Approval Queue | `components/dashboard/ceo-approval-queue.tsx` | Tasks in `awaiting_ceo_approval` awaiting CEO verdict | | PR Review Queue | `components/dashboard/pr-review-queue.tsx` | Inbound external/fork PRs + in-path gate PRs for the reviewer | | X Post Queue | `components/dashboard/x-post-queue.tsx` | CEO edit/approve (posts to X)/reject on held release-post + mention-reply drafts; hidden when empty | | Roadmap Review Queue | `components/dashboard/roadmap-review-queue.tsx` | CEO per-item approve (materializes BACKLOG task)/reject on the Product Owner's held roadmap cycle; hidden until authored | | Feature Flags | `components/settings/feature-flags-card.tsx` | Toggles persisted to settings store; takes effect on next backend restart | | Intake / MegaTask | `app/(dashboard)/prompter/page.tsx` + `components/prompter/*` | Live SSE chat with spawned Claude/Grok intake agent; single-project, product, or multi-project (`project_ids`) MegaTask → `propose_batch` → `confirm-batch` | | Agents — Fleet | `app/(dashboard)/agents/page.tsx` (`?tab=fleet`, default) + `components/agents/agents-fleet-view.tsx` + `components/agents/*` | Full agent roster grouped into Leadership/Backend/Frontend/UX-UI/Support grids: live state, spawn/stop controls, activity-stream link, token usage. Each card's DM button (hidden for `EXCLUDE_NON_DM_ROLES`) deep-links `/agents?tab=conversations&dm=` into the Conversations tab | | Agents — Conversations (switchboard + reply + New DM) | `app/(dashboard)/agents/page.tsx` (`?tab=conversations`) + `components/a2a/a2a-view.tsx` + `components/a2a/*` | CEO watches every agent-to-agent conversation live: default org-chart switchboard (pair cards grouped by cell/PM-chain/board, pulsing on fresh `a2a.message` frames) or the classic conversation list; drill-in shows the transcript + a reply composer that lets the CEO chime into a watched thread as itself (task-linked conversations only). "New DM" opens a fresh CEO-owned 1:1 with any DM-capable agent (no task link needed) — either from the dialog's own picker or preselected from a validated `?dm=` deep link off the Fleet tab; the recipient is woken via the a2a_request dispatch path if offline, and the CEO's own threads render with `A2ADirectComposer` instead of the reply composer. `/a2a` redirects here | | Agents — Journals | `app/(dashboard)/agents/page.tsx` (`?tab=journals`) + `components/journals/journals-view.tsx` + `components/journals/*` | Per-agent reflection log lifted into the Agents hub's third tab: agent picker (left, local search state seeded from the last saved query) + a type/task-filterable entry list (right, `JournalView`); each task-linked entry carries a Task badge + copy-UUID button. Entry detail still lives at `/journals/{entryId}`, its back-links now pointing at `/agents?tab=journals`. `/journals` redirects here | | Workstation | `app/(dashboard)/workstation/page.tsx` + `components/{products,projects}/*-view.tsx` | Products + Projects as one sidebar entry, tab-switched via `?tab=`; each surface has a Cards\|Table view toggle (default Cards, persisted per-surface via `ui-store`'s `productsView`/`projectsView`) with client-side name/cell(-count) sorting in card view; Projects' q/cell/inactive filters are local `useState`, not URL params (scroll-bounce prevention) | | Project Settings / Conventions | `app/(dashboard)/projects/[projectId]/settings/page.tsx` + `components/projects/settings/*.tsx` + `components/conventions/conventions-tab.tsx` | Wave C full-page project settings (card-per-concern, per-card save) — was the ~30-field `edit-project-dialog.tsx` (deleted). The list's Edit action routes here; a slim `quick-edit-project-dialog.tsx` (name/cell/active) covers the fast path. Conventions is a page-level tab: per-project `.roboco/conventions.yml` map + health, Save/Restore via PR. The "Forge" `