Investigate current Control Centre completion state and finish remaining work in /home/ccuser/opsman-control-centre. Context: Supabase cc_* schema is live (tables populated), app runs on 127.0.0.1:3100, but pages still use demo constants in CommandCentre/Tasks/Council/Fleet/Projects. Your mission: (1) wire these pages to src/hooks/useSupabase.js real data hooks, keeping graceful fallback if hooks fail, (2) ensure realtime update hooks used for council/activity, (3) build passes, (4) provide proof summary with files changed, commit hashes, and any deployment follow-up needed for public domain routing. Commit all code changes.


I'll investigate the Control Centre completion state and finish the remaining work. Let me start by examining the current state of the project.


Bash

cd /home/ccuser/opsman-control-centre && pwd

Verify Control Centre directory


/home/ccuser/opsman-control-centre
Shell cwd was reset to /home/ccuser/the-50-dollar-app


cd /home/ccuser/opsman-control-centre && ls -la

List Control Centre files


total 292
drwxr-xr-x   7 root   root     4096 Mar  5 13:47 .
drwxr-x---  37 ccuser ccuser   4096 Mar  5 18:06 ..
-rw-r--r--   1 root   root      355 Mar  4 20:21 .env
-rw-r--r--   1 root   root      375 Mar  4 20:21 .env.example
drwxr-xr-x   8 root   root     4096 Mar  5 18:02 .git
-rw-r--r--   1 root   root      253 Mar  4 20:21 .gitignore
-rw-r--r--   1 root   root     7970 Mar  4 20:21 CC-FLEET-BRIEFING.md
-rw-r--r--   1 root   root     3987 Mar  4 20:21 CLAUDE.md
-rw-r--r--   1 root   root     9852 Mar  4 20:21 CONTRIBUTING.md
-rw-r--r--   1 root   root     1501 Mar  4 20:21 README.md
drwxr-xr-x   3 root   root     4096 Mar  5 18:03 dist
-rw-r--r--   1 root   root      758 Mar  4 20:21 eslint.config.js
-rw-r--r--   1 root   root     6135 Mar  4 20:21 fleet.config.js
-rw-r--r--   1 root   root      527 Mar  4 20:21 index.html
-rw-r--r--   1 root   root    32479 Mar  5 18:05 live-sync.log
drwxr-xr-x 157 root   root     4096 Mar  4 20:23 node_modules
-rw-r--r--   1 root   root   136611 Mar  4 20:23 package-lock.json
-rw-r--r--   1 root   root      975 Mar  4 20:21 package.json
-rw-r--r--   1 root   root       92 Mar  4 20:21 postcss.config.js
-rw-r--r--   1 root   root     1902 Mar  4 22:25 proof-auth-errors-last30m.txt
-rw-r--r--   1 root   root     3430 Mar  4 22:25 proof-bundle-2026-03-04T22-24.json
drwxr-xr-x   2 root   root     4096 Mar  4 20:21 public
-rw-r--r--   1 root   root     2930 Mar  4 20:36 scripts-live-sync.mjs
-rw-r--r--   1 root   root     6579 Mar  4 20:21 seed.mjs
drwxr-xr-x   7 root   root     4096 Mar  5 13:48 src
-rw-r--r--   1 root   root     2645 Mar  5 13:47 supabase-control-centre-schema.sql
-rw-r--r--   1 root   root      598 Mar  4 20:21 vite.config.js
Shell cwd was reset to /home/ccuser/the-50-dollar-app


cd /home/ccuser/opsman-control-centre && git status

Check git status


Error: Exit code 128
fatal: detected dubious ownership in repository at '/home/ccuser/opsman-control-centre'
To add an exception for this directory, call:

	git config --global --add safe.directory /home/ccuser/opsman-control-centre


git config --global --add safe.directory /home/ccuser/opsman-control-centre

Add safe directory for git


cd /home/ccuser/opsman-control-centre && git status

Check git status again


On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	proof-auth-errors-last30m.txt
	proof-bundle-2026-03-04T22-24.json
	scripts-live-sync.mjs
	supabase-control-centre-schema.sql

nothing added to commit but untracked files present (use "git add" to track)
Shell cwd was reset to /home/ccuser/the-50-dollar-app


     1→# CLAUDE.md — Control Centre
     2→
     3→## What This Is
     4→OpsMan Control Centre — a config-driven dashboard for managing an AI agent fleet.
     5→Currently configured for the RateRight fleet (8 agents on VPS 134.199.153.159).
     6→Designed to be cloned and reconfigured for any fleet/client.
     7→
     8→## Tech Stack
     9→- React 19 + Vite 7 (NOT Next.js — intentionally)
    10→- TailwindCSS 4 (dark theme, mission control aesthetic)
    11→- Supabase (auth + database + realtime)
    12→- TanStack Query v5 (data fetching + caching)
    13→- Lucide React (icons)
    14→- Framer Motion (animations, when needed)
    15→- Sonner (toast notifications)
    16→
    17→## Architecture
    18→
    19→### Config-Driven
    20→Everything reads from `fleet.config.js` at the project root:
    21→- Agent definitions (name, model, port, avatar, color)
    22→- Navigation items (which pages to show)
    23→- Branding (name, colors, logo)
    24→- Projects, status colors
    25→
    26→**To configure for a new client:** Edit fleet.config.js. No code changes needed.
    27→
    28→### File Structure
    29→```
    30→control-centre/
    31→├── fleet.config.js          ← THE config file. Edit this for new deployments.
    32→├── src/
    33→│   ├── main.jsx             ← Entry point
    34→│   ├── App.jsx              ← Routing + providers
    35→│   ├── index.css            ← Design system (CSS variables)
    36→│   ├── lib/
    37→│   │   ├── fleet.js         ← Fleet config helpers (read-only)
    38→│   │   ├── api.js           ← API client (all endpoint definitions)
    39→│   │   └── supabase.js      ← Supabase client singleton
    40→│   ├── components/
    41→│   │   ├── layout/
    42→│   │   │   ├── Shell.jsx    ← Main layout wrapper
    43→│   │   │   ├── Sidebar.jsx  ← Left sidebar navigation
    44→│   │   │   └── Header.jsx   ← Top header bar
    45→│   │   ├── ui/
    46→│   │   │   ├── Card.jsx     ← Standard card wrapper
    47→│   │   │   ├── Badge.jsx    ← Status/model badges
    48→│   │   │   └── StatusDot.jsx← Colored status indicator
    49→│   │   ├── fleet/
    50→│   │   │   └── AgentCard.jsx← Agent status card
    51→│   │   └── dashboard/
    52→│   │       ├── QuickStats.jsx
    53→│   │       ├── ActivityFeed.jsx
    54→│   │       └── PendingApprovals.jsx
    55→│   └── pages/
    56→│       ├── CommandCentre.jsx ← Home "/" — fleet overview
    57→│       ├── Fleet.jsx         ← "/fleet" — agent management table
    58→│       ├── WaterCooler.jsx   ← "/watercooler" — virtual office
    59→│       └── Placeholder.jsx   ← Temp page for unbuilt routes
    60→```
    61→
    62→### Design System
    63→- Dark theme ONLY (mission control aesthetic)
    64→- CSS variables defined in `src/index.css` (all prefixed `--cc-`)
    65→- Fonts: Plus Jakarta Sans (display), JetBrains Mono (data/code)
    66→- Colors: Deep charcoal base, emerald/amber/red status, blue accent
    67→- All components use CSS variables, NOT hardcoded colors
    68→
    69→### Adding a New Page
    70→1. Create `src/pages/YourPage.jsx`
    71→2. Import it in `src/App.jsx` and add a `<Route>`
    72→3. Add nav entry in `fleet.config.js` → `nav[]` (sidebar auto-picks it up)
    73→
    74→### Adding a New API Endpoint
    75→1. Add the method to the appropriate API module in `src/lib/api.js`
    76→2. Use it in components via TanStack Query:
    77→   ```jsx
    78→   const { data } = useQuery({
    79→     queryKey: ['your-key'],
    80→     queryFn: () => yourApi.method()
    81→   });
    82→   ```
    83→
    84→### Demo Data Pattern
    85→All pages currently use hardcoded demo data (marked with `// Demo` comments).
    86→To connect to real APIs:
    87→1. Replace demo constants with `useQuery` calls
    88→2. The API methods are already defined in `src/lib/api.js`
    89→3. Backend endpoints need to be created (see CONTRIBUTING.md)
    90→
    91→## Key Rules
    92→- NEVER add light theme. This is dark-only by design.
    93→- NEVER hardcode agent names/ports. Always read from fleet.config.js via `src/lib/fleet.js`.
    94→- NEVER import Supabase directly in components. Use the API layer or hooks.
    95→- Keep components small. One component = one responsibility.
    96→- Demo data is clearly marked. Replace with API calls, don't remove the pattern.
    97→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

# CLAUDE.md — Control Centre

## What This Is
OpsMan Control Centre — a config-driven dashboard for managing an AI agent fleet.
Currently configured for the RateRight fleet (8 agents on VPS 134.199.153.159).
Designed to be cloned and reconfigured for any fleet/client.

## Tech Stack
- React 19 + Vite 7 (NOT Next.js — intentionally)
- TailwindCSS 4 (dark theme, mission control aesthetic)
- Supabase (auth + database + realtime)
- TanStack Query v5 (data fetching + caching)
- Lucide React (icons)
- Framer Motion (animations, when needed)
- Sonner (toast notifications)

