diff --git a/docs/map/_complete_map.md b/docs/map/_complete_map.md
index 6669a08c..270b9a2d 100644
--- a/docs/map/_complete_map.md
+++ b/docs/map/_complete_map.md
@@ -8652,7 +8652,7 @@ The Next.js 16 control panel (`panel/`, package `roboco-panel` v0.14.0) is the s
| `panel/src/app/(dashboard)/{agents,business,journals,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/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 |
-| `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, recent-activity, `x-post-queue.tsx`, `roadmap-review-queue.tsx` |
+| `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`, `roadmap-review-queue.tsx` |
| `panel/src/components/metrics/` | delivery-tab, usage-time-series-chart, agent/team-usage-chart, model-usage-donut, sessions-table |
| `panel/src/components/kanban/{core,shared,views}/` | core: kanban-board/column/card + 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 |
@@ -8729,6 +8729,8 @@ The Next.js 16 control panel (`panel/`, package `roboco-panel` v0.14.0) is the s
| `XCredentialsCard` | comp | `components/settings/x-credentials-card.tsx` | Write-only entry of the 4 OAuth 1.0a secrets; set-all-4 or clear-all-4 |
| `RequiredNotesDialog` | comp | `components/ui/required-notes-dialog.tsx` | Reusable notes-gated confirm; submit disabled on empty/whitespace |
| `CommandCenter` | comp | `components/dashboard/command-center.tsx` | Overview page body; composes all dashboard cards |
+| `QUICK_ACTIONS_REGISTRY` | const | `components/dashboard/quick-actions-registry.ts` | 23-action catalog (id/label/icon/href/tip) derived from the sidebar route surface + tab-parameterized deep links; `DEFAULT_QUICK_ACTION_IDS` absorbs every legacy `QuickActionsBar` destination; `resolveQuickActions`/`isKnownQuickActionId` drop stale persisted ids |
+| `QuickActionsCard` / `QuickActionsCustomizeDialog` | comp | `components/dashboard/quick-actions-card.tsx` | Chosen-order icon+label grid off `useUIStore.quickActionIds` (empty-state message when none selected); pencil-icon dialog toggles/reorders via checkbox + up/down arrows and resets to defaults, persisted through `setQuickActionIds`/`resetQuickActionIds` |
| `DeliveryTabContent` | comp | `components/metrics/delivery-tab.tsx` | Cycle-time/bottleneck/rework/scorecard panels |
| `GitActionsPanel` | comp | `components/git/git-actions-panel.tsx` | Commit/push/PR/rebase actions + destructive-confirm "Clean Up Stale Branches" (`AlertDialog`) |
| `useCleanupBranches` / `handleCleanupBranches` | hook | `hooks/use-git.ts` / `hooks/use-git-browser.ts` | Mutation over `POST /git/branches/cleanup`; the browser hook tracks a per-project cursor ref so a repeat click resumes a truncated sweep instead of re-scanning the first window |
@@ -8792,7 +8794,7 @@ panel/ (Next.js 16, package roboco-panel v0.14.0)
│ ├── workstation/page.tsx (Products + Projects tabs; projects/, products/ page.tsx now redirect here)
│ └── {agents,business,journals,git,knowledge-base,auditor,work-sessions,notifications}/page.tsx
├── src/components/
-│ ├── dashboard/ (command-center, key-metrics, release-proposal, playbook-review-queue, ceo-approval-queue, pr-review-queue, x-post-queue, roadmap-review-queue, usage-overview, team-health, active-blockers, auditor-alerts, strategy-signals, quick-actions, recent-activity)
+│ ├── dashboard/ (command-center, key-metrics, release-proposal, playbook-review-queue, ceo-approval-queue, pr-review-queue, x-post-queue, roadmap-review-queue, usage-overview, team-health, active-blockers, auditor-alerts, strategy-signals, quick-actions-card, quick-actions-registry, recent-activity)
│ ├── metrics/ (delivery-tab, usage-time-series-chart, agent/team-usage-chart, model-usage-donut, sessions-table)
│ ├── kanban/
│ │ ├── core/ (kanban-board/column/card + bypass-preconditions)
@@ -8912,6 +8914,7 @@ Deliberately **not** on this card (compose/env-coupled, unsafe for a runtime tog
> - (open PR #548, branch `feature/wave-2-hygiene-charts`, 2026-07-17) Wave 2 hygiene + charts: `/work-sessions` route now redirects to `/git?tab=sessions` (moved under Git as a "Work Sessions" tab, `git-page.tsx` gains a `Tabs`); `GitActionsPanel` gains a confirm-gated "Clean Up Stale Branches" button (`useCleanupBranches`, cursor-resumable); `WorkSessionsView`'s filters moved from URL params to local state (ScrollRestoration bounce fix); new `SessionTrendChart` / `CostTrendChart` / `SpendTrendChart`.
> - `dd4cb7f1` Wave 4: Workstation page (PR #549, 2026-07-17) — Products + Projects merged into one `/workstation?tab=` page (Products first); content extracted byte-faithfully into `components/products/products-view.tsx` + `components/projects/projects-view.tsx`; `products/page.tsx` + `projects/page.tsx` become redirect shims; the two sidebar entries collapse into one "Workstation" entry (`Briefcase` icon); `task-metadata.tsx`'s project-card link retargets to `/workstation?tab=projects`; `ProjectsView`'s q/cell/inactive filters move from URL params to local `useState` (scroll-bounce prevention, trades away shareable filtered-view links).
> - `49a87c2b`+`9a33efba` (2026-07-18, PR #556, Workstation card grids) — new `product-card-grid.tsx`/`project-card-grid.tsx` (same intrinsic `grid-cols-[repeat(auto-fill,minmax(17rem,1fr))]` sizing as `agent-grid.tsx`), reusing `CellsList`/`ProgressCell` (newly exported from `product-table.tsx`) and `getExternalUrl`/badge renderers (newly exported from `project-table.tsx`); each Workstation surface gains a Cards\|Table toggle (default Cards, persisted per-surface via `ui-store`'s new `productsView`/`projectsView`) plus a name/cell(-count) sort control that only applies in card view — the table keeps its own unsorted render. `sortProducts`/`sortProjects` (both exported for direct unit tests) use a direction multiplier rather than sort-then-reverse, since reversing also flips the relative order of ties, and `sortProjects`' cell-key comparator falls back to the raw `assigned_cell` value for a backend `Team` outside the panel's `teamLabels` map instead of crashing; the fix commit tightened both to this shape after adversarial review caught the initial sort-then-reverse cut.
+> - `97306fe7`+`151d6e0f` (2026-07-18, PR #557, customizable Quick Actions) — the hardcoded `QuickActionsBar` (deleted) is replaced by `QuickActionsCard` (`components/dashboard/quick-actions-card.tsx`), backed by a new 23-action `QUICK_ACTIONS_REGISTRY` (`components/dashboard/quick-actions-registry.ts`, id/label/icon/href/tip per entry, tab-parameterized deep links e.g. `/git?tab=sessions`, `/metrics?tab=delivery`); `DEFAULT_QUICK_ACTION_IDS` absorbs every legacy bar destination (secretary/journals/auditor) so the swap drops nothing from a fresh install's default view. Selection persists as `quickActionIds` on `useUIStore` (new key in `partialize`, contract-tested); `resolveQuickActions`/`isKnownQuickActionId` drop stale persisted ids instead of crashing. A pencil-icon `QuickActionsCustomizeDialog` (inline in `quick-actions-card.tsx`) lets the CEO show/hide + reorder (up/down arrows) + reset to defaults; the card shows an empty-state message when every action is hidden. `command-center.tsx` and its test swap `QuickActionsBar`/`QuickActionsBarStub` references for `QuickActionsCard`/`QuickActionsCardStub`.
## Regression Risks
diff --git a/docs/map/panel.md b/docs/map/panel.md
index 4eedce03..902d2aaf 100644
--- a/docs/map/panel.md
+++ b/docs/map/panel.md
@@ -22,7 +22,7 @@ The Next.js 16 control panel (`panel/`, package `roboco-panel` v0.14.0) is the s
| `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 (no sidebar/header, `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" / error state) |
| `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, recent-activity, `x-post-queue.tsx`, `roadmap-review-queue.tsx` |
+| `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`, `roadmap-review-queue.tsx` |
| `panel/src/components/metrics/` | delivery-tab, usage-time-series-chart, agent/team-usage-chart, model-usage-donut, sessions-table |
| `panel/src/components/kanban/{core,shared,views}/` | core: kanban-board/column/card + 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 |
@@ -102,6 +102,8 @@ The Next.js 16 control panel (`panel/`, package `roboco-panel` v0.14.0) is the s
| `XCredentialsCard` | comp | `components/settings/x-credentials-card.tsx` | Write-only entry of the 4 OAuth 1.0a secrets; set-all-4 or clear-all-4 |
| `RequiredNotesDialog` | comp | `components/ui/required-notes-dialog.tsx` | Reusable notes-gated confirm; submit disabled on empty/whitespace |
| `CommandCenter` | comp | `components/dashboard/command-center.tsx` | Overview page body; composes all dashboard cards |
+| `QUICK_ACTIONS_REGISTRY` | const | `components/dashboard/quick-actions-registry.ts` | 23-action catalog (id/label/icon/href/tip) derived from the sidebar route surface + tab-parameterized deep links; `DEFAULT_QUICK_ACTION_IDS` absorbs every legacy `QuickActionsBar` destination; `resolveQuickActions`/`isKnownQuickActionId` drop stale persisted ids |
+| `QuickActionsCard` / `QuickActionsCustomizeDialog` | comp | `components/dashboard/quick-actions-card.tsx` | Chosen-order icon+label grid off `useUIStore.quickActionIds` (empty-state message when none selected); pencil-icon dialog toggles/reorders via checkbox + up/down arrows and resets to defaults, persisted through `setQuickActionIds`/`resetQuickActionIds` |
| `DeliveryTabContent` | comp | `components/metrics/delivery-tab.tsx` | Cycle-time/bottleneck/rework/scorecard panels |
| `GitActionsPanel` | comp | `components/git/git-actions-panel.tsx` | Commit/push/PR/rebase actions + destructive-confirm "Clean Up Stale Branches" (`AlertDialog`) |
| `useCleanupBranches` / `handleCleanupBranches` | hook | `hooks/use-git.ts` / `hooks/use-git-browser.ts` | Mutation over `POST /git/branches/cleanup`; the browser hook tracks a per-project cursor ref so a repeat click resumes a truncated sweep instead of re-scanning the first window |
@@ -168,7 +170,7 @@ panel/ (Next.js 16, package roboco-panel v0.14.0)
│ ├── workstation/page.tsx (Products + Projects tabs; projects/, products/ page.tsx now redirect here)
│ └── {agents,business,journals,git,knowledge-base,auditor,work-sessions,notifications}/page.tsx
├── src/components/
-│ ├── dashboard/ (command-center, key-metrics, release-proposal, playbook-review-queue, ceo-approval-queue, pr-review-queue, x-post-queue, roadmap-review-queue, usage-overview, team-health, active-blockers, auditor-alerts, strategy-signals, quick-actions, recent-activity)
+│ ├── dashboard/ (command-center, key-metrics, release-proposal, playbook-review-queue, ceo-approval-queue, pr-review-queue, x-post-queue, roadmap-review-queue, usage-overview, team-health, active-blockers, auditor-alerts, strategy-signals, quick-actions-card, quick-actions-registry, recent-activity)
│ ├── metrics/ (delivery-tab, usage-time-series-chart, agent/team-usage-chart, model-usage-donut, sessions-table)
│ ├── kanban/
│ │ ├── core/ (kanban-board/column/card + bypass-preconditions)
@@ -293,6 +295,7 @@ Deliberately **not** on this card (compose/env-coupled, unsafe for a runtime tog
> - `dd4cb7f1` Wave 4: Workstation page (PR #549, 2026-07-17) — Products + Projects merged into one `/workstation?tab=` page (Products first); content extracted byte-faithfully into `components/products/products-view.tsx` + `components/projects/projects-view.tsx`; `products/page.tsx` + `projects/page.tsx` become redirect shims; the two sidebar entries collapse into one "Workstation" entry (`Briefcase` icon); `task-metadata.tsx`'s project-card link retargets to `/workstation?tab=projects`; `ProjectsView`'s q/cell/inactive filters move from URL params to local `useState` (scroll-bounce prevention, trades away shareable filtered-view links).
> - `e16fb634`+`8d727785` (2026-07-18, PR #554, Telegram V3 Mini App) — new `(tg)` route group (`layout.tsx` + `tg/page.tsx`) and `components/tg/{tg-tab-bar,tg-approvals-tab,tg-inbox-tab,tg-board-tab,tg-chat-tab}.tsx`; new `components/tasks/mobile-task-board.tsx` (read-only, grouped-by-status) and `lib/telegram/webapp.ts` (WebApp bridge + `waitForTelegramWebApp` poll); `a2a-new-dm-dialog.tsx` exports `EXCLUDE_NON_DM_ROLES` for `tg-chat-tab.tsx` to reuse; `use-a2a-live.ts`'s `useA2AMessages` gains an optional `refetchInterval` (the cockpit polls instead of using WS); `proxy.ts` matcher excludes `tg(?:/|$)` (anchored in the fix commit — see Gotchas). Backend companion: `POST /api/telegram/webapp-auth` — see `docs/map/api-routes-schemas.md`.
> - `49a87c2b`+`9a33efba` (2026-07-18, PR #556, Workstation card grids) — new `product-card-grid.tsx`/`project-card-grid.tsx` (same intrinsic `grid-cols-[repeat(auto-fill,minmax(17rem,1fr))]` sizing as `agent-grid.tsx`), reusing `CellsList`/`ProgressCell` (newly exported from `product-table.tsx`) and `getExternalUrl`/badge renderers (newly exported from `project-table.tsx`); each Workstation surface gains a Cards\|Table toggle (default Cards, persisted per-surface via `ui-store`'s new `productsView`/`projectsView`) plus a name/cell(-count) sort control that only applies in card view — the table keeps its own unsorted render. `sortProducts`/`sortProjects` (both exported for direct unit tests) use a direction multiplier rather than sort-then-reverse, since reversing also flips the relative order of ties, and `sortProjects`' cell-key comparator falls back to the raw `assigned_cell` value for a backend `Team` outside the panel's `teamLabels` map instead of crashing; the fix commit tightened both to this shape after adversarial review caught the initial sort-then-reverse cut.
+> - `97306fe7`+`151d6e0f` (2026-07-18, PR #557, customizable Quick Actions) — the hardcoded `QuickActionsBar` (deleted) is replaced by `QuickActionsCard` (`components/dashboard/quick-actions-card.tsx`), backed by a new 23-action `QUICK_ACTIONS_REGISTRY` (`components/dashboard/quick-actions-registry.ts`, id/label/icon/href/tip per entry, tab-parameterized deep links e.g. `/git?tab=sessions`, `/metrics?tab=delivery`); `DEFAULT_QUICK_ACTION_IDS` absorbs every legacy bar destination (secretary/journals/auditor) so the swap drops nothing from a fresh install's default view. Selection persists as `quickActionIds` on `useUIStore` (new key in `partialize`, contract-tested); `resolveQuickActions`/`isKnownQuickActionId` drop stale persisted ids instead of crashing. A pencil-icon `QuickActionsCustomizeDialog` (inline in `quick-actions-card.tsx`) lets the CEO show/hide + reorder (up/down arrows) + reset to defaults; the card shows an empty-state message when every action is hidden. `command-center.tsx` and its test swap `QuickActionsBar`/`QuickActionsBarStub` references for `QuickActionsCard`/`QuickActionsCardStub`.
## Regression Risks
diff --git a/panel/src/components/dashboard/__tests__/command-center.test.tsx b/panel/src/components/dashboard/__tests__/command-center.test.tsx
index ca09b5c1..d03cfa9b 100644
--- a/panel/src/components/dashboard/__tests__/command-center.test.tsx
+++ b/panel/src/components/dashboard/__tests__/command-center.test.tsx
@@ -55,8 +55,8 @@ vi.mock("../active-blockers-panel", () => ({
vi.mock("../recent-activity-feed", () => ({
RecentActivityFeed: () =>
- );
-}
diff --git a/panel/src/components/dashboard/quick-actions-card.tsx b/panel/src/components/dashboard/quick-actions-card.tsx
new file mode 100644
index 00000000..f6c66df4
--- /dev/null
+++ b/panel/src/components/dashboard/quick-actions-card.tsx
@@ -0,0 +1,214 @@
+"use client";
+
+import { useState } from "react";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+import { HelpTip } from "@/components/ui/help-tip";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { ArrowDown, ArrowUp, Pencil, RotateCcw } from "lucide-react";
+import { useUIStore } from "@/store";
+import {
+ QUICK_ACTIONS_REGISTRY,
+ resolveQuickActions,
+} from "./quick-actions-registry";
+
+// Per-user honesty: the panel is a single-operator surface today. This
+// customization is persisted per-browser via the existing localStorage-backed
+// useUIStore — different browsers/profiles keep their own chosen set, which
+// IS the "different people need different quick access buttons" mechanism
+// for now. No server-side per-user storage is built.
+
+/**
+ * Compact icon+label grid of the CEO's chosen quick actions, in their chosen
+ * order — replaces the old hardcoded QuickActionsBar. A gear/pencil affordance
+ * opens the customize dialog (below) to pick which actions show and reorder
+ * them.
+ */
+export function QuickActionsCard() {
+ const quickActionIds = useUIStore((s) => s.quickActionIds);
+ const actions = resolveQuickActions(quickActionIds);
+
+ return (
+
+ {actions.length === 0 && (
+
+ No quick actions selected — use the pencil to add some.
+
+ )}
+
+ {actions.map((action) => (
+
+
+
+
+
+ ))}
+
+
+
+ );
+}
+
+const CUSTOMIZE_LABEL = "Customize Quick Actions";
+
+function QuickActionsCustomizeDialog() {
+ const [open, setOpen] = useState(false);
+ const quickActionIds = useUIStore((s) => s.quickActionIds);
+ const setQuickActionIds = useUIStore((s) => s.setQuickActionIds);
+ const resetQuickActionIds = useUIStore((s) => s.resetQuickActionIds);
+
+ // Stale ids (an action removed from the registry since it was picked) are
+ // dropped here too — the dialog only ever shows/reorders real actions.
+ const enabledIds = resolveQuickActions(quickActionIds).map((a) => a.id);
+ const enabledSet = new Set(enabledIds);
+ // Enabled actions first (in the user's chosen order), then everything else
+ // available to add, in registry order.
+ const orderedIds = [
+ ...enabledIds,
+ ...QUICK_ACTIONS_REGISTRY.filter((a) => !enabledSet.has(a.id)).map(
+ (a) => a.id,
+ ),
+ ];
+
+ const toggle = (id: string) => {
+ if (enabledSet.has(id)) {
+ setQuickActionIds(enabledIds.filter((x) => x !== id));
+ } else {
+ setQuickActionIds([...enabledIds, id]);
+ }
+ };
+
+ const move = (id: string, direction: -1 | 1) => {
+ const index = enabledIds.indexOf(id);
+ if (index === -1) return;
+ const target = index + direction;
+ if (target < 0 || target >= enabledIds.length) return;
+ const next = [...enabledIds];
+ [next[index], next[target]] = [next[target], next[index]];
+ setQuickActionIds(next);
+ };
+
+ return (
+
+ );
+}
diff --git a/panel/src/components/dashboard/quick-actions-registry.ts b/panel/src/components/dashboard/quick-actions-registry.ts
new file mode 100644
index 00000000..60d4121e
--- /dev/null
+++ b/panel/src/components/dashboard/quick-actions-registry.ts
@@ -0,0 +1,241 @@
+import {
+ Sparkles,
+ ListTodo,
+ Kanban,
+ GitBranch,
+ Briefcase,
+ Share2,
+ Database,
+ Radio,
+ Bot,
+ BookOpen,
+ Shield,
+ Activity,
+ Building2,
+ Cpu,
+ Settings,
+ type LucideIcon,
+} from "lucide-react";
+
+export interface QuickAction {
+ id: string;
+ label: string;
+ icon: LucideIcon;
+ href: string;
+ tip: string;
+}
+
+// Static catalog of every quick-action destination the Overview dashboard can
+// jump to. Derived from the panel's real route surface — the sidebar's
+// canonical nav list (components/layout/sidebar.tsx) plus the tab-
+// parameterized deep links each of those pages actually supports — never an
+// invented route. The release/X/video/roadmap approval queues are
+// deliberately absent: they already render directly on the Overview page
+// itself (see command-center.tsx), so a "quick action" pointing at the page
+// the user is already on would be dead weight.
+export const QUICK_ACTIONS_REGISTRY: QuickAction[] = [
+ {
+ id: "new-task",
+ label: "New Task",
+ icon: Sparkles,
+ href: "/prompter",
+ tip: "Chat with Intake to draft and confirm a new task, including MegaTask batches",
+ },
+ {
+ id: "tasks",
+ label: "Tasks",
+ icon: ListTodo,
+ href: "/tasks",
+ tip: "Full task list — filter, search, and open any task's detail",
+ },
+ {
+ id: "kanban",
+ label: "Kanban",
+ icon: Kanban,
+ href: "/kanban",
+ tip: "Task board grouped by lifecycle status",
+ },
+ {
+ id: "git-repository",
+ label: "Repository",
+ icon: GitBranch,
+ href: "/git?tab=repository",
+ tip: "Branches, commits, and diffs across every project workspace",
+ },
+ {
+ id: "git-sessions",
+ label: "Work Sessions",
+ icon: GitBranch,
+ href: "/git?tab=sessions",
+ tip: "Active agent work sessions — branch, commits, and PR per task",
+ },
+ {
+ id: "workstation-products",
+ label: "Products",
+ icon: Briefcase,
+ href: "/workstation?tab=products",
+ tip: "Products the fleet ships against",
+ },
+ {
+ id: "workstation-projects",
+ label: "Projects",
+ icon: Briefcase,
+ href: "/workstation?tab=projects",
+ tip: "Manage repos, git tokens, and per-project settings",
+ },
+ {
+ id: "social",
+ label: "Social",
+ icon: Share2,
+ href: "/social",
+ tip: "X and TikTok post queues, plus the video pipeline",
+ },
+ {
+ id: "knowledge-base",
+ label: "Knowledge Base",
+ icon: Database,
+ href: "/knowledge-base",
+ tip: "Search the RAG corpus — playbooks, learnings, and vault notes",
+ },
+ {
+ id: "a2a",
+ label: "A2A",
+ icon: Radio,
+ href: "/a2a",
+ tip: "Live agent-to-agent message switchboard and history",
+ },
+ {
+ id: "agents",
+ label: "Agents",
+ icon: Bot,
+ href: "/agents",
+ tip: "Every agent's live state, spawn controls, and activity stream",
+ },
+ {
+ id: "journals",
+ label: "Journals",
+ icon: BookOpen,
+ href: "/journals",
+ tip: "Per-agent reflections and learnings",
+ },
+ {
+ id: "auditor",
+ label: "Auditor",
+ icon: Shield,
+ href: "/auditor",
+ tip: "Silent-observer quality flags and findings review queue",
+ },
+ {
+ id: "metrics-performance",
+ label: "Metrics",
+ icon: Activity,
+ href: "/metrics?tab=performance",
+ tip: "Task velocity, status counts, agent load, and team health",
+ },
+ {
+ id: "metrics-delivery",
+ label: "Delivery Metrics",
+ icon: Activity,
+ href: "/metrics?tab=delivery",
+ tip: "Cycle time, bottlenecks, and rework rate reconstructed from the audit log",
+ },
+ {
+ id: "metrics-token-usage",
+ label: "Token Usage",
+ icon: Activity,
+ href: "/metrics?tab=token-usage",
+ tip: "Token spend, cost projections, cache efficiency, and per-session detail",
+ },
+ {
+ id: "metrics-scorecards",
+ label: "Scorecards",
+ icon: Activity,
+ href: "/metrics?tab=scorecards",
+ tip: "Per-agent and per-team delivery scorecards",
+ },
+ {
+ id: "business-goals",
+ label: "Goals",
+ icon: Building2,
+ href: "/business?tab=goals",
+ tip: "CEO-owned charter — north star, brand voice, objectives, constraints",
+ },
+ {
+ id: "business-scorecard",
+ label: "Business Scorecard",
+ icon: Building2,
+ href: "/business?tab=scorecard",
+ tip: "Live delivery, spend, and speed metrics against the charter",
+ },
+ {
+ id: "business-secretary",
+ label: "Secretary",
+ icon: Bot,
+ href: "/business?tab=secretary",
+ tip: "Chat with your chief-of-staff and confirm or reject pending directives",
+ },
+ {
+ id: "business-pitches",
+ label: "Pitches",
+ icon: Building2,
+ href: "/business?tab=pitches",
+ tip: "Board-authored product pitches awaiting your decision",
+ },
+ {
+ id: "ai-providers",
+ label: "AI Providers",
+ icon: Cpu,
+ href: "/settings/ai-providers",
+ tip: "Model routing and per-role provider assignments",
+ },
+ {
+ id: "settings",
+ label: "Settings",
+ icon: Settings,
+ href: "/settings",
+ tip: "Feature flags, credentials, and panel preferences",
+ },
+];
+
+const REGISTRY_BY_ID: ReadonlyMap = new Map(
+ QUICK_ACTIONS_REGISTRY.map((action) => [action.id, action]),
+);
+
+// Curated for the CEO's actual day-to-day workflow — replaces the old
+// hardcoded QuickActionsBar (New Task dialog / Spawn Agent / Secretary /
+// Journals / Auditor), which never surfaced Tasks, Kanban, Git, or Metrics
+// at all despite those being the highest-traffic destinations.
+// Includes every destination the legacy QuickActionsBar offered (secretary,
+// journals, auditor) — absorbing it must not silently demote any of them out
+// of a fresh install's default view.
+export const DEFAULT_QUICK_ACTION_IDS: string[] = [
+ "new-task",
+ "tasks",
+ "kanban",
+ "git-repository",
+ "agents",
+ "a2a",
+ "business-secretary",
+ "journals",
+ "auditor",
+ "metrics-performance",
+ "settings",
+];
+
+/**
+ * Resolves a stored, ordered id list into real actions, dropping any id that
+ * no longer exists in the registry (a stale localStorage entry from a since-
+ * removed action) instead of crashing. Order-preserving.
+ */
+export function resolveQuickActions(ids: readonly string[]): QuickAction[] {
+ const resolved: QuickAction[] = [];
+ for (const id of ids) {
+ const action = REGISTRY_BY_ID.get(id);
+ if (action) resolved.push(action);
+ }
+ return resolved;
+}
+
+export function isKnownQuickActionId(id: string): boolean {
+ return REGISTRY_BY_ID.has(id);
+}
diff --git a/panel/src/store/__tests__/ui-store.test.ts b/panel/src/store/__tests__/ui-store.test.ts
new file mode 100644
index 00000000..feec635b
--- /dev/null
+++ b/panel/src/store/__tests__/ui-store.test.ts
@@ -0,0 +1,19 @@
+import { describe, expect, it } from "vitest";
+import { useUIStore } from "../ui-store";
+import { DEFAULT_QUICK_ACTION_IDS } from "@/components/dashboard/quick-actions-registry";
+
+describe("ui-store persistence contract", () => {
+ it("persists quickActionIds through partialize (a key missing here silently stops persisting)", () => {
+ const partialize = useUIStore.persist.getOptions().partialize;
+ expect(partialize).toBeDefined();
+ const persisted = partialize!(useUIStore.getState()) as Record;
+ expect(persisted.quickActionIds).toEqual(DEFAULT_QUICK_ACTION_IDS);
+ });
+
+ it("reset restores the exact default list after customization", () => {
+ useUIStore.getState().setQuickActionIds(["tasks"]);
+ expect(useUIStore.getState().quickActionIds).toEqual(["tasks"]);
+ useUIStore.getState().resetQuickActionIds();
+ expect(useUIStore.getState().quickActionIds).toEqual(DEFAULT_QUICK_ACTION_IDS);
+ });
+});
diff --git a/panel/src/store/ui-store.ts b/panel/src/store/ui-store.ts
index a445a7ca..9cd26808 100644
--- a/panel/src/store/ui-store.ts
+++ b/panel/src/store/ui-store.ts
@@ -1,6 +1,7 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import type { Team } from "@/types";
+import { DEFAULT_QUICK_ACTION_IDS } from "@/components/dashboard/quick-actions-registry";
interface UIState {
// Sidebar
@@ -30,6 +31,12 @@ interface UIState {
autoRefresh: boolean;
refreshIntervalSeconds: number;
+ // Quick Actions (Overview dashboard) — ordered list of
+ // quick-actions-registry ids the CEO has chosen to show. Per-browser only,
+ // same persisted-preference idiom as everything else in this store; see
+ // quick-actions-card.tsx for the render/customize side.
+ quickActionIds: string[];
+
// Actions
toggleSidebar: () => void;
setSidebarCollapsed: (collapsed: boolean) => void;
@@ -42,6 +49,10 @@ interface UIState {
setSoundEnabled: (enabled: boolean) => void;
setAutoRefresh: (enabled: boolean) => void;
setRefreshIntervalSeconds: (seconds: number) => void;
+
+ // Quick Actions actions
+ setQuickActionIds: (ids: string[]) => void;
+ resetQuickActionIds: () => void;
}
export const useUIStore = create()(
@@ -59,6 +70,9 @@ export const useUIStore = create()(
autoRefresh: false, // default-off: never start a background poller unasked
refreshIntervalSeconds: 30,
+ // Quick Actions default
+ quickActionIds: DEFAULT_QUICK_ACTION_IDS,
+
toggleSidebar: () =>
set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
@@ -74,6 +88,11 @@ export const useUIStore = create()(
setAutoRefresh: (enabled) => set({ autoRefresh: enabled }),
setRefreshIntervalSeconds: (seconds) =>
set({ refreshIntervalSeconds: seconds }),
+
+ // Quick Actions actions
+ setQuickActionIds: (ids) => set({ quickActionIds: ids }),
+ resetQuickActionIds: () =>
+ set({ quickActionIds: DEFAULT_QUICK_ACTION_IDS }),
}),
{
name: "roboco-ui-storage",
@@ -88,6 +107,8 @@ export const useUIStore = create()(
soundEnabled: state.soundEnabled,
autoRefresh: state.autoRefresh,
refreshIntervalSeconds: state.refreshIntervalSeconds,
+ // Quick Actions
+ quickActionIds: state.quickActionIds,
}),
},
),