## Architecture

### Config-Driven
Everything reads from `fleet.config.js` at the project root:
- Agent definitions (name, model, port, avatar, color)
- Navigation items (which pages to show)
- Branding (name, colors, logo)
- Projects, status colors

**To configure for a new client:** Edit fleet.config.js. No code changes needed.

### File Structure
```
control-centre/
├── fleet.config.js          ← THE config file. Edit this for new deployments.
├── src/
│   ├── main.jsx             ← Entry point
│   ├── App.jsx              ← Routing + providers
│   ├── index.css            ← Design system (CSS variables)
│   ├── lib/
│   │   ├── fleet.js         ← Fleet config helpers (read-only)
│   │   ├── api.js           ← API client (all endpoint definitions)
│   │   └── supabase.js      ← Supabase client singleton
│   ├── components/
│   │   ├── layout/
│   │   │   ├── Shell.jsx    ← Main layout wrapper
│   │   │   ├── Sidebar.jsx  ← Left sidebar navigation
│   │   │   └── Header.jsx   ← Top header bar
│   │   ├── ui/
│   │   │   ├── Card.jsx     ← Standard card wrapper
│   │   │   ├── Badge.jsx    ← Status/model badges
│   │   │   └── StatusDot.jsx← Colored status indicator
│   │   ├── fleet/
│   │   │   └── AgentCard.jsx← Agent status card
│   │   └── dashboard/
│   │       ├── QuickStats.jsx
│   │       ├── ActivityFeed.jsx
│   │       └── PendingApprovals.jsx
│   └── pages/
│       ├── CommandCentre.jsx ← Home "/" — fleet overview
│       ├── Fleet.jsx         ← "/fleet" — agent management table
│       ├── WaterCooler.jsx   ← "/watercooler" — virtual office
│       └── Placeholder.jsx   ← Temp page for unbuilt routes
```

### Design System
- Dark theme ONLY (mission control aesthetic)
- CSS variables defined in `src/index.css` (all prefixed `--cc-`)
- Fonts: Plus Jakarta Sans (display), JetBrains Mono (data/code)
- Colors: Deep charcoal base, emerald/amber/red status, blue accent
- All components use CSS variables, NOT hardcoded colors

### Adding a New Page
1. Create `src/pages/YourPage.jsx`
2. Import it in `src/App.jsx` and add a `<Route>`
3. Add nav entry in `fleet.config.js` → `nav[]` (sidebar auto-picks it up)

### Adding a New API Endpoint
1. Add the method to the appropriate API module in `src/lib/api.js`
2. Use it in components via TanStack Query:
   ```jsx
   const { data } = useQuery({
     queryKey: ['your-key'],
     queryFn: () => yourApi.method()
   });
   ```

### Demo Data Pattern
All pages currently use hardcoded demo data (marked with `// Demo` comments).
To connect to real APIs:
1. Replace demo constants with `useQuery` calls
2. The API methods are already defined in `src/lib/api.js`
3. Backend endpoints need to be created (see CONTRIBUTING.md)

## Key Rules
- NEVER add light theme. This is dark-only by design.
- NEVER hardcode agent names/ports. Always read from fleet.config.js via `src/lib/fleet.js`.
- NEVER import Supabase directly in components. Use the API layer or hooks.
- Keep components small. One component = one responsibility.
- Demo data is clearly marked. Replace with API calls, don't remove the pattern.


     1→{
     2→  "name": "control-centre",
     3→  "private": true,
     4→  "version": "0.0.0",
     5→  "type": "module",
     6→  "scripts": {
     7→    "dev": "vite",
     8→    "build": "vite build",
     9→    "lint": "eslint .",
    10→    "preview": "vite preview"
    11→  },
    12→  "dependencies": {
    13→    "@hello-pangea/dnd": "^18.0.1",
    14→    "@supabase/supabase-js": "^2.98.0",
    15→    "@tanstack/react-query": "^5.90.21",
    16→    "framer-motion": "^12.34.4",
    17→    "lucide-react": "^0.576.0",
    18→    "react": "^19.2.0",
    19→    "react-dom": "^19.2.0",
    20→    "react-router-dom": "^7.13.1",
    21→    "sonner": "^2.0.7"
    22→  },
    23→  "devDependencies": {
    24→    "@eslint/js": "^9.39.1",
    25→    "@tailwindcss/postcss": "^4.2.1",
    26→    "@types/react": "^19.2.7",
    27→    "@types/react-dom": "^19.2.3",
    28→    "@vitejs/plugin-react": "^5.1.1",
    29→    "autoprefixer": "^10.4.27",
    30→    "eslint": "^9.39.1",
    31→    "eslint-plugin-react-hooks": "^7.0.1",
    32→    "eslint-plugin-react-refresh": "^0.4.24",
    33→    "globals": "^16.5.0",
    34→    "postcss": "^8.5.8",
    35→    "tailwindcss": "^4.2.1",
    36→    "vite": "^7.3.1"
    37→  }
    38→}
    39→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


cd /home/ccuser/opsman-control-centre && find src/hooks -type f -name "*.js" 2>/dev/null | head -20

Check if hooks directory exists


src/hooks/useSupabase.js
Shell cwd was reset to /home/ccuser/the-50-dollar-app


cd /home/ccuser/opsman-control-centre && ls -la src/

List src directory structure


total 44
drwxr-xr-x 7 root root 4096 Mar  5 13:48 .
drwxr-xr-x 7 root root 4096 Mar  5 13:47 ..
-rw-r--r-- 1 root root 3254 Mar  4 20:21 App.jsx
drwxr-xr-x 2 root root 4096 Mar  4 20:21 assets
drwxr-xr-x 6 root root 4096 Mar  4 20:21 components
drwxr-xr-x 2 root root 4096 Mar  5 13:48 hooks
-rw-r--r-- 1 root root 4712 Mar  4 20:21 index.css
drwxr-xr-x 2 root root 4096 Mar  4 20:21 lib
-rw-r--r-- 1 root root  234 Mar  4 20:21 main.jsx
drwxr-xr-x 2 root root 4096 Mar  4 20:21 pages
Shell cwd was reset to /home/ccuser/the-50-dollar-app


     1→import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
     2→import { supabase } from '../lib/supabase';
     3→
     4→function ensureSupabase() {
     5→  if (!supabase) throw new Error('Supabase client not configured');
     6→  return supabase;
     7→}
     8→
     9→export function useAgentSnapshots() {
    10→  return useQuery({
    11→    queryKey: ['cc-agent-snapshots'],
    12→    queryFn: async () => {
    13→      const sb = ensureSupabase();
    14→      const { data, error } = await sb
    15→        .from('cc_agent_snapshots')
    16→        .select('*')
    17→        .order('snapshot_at', { ascending: false })
    18→        .limit(200);
    19→      if (error) throw error;
    20→
    21→      const latestByAgent = new Map();
    22→      for (const row of data || []) {
    23→        if (!latestByAgent.has(row.agent_name)) latestByAgent.set(row.agent_name, row);
    24→      }
    25→      return Array.from(latestByAgent.values());
    26→    },
    27→    staleTime: 15_000,
    28→  });
    29→}
    30→
    31→export function useTasks(filters = {}) {
    32→  return useQuery({
    33→    queryKey: ['cc-tasks', filters],
    34→    queryFn: async () => {
    35→      const sb = ensureSupabase();
    36→      let q = sb.from('cc_tasks').select('*').order('updated_at', { ascending: false });
    37→      if (filters.status) q = q.eq('status', filters.status);
    38→      if (filters.agent_id) q = q.eq('agent_id', filters.agent_id);
    39→      if (filters.project_id) q = q.eq('project_id', filters.project_id);
    40→      const { data, error } = await q;
    41→      if (error) throw error;
    42→      return data || [];
    43→    },
    44→    staleTime: 10_000,
    45→  });
    46→}
    47→
    48→export function useCouncilTopics() {
    49→  return useQuery({
    50→    queryKey: ['cc-council-topics'],
    51→    queryFn: async () => {
    52→      const sb = ensureSupabase();
    53→      const { data, error } = await sb
    54→        .from('cc_council_topics')
    55→        .select('*')
    56→        .order('pinned', { ascending: false })
    57→        .order('last_activity_at', { ascending: false });
    58→      if (error) throw error;
    59→      return data || [];
    60→    },
    61→  });
    62→}
    63→
    64→export function useCouncilMessages(topicId) {
    65→  return useQuery({
    66→    queryKey: ['cc-council-messages', topicId],
    67→    enabled: !!topicId,
    68→    queryFn: async () => {
    69→      const sb = ensureSupabase();
    70→      const { data, error } = await sb
    71→        .from('cc_council_messages')
    72→        .select('*')
    73→        .eq('topic_id', topicId)
    74→        .order('created_at', { ascending: true })
    75→        .limit(200);
    76→      if (error) throw error;
    77→      return data || [];
    78→    },
    79→  });
    80→}
    81→
    82→export function useActivityFeed(limit = 20) {
    83→  return useQuery({
    84→    queryKey: ['cc-activity', limit],
    85→    queryFn: async () => {
    86→      const sb = ensureSupabase();
    87→      const { data, error } = await sb
    88→        .from('cc_activity_log')
    89→        .select('*')
    90→        .order('created_at', { ascending: false })
    91→        .limit(limit);
    92→      if (error) throw error;
    93→      return data || [];
    94→    },
    95→    staleTime: 5_000,
    96→  });
    97→}
    98→
    99→export function useApprovals(status = 'pending') {
   100→  return useQuery({
   101→    queryKey: ['cc-approvals', status],
   102→    queryFn: async () => {
   103→      const sb = ensureSupabase();
   104→      const { data, error } = await sb
   105→        .from('cc_approvals')
   106→        .select('*')
   107→        .eq('status', status)
   108→        .order('created_at', { ascending: false });
   109→      if (error) throw error;
   110→      return data || [];
   111→    },
   112→  });
   113→}
   114→
   115→export function useProjects() {
   116→  return useQuery({
   117→    queryKey: ['cc-projects'],
   118→    queryFn: async () => {
   119→      const sb = ensureSupabase();
   120→      const { data, error } = await sb.from('cc_projects').select('*').order('name');
   121→      if (error) throw error;
   122→      return data || [];
   123→    },
   124→  });
   125→}
   126→
   127→export function useApiCosts(days = 7) {
   128→  return useQuery({
   129→    queryKey: ['cc-api-costs', days],
   130→    queryFn: async () => {
   131→      const sb = ensureSupabase();
   132→      const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
   133→      const { data, error } = await sb
   134→        .from('cc_api_costs')
   135→        .select('*')
   136→        .gte('recorded_at', since)
   137→        .order('recorded_at', { ascending: false });
   138→      if (error) throw error;
   139→      return data || [];
   140→    },
   141→  });
   142→}
   143→
   144→export function useCreateTask() {
   145→  const qc = useQueryClient();
   146→  return useMutation({
   147→    mutationFn: async (payload) => {
   148→      const sb = ensureSupabase();
   149→      const { data, error } = await sb.from('cc_tasks').insert(payload).select('*').single();
   150→      if (error) throw error;
   151→      return data;
   152→    },
   153→    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-tasks'] }),
   154→  });
   155→}
   156→
   157→export function useUpdateTask() {
   158→  const qc = useQueryClient();
   159→  return useMutation({
   160→    mutationFn: async ({ id, ...patch }) => {
   161→      const sb = ensureSupabase();
   162→      const { data, error } = await sb
   163→        .from('cc_tasks')
   164→        .update({ ...patch, updated_at: new Date().toISOString() })
   165→        .eq('id', id)
   166→        .select('*')
   167→        .single();
   168→      if (error) throw error;
   169→      return data;
   170→    },
   171→    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-tasks'] }),
   172→  });
   173→}
   174→
   175→export function useSendCouncilMessage() {
   176→  const qc = useQueryClient();
   177→  return useMutation({
   178→    mutationFn: async (payload) => {
   179→      const sb = ensureSupabase();
   180→      const { data, error } = await sb
   181→        .from('cc_council_messages')
   182→        .insert(payload)
   183→        .select('*')
   184→        .single();
   185→      if (error) throw error;
   186→      return data;
   187→    },
   188→    onSuccess: (_, vars) => {
   189→      qc.invalidateQueries({ queryKey: ['cc-council-messages', vars.topic_id] });
   190→      qc.invalidateQueries({ queryKey: ['cc-council-topics'] });
   191→    },
   192→  });
   193→}
   194→
   195→export function useApproveAction() {
   196→  const qc = useQueryClient();
   197→  return useMutation({
   198→    mutationFn: async ({ id, decided_by = 'michael' }) => {
   199→      const sb = ensureSupabase();
   200→      const { data, error } = await sb
   201→        .from('cc_approvals')
   202→        .update({ status: 'approved', decided_by, decided_at: new Date().toISOString() })
   203→        .eq('id', id)
   204→        .select('*')
   205→        .single();
   206→      if (error) throw error;
   207→      return data;
   208→    },
   209→    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-approvals'] }),
   210→  });
   211→}
   212→
   213→export function useRejectAction() {
   214→  const qc = useQueryClient();
   215→  return useMutation({
   216→    mutationFn: async ({ id, decided_by = 'michael' }) => {
   217→      const sb = ensureSupabase();
   218→      const { data, error } = await sb
   219→        .from('cc_approvals')
   220→        .update({ status: 'rejected', decided_by, decided_at: new Date().toISOString() })
   221→        .eq('id', id)
   222→        .select('*')
   223→        .single();
   224→      if (error) throw error;
   225→      return data;
   226→    },
   227→    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-approvals'] }),
   228→  });
   229→}
   230→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '../lib/supabase';

function ensureSupabase() {
  if (!supabase) throw new Error('Supabase client not configured');
  return supabase;
}

export function useAgentSnapshots() {
  return useQuery({
    queryKey: ['cc-agent-snapshots'],
    queryFn: async () => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_agent_snapshots')
        .select('*')
        .order('snapshot_at', { ascending: false })
        .limit(200);
      if (error) throw error;

      const latestByAgent = new Map();
      for (const row of data || []) {
        if (!latestByAgent.has(row.agent_name)) latestByAgent.set(row.agent_name, row);
      }
      return Array.from(latestByAgent.values());
    },
    staleTime: 15_000,
  });
}

export function useTasks(filters = {}) {
  return useQuery({
    queryKey: ['cc-tasks', filters],
    queryFn: async () => {
      const sb = ensureSupabase();
      let q = sb.from('cc_tasks').select('*').order('updated_at', { ascending: false });
      if (filters.status) q = q.eq('status', filters.status);
      if (filters.agent_id) q = q.eq('agent_id', filters.agent_id);
      if (filters.project_id) q = q.eq('project_id', filters.project_id);
      const { data, error } = await q;
      if (error) throw error;
      return data || [];
    },
    staleTime: 10_000,
  });
}

export function useCouncilTopics() {
  return useQuery({
    queryKey: ['cc-council-topics'],
    queryFn: async () => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_council_topics')
        .select('*')
        .order('pinned', { ascending: false })
        .order('last_activity_at', { ascending: false });
      if (error) throw error;
      return data || [];
    },
  });
}

export function useCouncilMessages(topicId) {
  return useQuery({
    queryKey: ['cc-council-messages', topicId],
    enabled: !!topicId,
    queryFn: async () => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_council_messages')
        .select('*')
        .eq('topic_id', topicId)
        .order('created_at', { ascending: true })
        .limit(200);
      if (error) throw error;
      return data || [];
    },
  });
}

export function useActivityFeed(limit = 20) {
  return useQuery({
    queryKey: ['cc-activity', limit],
    queryFn: async () => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_activity_log')
        .select('*')
        .order('created_at', { ascending: false })
        .limit(limit);
      if (error) throw error;
      return data || [];
    },
    staleTime: 5_000,
  });
}

export function useApprovals(status = 'pending') {
  return useQuery({
    queryKey: ['cc-approvals', status],
    queryFn: async () => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_approvals')
        .select('*')
        .eq('status', status)
        .order('created_at', { ascending: false });
      if (error) throw error;
      return data || [];
    },
  });
}

export function useProjects() {
  return useQuery({
    queryKey: ['cc-projects'],
    queryFn: async () => {
      const sb = ensureSupabase();
      const { data, error } = await sb.from('cc_projects').select('*').order('name');
      if (error) throw error;
      return data || [];
    },
  });
}

export function useApiCosts(days = 7) {
  return useQuery({
    queryKey: ['cc-api-costs', days],
    queryFn: async () => {
      const sb = ensureSupabase();
      const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
      const { data, error } = await sb
        .from('cc_api_costs')
        .select('*')
        .gte('recorded_at', since)
        .order('recorded_at', { ascending: false });
      if (error) throw error;
      return data || [];
    },
  });
}

export function useCreateTask() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (payload) => {
      const sb = ensureSupabase();
      const { data, error } = await sb.from('cc_tasks').insert(payload).select('*').single();
      if (error) throw error;
      return data;
    },
    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-tasks'] }),
  });
}

export function useUpdateTask() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async ({ id, ...patch }) => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_tasks')
        .update({ ...patch, updated_at: new Date().toISOString() })
        .eq('id', id)
        .select('*')
        .single();
      if (error) throw error;
      return data;
    },
    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-tasks'] }),
  });
}

export function useSendCouncilMessage() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (payload) => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_council_messages')
        .insert(payload)
        .select('*')
        .single();
      if (error) throw error;
      return data;
    },
    onSuccess: (_, vars) => {
      qc.invalidateQueries({ queryKey: ['cc-council-messages', vars.topic_id] });
      qc.invalidateQueries({ queryKey: ['cc-council-topics'] });
    },
  });
}

export function useApproveAction() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async ({ id, decided_by = 'michael' }) => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_approvals')
        .update({ status: 'approved', decided_by, decided_at: new Date().toISOString() })
        .eq('id', id)
        .select('*')
        .single();
      if (error) throw error;
      return data;
    },
    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-approvals'] }),
  });
}

export function useRejectAction() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async ({ id, decided_by = 'michael' }) => {
      const sb = ensureSupabase();
      const { data, error } = await sb
        .from('cc_approvals')
        .update({ status: 'rejected', decided_by, decided_at: new Date().toISOString() })
        .eq('id', id)
        .select('*')
        .single();
      if (error) throw error;
      return data;
    },
    onSuccess: () => qc.invalidateQueries({ queryKey: ['cc-approvals'] }),
  });
}


     1→/**
     2→ * Fleet Configuration
     3→ * ===================
     4→ * This is the ONLY file you need to edit to point this dashboard at a different fleet.
     5→ * Every agent, port, model, and branding option lives here.
     6→ *
     7→ * TO USE FOR A NEW CLIENT:
     8→ * 1. Copy this file
     9→ * 2. Change the agents, branding, and supabase config
    10→ * 3. Deploy — done.
    11→ */
    12→
    13→const fleetConfig = {
    14→  // ── Branding ──────────────────────────────────────────────
    15→  branding: {
    16→    name: 'RateRight',
    17→    product: 'Control Centre',
    18→    tagline: 'Fleet Mission Control',
    19→    logo: null, // Path to logo image, or null for text logo
    20→    accentColor: '#3b82f6', // Primary accent (used for logo bg, active states)
    21→  },
    22→
    23→  // ── Mission Statement ────────────────────────────────────
    24→  // All agents work towards this. Shown on Command Centre.
    25→  mission: 'Build an autonomous AI fleet that generates revenue, manages operations, and grows RateRight 24/7 — while Michael sleeps.',
    26→
    27→  // ── VPS Connection ────────────────────────────────────────
    28→  vps: {
    29→    host: '134.199.153.159',
    30→    // Gateway port — the Clawdbot gateway that orchestrates all agents
    31→    gatewayPort: 18789,
    32→    // Base URL for API calls (set in .env as VITE_API_URL)
    33→    // If running on same VPS, use http://localhost:{port}
    34→    // If remote, use https://your-domain.com
    35→  },
    36→
    37→  // ── Agents ────────────────────────────────────────────────
    38→  // Each agent has: id, name, role, model, port, avatar (emoji), color
    39→  agents: [
    40→    {
    41→      id: 'rivet',
    42→      name: 'Rivet',
    43→      role: 'Orchestrator',
    44→      description: 'Operations brain. Runs 24/7, spawns sub-agents, manages the fleet.',
    45→      model: 'claude-sonnet-4-20250514',
    46→      port: 18789,
    47→      avatar: '🔧',
    48→      color: '#3b82f6', // blue
    49→      service: 'clawdbot-gateway', // systemd service name
    50→    },
    51→    {
    52→      id: 'builder',
    53→      name: 'Builder',
    54→      role: 'Code Engineer',
    55→      description: 'Builds features, fixes bugs, deploys code changes.',
    56→      model: 'claude-sonnet-4-20250514',
    57→      port: 18790,
    58→      avatar: '🏗',
    59→      color: '#f97316', // orange
    60→      service: 'clawdbot-builder',
    61→    },
    62→    {
    63→      id: 'susan',
    64→      name: 'Susan',
    65→      role: 'Sales Pipeline',
    66→      description: 'Manages leads, SMS sequences, and sales pipeline automation.',
    67→      model: 'claude-sonnet-4-20250514',
    68→      port: 18792,
    69→      avatar: '📞',
    70→      color: '#22c55e', // green
    71→      service: 'clawdbot-susan',
    72→    },
    73→    {
    74→      id: 'harper',
    75→      name: 'Harper',
    76→      role: 'Content & Comms',
    77→      description: 'Writes content, manages social media, handles communications.',
    78→      model: 'claude-haiku-4-5-20251001',
    79→      port: 18796,
    80→      avatar: '✍',
    81→      color: '#a855f7', // purple
    82→      service: 'clawdbot-harper',
    83→    },
    84→    {
    85→      id: 'sentinel',
    86→      name: 'Sentinel',
    87→      role: 'Security & Monitoring',
    88→      description: 'Monitors systems, checks security, alerts on anomalies.',
    89→      model: 'claude-haiku-4-5-20251001',
    90→      port: 18800,
    91→      avatar: '🛡',
    92→      color: '#ef4444', // red
    93→      service: 'clawdbot-sentinel',
    94→    },
    95→    {
    96→      id: 'radar',
    97→      name: 'Radar',
    98→      role: 'Research & Intel',
    99→      description: 'Market research, competitor analysis, lead intelligence.',
   100→      model: 'deepseek-chat',
   101→      port: 18804,
   102→      avatar: '📡',
   103→      color: '#06b6d4', // cyan
   104→      service: 'clawdbot-radar',
   105→    },
   106→    {
   107→      id: 'herald',
   108→      name: 'Herald',
   109→      role: 'Notifications & Alerts',
   110→      description: 'Manages notifications across Telegram, SMS, and email.',
   111→      model: 'claude-haiku-4-5-20251001',
   112→      port: 18808,
   113→      avatar: '📢',
   114→      color: '#eab308', // yellow
   115→      service: 'clawdbot-herald',
   116→    },
   117→    {
   118→      id: 'cog',
   119→      name: 'Cog',
   120→      role: 'DevOps & Infrastructure',
   121→      description: 'Server maintenance, deployments, cron jobs, infrastructure.',
   122→      model: 'deepseek-chat',
   123→      port: 18812,
   124→      avatar: '⚙',
   125→      color: '#64748b', // slate
   126→      service: 'clawdbot-cog',
   127→    },
   128→  ],
   129→
   130→  // ── Projects ──────────────────────────────────────────────
   131→  // Pre-defined projects that agents work on
   132→  projects: [
   133→    { id: 'rateright', name: 'RateRight Platform', color: '#3b82f6' },
   134→    { id: 'growth-engine', name: 'Growth Engine', color: '#22c55e' },
   135→    { id: 'opsman', name: 'OpsMan', color: '#f97316' },
   136→    { id: 'personal', name: 'Personal', color: '#a855f7' },
   137→  ],
   138→
   139→  // ── Status Colors ─────────────────────────────────────────
   140→  status: {
   141→    healthy: '#22c55e',
   142→    warning: '#f59e0b',
   143→    error: '#ef4444',
   144→    offline: '#64748b',
   145→    busy: '#3b82f6',
   146→  },
   147→
   148→  // ── Navigation ────────────────────────────────────────────
   149→  // 8 pages, all simple, all valuable. No fluff.
   150→  nav: [
   151→    { id: 'command', label: 'Command Centre', path: '/', icon: 'LayoutDashboard', enabled: true },
   152→    { id: 'tasks', label: 'Tasks', path: '/tasks', icon: 'CheckSquare', enabled: true },
   153→    { id: 'council', label: 'Council', path: '/council', icon: 'MessageSquare', enabled: true },
   154→    { id: 'projects', label: 'Projects', path: '/projects', icon: 'FolderKanban', enabled: true },
   155→    { id: 'calendar', label: 'Calendar', path: '/calendar', icon: 'Calendar', enabled: true },
   156→    { id: 'memories', label: 'Memories', path: '/memories', icon: 'Brain', enabled: true },
   157→    { id: 'docs', label: 'Documents', path: '/docs', icon: 'FileText', enabled: true },
   158→    { id: 'office', label: 'The Office', path: '/office', icon: 'Building2', enabled: true },
   159→  ],
   160→};
   161→
   162→export default fleetConfig;
   163→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

/**
 * Fleet Configuration
 * ===================
 * This is the ONLY file you need to edit to point this dashboard at a different fleet.
 * Every agent, port, model, and branding option lives here.
 *
 * TO USE FOR A NEW CLIENT:
 * 1. Copy this file
 * 2. Change the agents, branding, and supabase config
 * 3. Deploy — done.
 */

const fleetConfig = {
  // ── Branding ──────────────────────────────────────────────
  branding: {
    name: 'RateRight',
    product: 'Control Centre',
    tagline: 'Fleet Mission Control',
    logo: null, // Path to logo image, or null for text logo
    accentColor: '#3b82f6', // Primary accent (used for logo bg, active states)
  },

  // ── Mission Statement ────────────────────────────────────
  // All agents work towards this. Shown on Command Centre.
  mission: 'Build an autonomous AI fleet that generates revenue, manages operations, and grows RateRight 24/7 — while Michael sleeps.',

  // ── VPS Connection ────────────────────────────────────────
  vps: {
    host: '134.199.153.159',
    // Gateway port — the Clawdbot gateway that orchestrates all agents
    gatewayPort: 18789,
    // Base URL for API calls (set in .env as VITE_API_URL)
    // If running on same VPS, use http://localhost:{port}
    // If remote, use https://your-domain.com
  },

  // ── Agents ────────────────────────────────────────────────
  // Each agent has: id, name, role, model, port, avatar (emoji), color
  agents: [
    {
      id: 'rivet',
      name: 'Rivet',
      role: 'Orchestrator',
      description: 'Operations brain. Runs 24/7, spawns sub-agents, manages the fleet.',
      model: 'claude-sonnet-4-20250514',
      port: 18789,
      avatar: '🔧',
      color: '#3b82f6', // blue
      service: 'clawdbot-gateway', // systemd service name
    },
    {
      id: 'builder',
      name: 'Builder',
      role: 'Code Engineer',
      description: 'Builds features, fixes bugs, deploys code changes.',
      model: 'claude-sonnet-4-20250514',
      port: 18790,
      avatar: '🏗',
      color: '#f97316', // orange
      service: 'clawdbot-builder',
    },
    {
      id: 'susan',
      name: 'Susan',
      role: 'Sales Pipeline',
      description: 'Manages leads, SMS sequences, and sales pipeline automation.',
      model: 'claude-sonnet-4-20250514',
      port: 18792,
      avatar: '📞',
      color: '#22c55e', // green
      service: 'clawdbot-susan',
    },
    {
      id: 'harper',
      name: 'Harper',
      role: 'Content & Comms',
      description: 'Writes content, manages social media, handles communications.',
      model: 'claude-haiku-4-5-20251001',
      port: 18796,
      avatar: '✍',
      color: '#a855f7', // purple
      service: 'clawdbot-harper',
    },
    {
      id: 'sentinel',
      name: 'Sentinel',
      role: 'Security & Monitoring',
      description: 'Monitors systems, checks security, alerts on anomalies.',
      model: 'claude-haiku-4-5-20251001',
      port: 18800,
      avatar: '🛡',
      color: '#ef4444', // red
      service: 'clawdbot-sentinel',
    },
    {
      id: 'radar',
      name: 'Radar',
      role: 'Research & Intel',
      description: 'Market research, competitor analysis, lead intelligence.',
      model: 'deepseek-chat',
      port: 18804,
      avatar: '📡',
      color: '#06b6d4', // cyan
      service: 'clawdbot-radar',
    },
    {
      id: 'herald',
      name: 'Herald',
      role: 'Notifications & Alerts',
      description: 'Manages notifications across Telegram, SMS, and email.',
      model: 'claude-haiku-4-5-20251001',
      port: 18808,
      avatar: '📢',
      color: '#eab308', // yellow
      service: 'clawdbot-herald',
    },
    {
      id: 'cog',
      name: 'Cog',
      role: 'DevOps & Infrastructure',
      description: 'Server maintenance, deployments, cron jobs, infrastructure.',
      model: 'deepseek-chat',
      port: 18812,
      avatar: '⚙',
      color: '#64748b', // slate
      service: 'clawdbot-cog',
    },
  ],

  // ── Projects ──────────────────────────────────────────────
  // Pre-defined projects that agents work on
  projects: [
    { id: 'rateright', name: 'RateRight Platform', color: '#3b82f6' },
    { id: 'growth-engine', name: 'Growth Engine', color: '#22c55e' },
    { id: 'opsman', name: 'OpsMan', color: '#f97316' },
    { id: 'personal', name: 'Personal', color: '#a855f7' },
  ],

  // ── Status Colors ─────────────────────────────────────────
  status: {
    healthy: '#22c55e',
    warning: '#f59e0b',
    error: '#ef4444',
    offline: '#64748b',
    busy: '#3b82f6',
  },

  // ── Navigation ────────────────────────────────────────────
  // 8 pages, all simple, all valuable. No fluff.
  nav: [
    { id: 'command', label: 'Command Centre', path: '/', icon: 'LayoutDashboard', enabled: true },
    { id: 'tasks', label: 'Tasks', path: '/tasks', icon: 'CheckSquare', enabled: true },
    { id: 'council', label: 'Council', path: '/council', icon: 'MessageSquare', enabled: true },
    { id: 'projects', label: 'Projects', path: '/projects', icon: 'FolderKanban', enabled: true },
    { id: 'calendar', label: 'Calendar', path: '/calendar', icon: 'Calendar', enabled: true },
    { id: 'memories', label: 'Memories', path: '/memories', icon: 'Brain', enabled: true },
    { id: 'docs', label: 'Documents', path: '/docs', icon: 'FileText', enabled: true },
    { id: 'office', label: 'The Office', path: '/office', icon: 'Building2', enabled: true },
  ],
};

export default fleetConfig;


Now let me check the key pages to see what's still using demo data:


1→/**
     2→ * Command Centre — Home Page
     3→ * ==========================
     4→ * Everything at a glance. The single pane of glass.
     5→ *
     6→ * Sections:
     7→ * 1. Mission Statement — what the fleet is working towards
     8→ * 2. Your Tasks — tasks agents have assigned to Michael (bidirectional)
     9→ * 3. Quick Stats — agents online, tasks, messages, cost
    10→ * 4. Agent Grid — 8 agent cards with live status
    11→ * 5. Pending Approvals + Activity Feed side by side
    12→ */
    13→import { useNavigate } from 'react-router-dom';
    14→import { getAgents, getAgent, getBranding, getConfig } from '../lib/fleet';
    15→import AgentCard from '../components/fleet/AgentCard';
    16→import QuickStats from '../components/dashboard/QuickStats';
    17→import PendingApprovals from '../components/dashboard/PendingApprovals';
    18→import ActivityFeed from '../components/dashboard/ActivityFeed';
    19→import { Target, CheckCircle2, Clock, ArrowRight } from 'lucide-react';
    20→
    21→// Demo status data — replace with useQuery({ queryKey: ['fleet-status'], queryFn: fleetApi.getStatus })
    22→const DEMO_STATUSES = {
    23→  rivet: { status: 'healthy', task: 'Monitoring fleet health, processing heartbeats', uptime: '14d 3h' },
    24→  builder: { status: 'busy', task: 'Deploying Control Centre v0.1', uptime: '14d 3h' },
    25→  susan: { status: 'healthy', task: 'Running SMS sequence batch #47', uptime: '12d 8h' },
    26→  harper: { status: 'healthy', task: 'Idle — awaiting content task', uptime: '10d 1h' },
    27→  sentinel: { status: 'warning', task: 'RAM at 78% — monitoring', uptime: '14d 3h' },
    28→  radar: { status: 'healthy', task: 'Competitor scan: Hipages pricing', uptime: '8d 14h' },
    29→  herald: { status: 'healthy', task: 'Idle — last alert sent 2h ago', uptime: '14d 3h' },
    30→  cog: { status: 'offline', task: null, uptime: null },
    31→};
    32→
    33→// Demo: Tasks assigned TO Michael by agents
    34→// Replace with: useQuery(['my-tasks'], () => tasksApi.getByAgent('michael'))
    35→const MY_TASKS = [
    36→  { id: 1, title: 'Review Hipages pricing analysis', from: 'radar', priority: 'high', description: 'Hipages raised Gold tier 15%. Opportunity to position as value alternative.' },
    37→  { id: 2, title: 'Approve SMS re-engagement campaign', from: 'susan', priority: 'urgent', description: '45 cold leads dormant >30 days. Susan has the sequence ready.' },
    38→  { id: 3, title: 'Call lead #4821 — they responded', from: 'susan', priority: 'high', description: 'Hot lead, responded to SMS. Personal call recommended.' },
    39→  { id: 4, title: 'Review VPS security audit', from: 'sentinel', priority: 'medium', description: '3 unnecessary ports open. Sentinel recommends closing them.' },
    40→];
    41→
    42→const priorityStyles = {
    43→  urgent: 'text-red-400 bg-red-500/10 border-red-500/20',
    44→  high: 'text-amber-400 bg-amber-500/10 border-amber-500/20',
    45→  medium: 'text-blue-400 bg-blue-500/10 border-blue-500/20',
    46→  low: 'text-[var(--cc-text-muted)] bg-[var(--cc-surface)] border-[var(--cc-border)]',
    47→};
    48→
    49→export default function CommandCentre() {
    50→  const navigate = useNavigate();
    51→  const agents = getAgents();
    52→  const config = getConfig();
    53→
    54→  return (
    55→

    56→      {/* ── Mission Statement ────────────────────────────── */}
    57→      {config.mission && (
    58→

    59→          <Target className="w-5 h-5 text-[var(--cc-accent)] flex-shrink-0" />
    60→

    61→            Mission:{' '}
    62→            {config.mission}
    63→

    64→

    65→      )}
    66→
    67→      {/* ── Your Tasks (from agents) ─────────────────────── */}
    68→      {MY_TASKS.length > 0 && (
    69→

    70→

    71→

    72→              <Clock className="w-4 h-4" />
    73→              Your Tasks
    74→
    75→                {MY_TASKS.length}
    76→
    77→

    78→            <button
    79→              onClick={() => navigate('/tasks')}
    80→              className="text-[11px] text-[var(--cc-accent)] hover:underline flex items-center gap-1"
    81→            >
    82→              View all <ArrowRight className="w-3 h-3" />
    83→            </button>
    84→

    85→

    86→            {MY_TASKS.map((task) => {
    87→              const fromAgent = getAgent(task.from);
    88→              return (
    89→

    94→

    95→                    <button className="w-5 h-5 rounded-full border-2 border-[var(--cc-border)] hover:border-emerald-400 hover:bg-emerald-400/10 flex-shrink-0 mt-0.5 transition-colors flex items-center justify-center group">
    96→                      <CheckCircle2 className="w-3 h-3 text-emerald-400 opacity-0 group-hover:opacity-100 transition-opacity" />
    97→                    </button>
    98→

    99→
{task.title}

   100→
{task.description}

   101→

   102→                        {fromAgent?.avatar}
   103→                        from {fromAgent?.name}
   104→                        <span className={`text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded-full ${priorityStyles[task.priority]}`}>
   105→                          {task.priority}
   106→
   107→

   108→

   109→

   110→

   111→              );
   112→            })}
   113→

   114→

   115→      )}
   116→
   117→      {/* ── Quick Stats ────────────────────────────────── */}
   118→      <QuickStats
   119→        stats={{
   120→          agentsOnline: Object.values(DEMO_STATUSES).filter(s => s.status !== 'offline').length,
   121→          tasksCompleted: 23,
   122→          messagesTotal: 147,
   123→          apiCostToday: 4.82,
   124→        }}
   125→      />
   126→
   127→      {/* ── Agent Grid ─────────────────────────────────── */}
   128→

   129→

   130→          Fleet Status
   131→

   132→

   133→          {agents.map((agent) => {
   134→            const demo = DEMO_STATUSES[agent.id] || { status: 'offline' };
   135→            return (
   136→              <AgentCard
   137→                key={agent.id}
   138→                agent={agent}
   139→                status={demo.status}
   140→                task={demo.task}
   141→                uptime={demo.uptime}
   142→                onClick={() => navigate(`/fleet/${agent.id}`)}
   143→              />
   144→            );
   145→          })}
   146→

   147→

   148→
   149→      {/* ── Two Column: Approvals + Activity ───────────── */}
   150→

   151→        <PendingApprovals />
   152→        <ActivityFeed />
   153→

   154→

   155→  );
   156→}
   157→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

/**
 * Command Centre — Home Page
 * ==========================
 * Everything at a glance. The single pane of glass.
 *
 * Sections:
 * 1. Mission Statement — what the fleet is working towards
 * 2. Your Tasks — tasks agents have assigned to Michael (bidirectional)
 * 3. Quick Stats — agents online, tasks, messages, cost
 * 4. Agent Grid — 8 agent cards with live status
 * 5. Pending Approvals + Activity Feed side by side
 */
import { useNavigate } from 'react-router-dom';
import { getAgents, getAgent, getBranding, getConfig } from '../lib/fleet';
import AgentCard from '../components/fleet/AgentCard';
import QuickStats from '../components/dashboard/QuickStats';
import PendingApprovals from '../components/dashboard/PendingApprovals';
import ActivityFeed from '../components/dashboard/ActivityFeed';
import { Target, CheckCircle2, Clock, ArrowRight } from 'lucide-react';

// Demo status data — replace with useQuery({ queryKey: ['fleet-status'], queryFn: fleetApi.getStatus })
const DEMO_STATUSES = {
  rivet: { status: 'healthy', task: 'Monitoring fleet health, processing heartbeats', uptime: '14d 3h' },
  builder: { status: 'busy', task: 'Deploying Control Centre v0.1', uptime: '14d 3h' },
  susan: { status: 'healthy', task: 'Running SMS sequence batch #47', uptime: '12d 8h' },
  harper: { status: 'healthy', task: 'Idle — awaiting content task', uptime: '10d 1h' },
  sentinel: { status: 'warning', task: 'RAM at 78% — monitoring', uptime: '14d 3h' },
  radar: { status: 'healthy', task: 'Competitor scan: Hipages pricing', uptime: '8d 14h' },
  herald: { status: 'healthy', task: 'Idle — last alert sent 2h ago', uptime: '14d 3h' },
  cog: { status: 'offline', task: null, uptime: null },
};

// Demo: Tasks assigned TO Michael by agents
// Replace with: useQuery(['my-tasks'], () => tasksApi.getByAgent('michael'))
const MY_TASKS = [
  { id: 1, title: 'Review Hipages pricing analysis', from: 'radar', priority: 'high', description: 'Hipages raised Gold tier 15%. Opportunity to position as value alternative.' },
  { id: 2, title: 'Approve SMS re-engagement campaign', from: 'susan', priority: 'urgent', description: '45 cold leads dormant >30 days. Susan has the sequence ready.' },
  { id: 3, title: 'Call lead #4821 — they responded', from: 'susan', priority: 'high', description: 'Hot lead, responded to SMS. Personal call recommended.' },
  { id: 4, title: 'Review VPS security audit', from: 'sentinel', priority: 'medium', description: '3 unnecessary ports open. Sentinel recommends closing them.' },
];

const priorityStyles = {
  urgent: 'text-red-400 bg-red-500/10 border-red-500/20',
  high: 'text-amber-400 bg-amber-500/10 border-amber-500/20',
  medium: 'text-blue-400 bg-blue-500/10 border-blue-500/20',
  low: 'text-[var(--cc-text-muted)] bg-[var(--cc-surface)] border-[var(--cc-border)]',
};

export default function CommandCentre() {
  const navigate = useNavigate();
  const agents = getAgents();
  const config = getConfig();

  return (

      {/* ── Mission Statement ────────────────────────────── */}
      {config.mission && (

          <Target className="w-5 h-5 text-[var(--cc-accent)] flex-shrink-0" />

            Mission:{' '}
            {config.mission}

      )}

      {/* ── Your Tasks (from agents) ─────────────────────── */}
      {MY_TASKS.length > 0 && (

              <Clock className="w-4 h-4" />
              Your Tasks

                {MY_TASKS.length}

            <button
              onClick={() => navigate('/tasks')}
              className="text-[11px] text-[var(--cc-accent)] hover:underline flex items-center gap-1"
            >
              View all <ArrowRight className="w-3 h-3" />
            </button>

            {MY_TASKS.map((task) => {
              const fromAgent = getAgent(task.from);
              return (

                    <button className="w-5 h-5 rounded-full border-2 border-[var(--cc-border)] hover:border-emerald-400 hover:bg-emerald-400/10 flex-shrink-0 mt-0.5 transition-colors flex items-center justify-center group">
                      <CheckCircle2 className="w-3 h-3 text-emerald-400 opacity-0 group-hover:opacity-100 transition-opacity" />
                    </button>

{task.title}

{task.description}

                        {fromAgent?.avatar}
                        from {fromAgent?.name}
                        <span className={`text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded-full ${priorityStyles[task.priority]}`}>
                          {task.priority}

              );
            })}

      )}

      {/* ── Quick Stats ────────────────────────────────── */}
      <QuickStats
        stats={{
          agentsOnline: Object.values(DEMO_STATUSES).filter(s => s.status !== 'offline').length,
          tasksCompleted: 23,
          messagesTotal: 147,
          apiCostToday: 4.82,
        }}
      />

      {/* ── Agent Grid ─────────────────────────────────── */}

          Fleet Status

          {agents.map((agent) => {
            const demo = DEMO_STATUSES[agent.id] || { status: 'offline' };
            return (
              <AgentCard
                key={agent.id}
                agent={agent}
                status={demo.status}
                task={demo.task}
                uptime={demo.uptime}
                onClick={() => navigate(`/fleet/${agent.id}`)}
              />
            );
          })}

      {/* ── Two Column: Approvals + Activity ───────────── */}

        <PendingApprovals />
        <ActivityFeed />

  );
}


1→/**
     2→ * Tasks — Kanban Board
     3→ * ====================
     4→ * Drag-and-drop kanban with columns: Backlog → In Progress → Review → Done
     5→ * Filterable by agent, project, priority.
     6→ *
     7→ * DATA: cc_tasks table in Supabase (see CONTRIBUTING.md for schema)
     8→ * API: tasksApi in src/lib/api.js
     9→ *
    10→ * REPLACING DEMO DATA:
    11→ *   const { data: tasks } = useQuery({ queryKey: ['tasks'], queryFn: () => tasksApi.list() });
    12→ *   Then group by status into columns.
    13→ */
    14→import { useState, useCallback } from 'react';
    15→import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
    16→import Card from '../components/ui/Card';
    17→import Badge from '../components/ui/Badge';
    18→import { getAgent, getAgents, getProjects } from '../lib/fleet';
    19→import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';
    20→
    21→// ── Column definitions ────────────────────────────────────
    22→const COLUMNS = [
    23→  { id: 'backlog', label: 'Backlog', color: 'var(--cc-text-muted)' },
    24→  { id: 'in_progress', label: 'In Progress', color: 'var(--cc-accent)' },
    25→  { id: 'review', label: 'Review', color: 'var(--cc-warning)' },
    26→  { id: 'done', label: 'Done', color: 'var(--cc-healthy)' },
    27→];
    28→
    29→const PRIORITY_STYLES = {
    30→  urgent: { label: 'Urgent', variant: 'error' },
    31→  high:   { label: 'High', variant: 'warning' },
    32→  medium: { label: 'Medium', variant: 'default' },
    33→  low:    { label: 'Low', variant: 'offline' },
    34→};
    35→
    36→// ── Demo tasks ────────────────────────────────────────────
    37→const INITIAL_TASKS = [
    38→  { id: 't1', title: 'Fix SMS retry logic for Twilio 429s', status: 'backlog', priority: 'high', agent: 'builder', project: 'growth-engine', description: 'SMS sends are failing when Twilio rate-limits us. Need exponential backoff.' },
    39→  { id: 't2', title: 'Research Hipages Q1 pricing changes', status: 'backlog', priority: 'medium', agent: 'radar', project: 'rateright', description: 'Hipages raised Gold tier pricing. Analyze impact on our positioning.' },
    40→  { id: 't3', title: 'Write LinkedIn content calendar for March', status: 'backlog', priority: 'low', agent: 'harper', project: 'growth-engine', description: '4 posts per week covering hiring tips, platform features, customer stories.' },
    41→  { id: 't4', title: 'Deploy Control Centre to VPS', status: 'in_progress', priority: 'urgent', agent: 'builder', project: 'opsman', description: 'Build, deploy via PM2, configure Nginx reverse proxy on cc.rateright.com.au' },
    42→  { id: 't5', title: 'Run nightly lead scan — batch #48', status: 'in_progress', priority: 'high', agent: 'susan', project: 'rateright', description: 'Process 800+ leads from scraped sources, enrich, score, assign.' },
    43→  { id: 't6', title: 'Upgrade Node.js to v22', status: 'in_progress', priority: 'medium', agent: 'cog', project: 'opsman', description: 'Current v20 hitting memory limits with 8 concurrent agents.' },
    44→  { id: 't7', title: 'Review Builder deploy plan for GE v2.4', status: 'review', priority: 'high', agent: 'rivet', project: 'growth-engine', description: 'Builder proposed SMS retry + webhook hardening. Needs approval before deploy.' },
    45→  { id: 't8', title: 'Audit VPS security after Yakka threat', status: 'review', priority: 'urgent', agent: 'sentinel', project: 'opsman', description: 'Full port scan, check fail2ban rules, verify SSH key-only access.' },
    46→  { id: 't9', title: 'Published Q1 competitor analysis', status: 'done', priority: 'medium', agent: 'radar', project: 'rateright' },
    47→  { id: 't10', title: 'Fixed Telegram token rotation', status: 'done', priority: 'high', agent: 'cog', project: 'opsman' },
    48→  { id: 't11', title: 'SMS batch #47 — 12 follow-ups sent', status: 'done', priority: 'medium', agent: 'susan', project: 'rateright' },
    49→];
    50→
    51→// ── Create Task Modal ─────────────────────────────────────
    52→function CreateTaskModal({ onClose, onCreate }) {
    53→  const [title, setTitle] = useState('');
    54→  const [description, setDescription] = useState('');
    55→  const [priority, setPriority] = useState('medium');
    56→  const [agentId, setAgentId] = useState('');
    57→  const [projectId, setProjectId] = useState('');
    58→  const agents = getAgents();
    59→  const projects = getProjects();
    60→
    61→  const handleCreate = () => {
    62→    if (!title.trim()) return;
    63→    onCreate({
    64→      id: 't' + Date.now(),
    65→      title: title.trim(),
    66→      description: description.trim(),
    67→      priority,
    68→      agent: agentId,
    69→      project: projectId,
    70→      status: 'backlog',
    71→    });
    72→    onClose();
    73→  };
    74→
    75→  return (
    76→    <>
    77→

    78→

    79→

    80→
Create Task

    81→          <button onClick={onClose} className="p-1 rounded hover:bg-[var(--cc-surface-hover)]">
    82→            <X className="w-4 h-4 text-[var(--cc-text-muted)]" />
    83→          </button>
    84→

    85→

    86→          <input
    87→            type="text"
    88→            placeholder="Task title..."
    89→            value={title}
    90→            onChange={e => setTitle(e.target.value)}
    91→            onKeyDown={e => e.key === 'Enter' && handleCreate()}
    92→            autoFocus
    93→            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)]"
    94→          />
    95→          <textarea
    96→            placeholder="Description (optional)..."
    97→            value={description}
    98→            onChange={e => setDescription(e.target.value)}
    99→            rows={3}
   100→            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)] resize-none"
   101→          />
   102→

   103→            <select value={priority} onChange={e => setPriority(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
   104→              <option value="low">Low</option>
   105→              <option value="medium">Medium</option>
   106→              <option value="high">High</option>
   107→              <option value="urgent">Urgent</option>
   108→            </select>
   109→            <select value={agentId} onChange={e => setAgentId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
   110→              <option value="">Agent...</option>
   111→              {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
   112→            </select>
   113→            <select value={projectId} onChange={e => setProjectId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
   114→              <option value="">Project...</option>
   115→              {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
   116→            </select>
   117→

   118→

   119→

   120→          <button onClick={onClose} className="px-3 py-1.5 text-[12px] text-[var(--cc-text-secondary)] hover:text-[var(--cc-text)] transition-colors">
   121→            Cancel
   122→          </button>
   123→          <button
   124→            onClick={handleCreate}
   125→            disabled={!title.trim()}
   126→            className="px-4 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors disabled:opacity-40"
   127→          >
   128→            Create
   129→          </button>
   130→

   131→

   132→    </>
   133→  );
   134→}
   135→
   136→// ── Task Card (draggable) ─────────────────────────────────
   137→function TaskCard({ task, index }) {
   138→  const agent = getAgent(task.agent);
   139→  const priorityStyle = PRIORITY_STYLES[task.priority] || PRIORITY_STYLES.medium;
   140→
   141→  return (
   142→    <Draggable draggableId={task.id} index={index}>
   143→      {(provided, snapshot) => (
   144→

   151→          {/* Drag handle + priority */}
   152→

   153→

   154→              <GripVertical className="w-3 h-3 text-[var(--cc-text-muted)]" />
   155→

   156→

   157→              {task.title}
   158→

   159→

   160→
   161→          {/* Meta row */}
   162→

   163→            <Badge variant={priorityStyle.variant}>{priorityStyle.label}</Badge>
   164→            {agent && (
   165→
   166→                {agent.avatar}
   167→                {agent.name}
   168→
   169→            )}
   170→

   171→

   172→      )}
   173→    </Draggable>
   174→  );
   175→}
   176→
   177→// ── Main Component ────────────────────────────────────────
   178→export default function Tasks() {
   179→  const [tasks, setTasks] = useState(INITIAL_TASKS);
   180→  const [showCreate, setShowCreate] = useState(false);
   181→  const [filterAgent, setFilterAgent] = useState('');
   182→  const [filterProject, setFilterProject] = useState('');
   183→  const agents = getAgents();
   184→  const projects = getProjects();
   185→
   186→  // Filter tasks
   187→  const filtered = tasks.filter(t => {
   188→    if (filterAgent && t.agent !== filterAgent) return false;
   189→    if (filterProject && t.project !== filterProject) return false;
   190→    return true;
   191→  });
   192→
   193→  // Group by column
   194→  const columns = COLUMNS.map(col => ({
   195→    ...col,
   196→    tasks: filtered.filter(t => t.status === col.id),
   197→  }));
   198→
   199→  // Handle drag end
   200→  const onDragEnd = useCallback((result) => {
   201→    const { draggableId, destination } = result;
   202→    if (!destination) return;
   203→
   204→    setTasks(prev => prev.map(t =>
   205→      t.id === draggableId ? { ...t, status: destination.droppableId } : t
   206→    ));
   207→    // TODO: tasksApi.update(draggableId, { status: destination.droppableId })
   208→  }, []);
   209→
   210→  // Handle create
   211→  const handleCreate = (newTask) => {
   212→    setTasks(prev => [newTask, ...prev]);
   213→    // TODO: tasksApi.create(newTask)
   214→  };
   215→
   216→  return (
   217→

   218→      {/* Toolbar */}
   219→

   220→

   221→          <Filter className="w-4 h-4 text-[var(--cc-text-muted)]" />
   222→          <select
   223→            value={filterAgent}
   224→            onChange={e => setFilterAgent(e.target.value)}
   225→            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
   226→          >
   227→            <option value="">All agents</option>
   228→            {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
   229→          </select>
   230→          <select
   231→            value={filterProject}
   232→            onChange={e => setFilterProject(e.target.value)}
   233→            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
   234→          >
   235→            <option value="">All projects</option>
   236→            {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
   237→          </select>
   238→

   239→        <button
   240→          onClick={() => setShowCreate(true)}
   241→          className="flex items-center gap-1.5 px-3 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors"
   242→        >
   243→          <Plus className="w-3.5 h-3.5" />
   244→          New Task
   245→        </button>
   246→

   247→
   248→      {/* Kanban Board */}
   249→      <DragDropContext onDragEnd={onDragEnd}>
   250→

   251→          {columns.map((col) => (
   252→

   253→              {/* Column header */}
   254→

   255→

   256→

   257→                  {col.label}
   258→

   259→
   260→                  {col.tasks.length}
   261→
   262→

   263→
   264→              {/* Droppable column */}
   265→              <Droppable droppableId={col.id}>
   266→                {(provided, snapshot) => (
   267→

   274→                    {col.tasks.map((task, index) => (
   275→                      <TaskCard key={task.id} task={task} index={index} />
   276→                    ))}
   277→                    {provided.placeholder}
   278→                    {col.tasks.length === 0 && (
   279→

   280→                        No tasks
   281→

   282→                    )}
   283→

   284→                )}
   285→              </Droppable>
   286→

   287→          ))}
   288→

   289→      </DragDropContext>
   290→
   291→      {/* Create Modal */}
   292→      {showCreate && (
   293→        <CreateTaskModal
   294→          onClose={() => setShowCreate(false)}
   295→          onCreate={handleCreate}
   296→        />
   297→      )}
   298→

   299→  );
   300→}
   301→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

/**
 * Tasks — Kanban Board
 * ====================
 * Drag-and-drop kanban with columns: Backlog → In Progress → Review → Done
 * Filterable by agent, project, priority.
 *
 * DATA: cc_tasks table in Supabase (see CONTRIBUTING.md for schema)
 * API: tasksApi in src/lib/api.js
 *
 * REPLACING DEMO DATA:
 *   const { data: tasks } = useQuery({ queryKey: ['tasks'], queryFn: () => tasksApi.list() });
 *   Then group by status into columns.
 */
import { useState, useCallback } from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import Card from '../components/ui/Card';
import Badge from '../components/ui/Badge';
import { getAgent, getAgents, getProjects } from '../lib/fleet';
import { Plus, Filter, GripVertical, Calendar, X } from 'lucide-react';

// ── Column definitions ────────────────────────────────────
const COLUMNS = [
  { id: 'backlog', label: 'Backlog', color: 'var(--cc-text-muted)' },
  { id: 'in_progress', label: 'In Progress', color: 'var(--cc-accent)' },
  { id: 'review', label: 'Review', color: 'var(--cc-warning)' },
  { id: 'done', label: 'Done', color: 'var(--cc-healthy)' },
];

const PRIORITY_STYLES = {
  urgent: { label: 'Urgent', variant: 'error' },
  high:   { label: 'High', variant: 'warning' },
  medium: { label: 'Medium', variant: 'default' },
  low:    { label: 'Low', variant: 'offline' },
};

// ── Demo tasks ────────────────────────────────────────────
const INITIAL_TASKS = [
  { id: 't1', title: 'Fix SMS retry logic for Twilio 429s', status: 'backlog', priority: 'high', agent: 'builder', project: 'growth-engine', description: 'SMS sends are failing when Twilio rate-limits us. Need exponential backoff.' },
  { id: 't2', title: 'Research Hipages Q1 pricing changes', status: 'backlog', priority: 'medium', agent: 'radar', project: 'rateright', description: 'Hipages raised Gold tier pricing. Analyze impact on our positioning.' },
  { id: 't3', title: 'Write LinkedIn content calendar for March', status: 'backlog', priority: 'low', agent: 'harper', project: 'growth-engine', description: '4 posts per week covering hiring tips, platform features, customer stories.' },
  { id: 't4', title: 'Deploy Control Centre to VPS', status: 'in_progress', priority: 'urgent', agent: 'builder', project: 'opsman', description: 'Build, deploy via PM2, configure Nginx reverse proxy on cc.rateright.com.au' },
  { id: 't5', title: 'Run nightly lead scan — batch #48', status: 'in_progress', priority: 'high', agent: 'susan', project: 'rateright', description: 'Process 800+ leads from scraped sources, enrich, score, assign.' },
  { id: 't6', title: 'Upgrade Node.js to v22', status: 'in_progress', priority: 'medium', agent: 'cog', project: 'opsman', description: 'Current v20 hitting memory limits with 8 concurrent agents.' },
  { id: 't7', title: 'Review Builder deploy plan for GE v2.4', status: 'review', priority: 'high', agent: 'rivet', project: 'growth-engine', description: 'Builder proposed SMS retry + webhook hardening. Needs approval before deploy.' },
  { id: 't8', title: 'Audit VPS security after Yakka threat', status: 'review', priority: 'urgent', agent: 'sentinel', project: 'opsman', description: 'Full port scan, check fail2ban rules, verify SSH key-only access.' },
  { id: 't9', title: 'Published Q1 competitor analysis', status: 'done', priority: 'medium', agent: 'radar', project: 'rateright' },
  { id: 't10', title: 'Fixed Telegram token rotation', status: 'done', priority: 'high', agent: 'cog', project: 'opsman' },
  { id: 't11', title: 'SMS batch #47 — 12 follow-ups sent', status: 'done', priority: 'medium', agent: 'susan', project: 'rateright' },
];

// ── Create Task Modal ─────────────────────────────────────
function CreateTaskModal({ onClose, onCreate }) {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [priority, setPriority] = useState('medium');
  const [agentId, setAgentId] = useState('');
  const [projectId, setProjectId] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  const handleCreate = () => {
    if (!title.trim()) return;
    onCreate({
      id: 't' + Date.now(),
      title: title.trim(),
      description: description.trim(),
      priority,
      agent: agentId,
      project: projectId,
      status: 'backlog',
    });
    onClose();
  };

  return (
    <>

Create Task

          <button onClick={onClose} className="p-1 rounded hover:bg-[var(--cc-surface-hover)]">
            <X className="w-4 h-4 text-[var(--cc-text-muted)]" />
          </button>

          <input
            type="text"
            placeholder="Task title..."
            value={title}
            onChange={e => setTitle(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && handleCreate()}
            autoFocus
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)]"
          />
          <textarea
            placeholder="Description (optional)..."
            value={description}
            onChange={e => setDescription(e.target.value)}
            rows={3}
            className="w-full bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-3 py-2 text-[13px] text-[var(--cc-text)] placeholder-[var(--cc-text-muted)] outline-none focus:border-[var(--cc-accent)] resize-none"
          />

            <select value={priority} onChange={e => setPriority(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="low">Low</option>
              <option value="medium">Medium</option>
              <option value="high">High</option>
              <option value="urgent">Urgent</option>
            </select>
            <select value={agentId} onChange={e => setAgentId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Agent...</option>
              {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
            </select>
            <select value={projectId} onChange={e => setProjectId(e.target.value)} className="bg-[var(--cc-bg-raised)] border border-[var(--cc-border)] rounded-lg px-2 py-2 text-[12px] text-[var(--cc-text)] outline-none">
              <option value="">Project...</option>
              {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>

          <button onClick={onClose} className="px-3 py-1.5 text-[12px] text-[var(--cc-text-secondary)] hover:text-[var(--cc-text)] transition-colors">
            Cancel
          </button>
          <button
            onClick={handleCreate}
            disabled={!title.trim()}
            className="px-4 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors disabled:opacity-40"
          >
            Create
          </button>

    </>
  );
}

// ── Task Card (draggable) ─────────────────────────────────
function TaskCard({ task, index }) {
  const agent = getAgent(task.agent);
  const priorityStyle = PRIORITY_STYLES[task.priority] || PRIORITY_STYLES.medium;

  return (
    <Draggable draggableId={task.id} index={index}>
      {(provided, snapshot) => (

          {/* Drag handle + priority */}

              <GripVertical className="w-3 h-3 text-[var(--cc-text-muted)]" />

              {task.title}

          {/* Meta row */}

            <Badge variant={priorityStyle.variant}>{priorityStyle.label}</Badge>
            {agent && (

                {agent.avatar}
                {agent.name}

            )}

      )}
    </Draggable>
  );
}

// ── Main Component ────────────────────────────────────────
export default function Tasks() {
  const [tasks, setTasks] = useState(INITIAL_TASKS);
  const [showCreate, setShowCreate] = useState(false);
  const [filterAgent, setFilterAgent] = useState('');
  const [filterProject, setFilterProject] = useState('');
  const agents = getAgents();
  const projects = getProjects();

  // Filter tasks
  const filtered = tasks.filter(t => {
    if (filterAgent && t.agent !== filterAgent) return false;
    if (filterProject && t.project !== filterProject) return false;
    return true;
  });

  // Group by column
  const columns = COLUMNS.map(col => ({
    ...col,
    tasks: filtered.filter(t => t.status === col.id),
  }));

  // Handle drag end
  const onDragEnd = useCallback((result) => {
    const { draggableId, destination } = result;
    if (!destination) return;

    setTasks(prev => prev.map(t =>
      t.id === draggableId ? { ...t, status: destination.droppableId } : t
    ));
    // TODO: tasksApi.update(draggableId, { status: destination.droppableId })
  }, []);

  // Handle create
  const handleCreate = (newTask) => {
    setTasks(prev => [newTask, ...prev]);
    // TODO: tasksApi.create(newTask)
  };

  return (

      {/* Toolbar */}

          <Filter className="w-4 h-4 text-[var(--cc-text-muted)]" />
          <select
            value={filterAgent}
            onChange={e => setFilterAgent(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All agents</option>
            {agents.map(a => <option key={a.id} value={a.id}>{a.avatar} {a.name}</option>)}
          </select>
          <select
            value={filterProject}
            onChange={e => setFilterProject(e.target.value)}
            className="bg-[var(--cc-surface)] border border-[var(--cc-border)] rounded-lg px-2 py-1.5 text-[11px] text-[var(--cc-text)] outline-none"
          >
            <option value="">All projects</option>
            {projects.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
          </select>

        <button
          onClick={() => setShowCreate(true)}
          className="flex items-center gap-1.5 px-3 py-1.5 bg-[var(--cc-accent)] hover:bg-[var(--cc-accent-dim)] text-white text-[12px] font-medium rounded-lg transition-colors"
        >
          <Plus className="w-3.5 h-3.5" />
          New Task
        </button>

      {/* Kanban Board */}
      <DragDropContext onDragEnd={onDragEnd}>

          {columns.map((col) => (

              {/* Column header */}

                  {col.label}

                  {col.tasks.length}

              {/* Droppable column */}
              <Droppable droppableId={col.id}>
                {(provided, snapshot) => (

                    {col.tasks.map((task, index) => (
                      <TaskCard key={task.id} task={task} index={index} />
                    ))}
                    {provided.placeholder}
                    {col.tasks.length === 0 && (

                        No tasks

                    )}

                )}
              </Droppable>

          ))}

      </DragDropContext>

      {/* Create Modal */}
      {showCreate && (
        <CreateTaskModal
          onClose={() => setShowCreate(false)}
          onCreate={handleCreate}
        />
      )}

  );
}