// Assembly Line — task routing & refinement protocol.
// Ported from the original AI-Studio React app.
//
// The user explicitly wanted to see *the different states a task can be in* —
// so the page leads with a "Task States" gallery (a static reference of every
// state and substate), and then renders the live, interactive assembly line
// below it for context.

const { useState, useEffect } = React;

// --- Configuration ---
const STATIONS = [
  {
    id: "pm",
    title: "Project Management",
    humanReview: true,
    defaultFlow: ["p1", "p2"],
    validators: [{
      id: "m1", name: "PM Lead", role: "PM Lead",
      instructions: "Validate that every task has: a sharp objective tied to a metric, a numbered subtask list of ≤6 items, and acceptance criteria that are externally checkable. Reject anything that conflates 'how' with 'what'. Critical-path edge cases (billing, security, payouts) must be called out explicitly.",
    }],
    agents: [
      { id: "p1", name: "Scoper", role: "Scoper",
        instructions: "Translate fuzzy intake into a tight scope doc. Every task you ship must name (1) the user outcome, (2) the metric you'll move, (3) ≤6 subtasks, (4) acceptance criteria the validator can check without running code. If a request is ambiguous, ask one clarifying question; never guess." },
      { id: "p2", name: "Planner", role: "Planner",
        instructions: "Sequence work across stations. Identify dependencies, surface critical-path edge cases, and produce an execution order that minimizes idle handoffs. Always include rollback or kill-switch criteria for risky work." },
    ],
  },
  {
    id: "design",
    title: "Product Design",
    humanReview: true,
    defaultFlow: ["d1", "d2"],
    validators: [{
      id: "m2", name: "Design Lead", role: "Design Lead",
      instructions: "Validate that every design proposal: matches the brand voice doc, has at least 2 layout variants explored, names the primary metric, and ships with mobile + desktop frames. Reject anything that ignores accessibility (contrast, hit targets, keyboard) or skips the empty/error states.",
    }],
    agents: [
      { id: "d1", name: "UI Designer", role: "UI Designer",
        instructions: "Produce hi-fi designs grounded in the existing component library. Always sketch ≥3 directions before committing to one. Hand off with: hi-fi frames (mobile + desktop), copy in a separate review doc, and notes on motion/state changes." },
      { id: "d2", name: "UX Researcher", role: "UX Researcher",
        instructions: "Pressure-test interaction flows against real user data. Define success in terms of click-count or time-to-task. Always include keyboard, screen-reader, and empty/error states in your review. Cite the funnel data behind every recommendation." },
    ],
  },
  {
    id: "dev",
    title: "Development",
    humanReview: false,
    defaultFlow: ["e1", "e2"],
    validators: [{
      id: "m3", name: "Eng Lead", role: "Eng Lead",
      instructions: "Validate that every PR: passes type checks + tests in CI, has a rollback plan, doesn't widen the public API surface unnecessarily, and includes telemetry for the new code path. Reject if the diff touches critical-path systems (auth, billing) without an integration test.",
    }],
    agents: [
      { id: "e1", name: "Frontend", role: "Frontend",
        instructions: "Implement UI from approved design specs. Reuse existing primitives before adding new ones. Always include loading, empty, and error states. Add Storybook entries for new components and ensure keyboard parity with mouse." },
      { id: "e2", name: "Backend", role: "Backend",
        instructions: "Ship backwards-compatible APIs. Every migration ships with a dual-read shim and a tested rollback. Add structured logging at every external boundary. Never break an existing contract without a deprecation window of ≥2 releases." },
    ],
  },
];

const INITIAL_TASKS = [
  {
    id: "t-101",
    title: "Implement Auth Flow",
    source: { kind: "teamwork", ref: "TW-4821", url: "https://example.teamwork.com/#/tasks/4821" },
    description: "Setup OAuth and initial onboarding screens for new users coming in from marketing landing pages.",
    objective: "New users should be able to sign up with Google or GitHub in under 30 seconds and land in a populated empty state.",
    relevantFiles: ["src/auth/oauth.ts", "src/onboarding/Welcome.tsx", "docs/auth-flow.md"],
    subtasks: [
      { id: "s1", text: "Map out the OAuth redirect chain end-to-end", done: true },
      { id: "s2", text: "Define onboarding screen states and copy", done: false },
      { id: "s3", text: "List edge cases (existing email, denied scope)", done: false },
    ],
    acceptance: [
      { id: "a1", text: "Spec covers Google + GitHub flows", done: false },
      { id: "a2", text: "All edge cases have a defined path", done: false },
      { id: "a3", text: "Reviewed with security team", done: false },
    ],
    area: "pm",
    status: "queue",
    assigneeId: null,
    iteration: 1,
    events: [
      { id: "e1", phase: "created", iteration: 1, actor: null, when: "2 days ago", message: "Created and placed in PM queue." },
    ],
  },
  {
    id: "t-102",
    title: "Landing Page Redesign",
    source: { kind: "teamwork", ref: "TW-4933", url: "https://example.teamwork.com/#/tasks/4933" },
    description: "Update hero section and metrics row to match the new brand voice.",
    objective: "Hero conversion lifts by communicating the product value in under 5 seconds of skim.",
    relevantFiles: ["src/marketing/Landing.tsx", "src/marketing/Hero.tsx", "public/hero-illustration.svg"],
    subtasks: [
      { id: "s1", text: "Audit current hero copy + metrics", done: true },
      { id: "s2", text: "Sketch 3 hero compositions", done: true },
      { id: "s3", text: "Build hi-fi for top pick", done: false },
      { id: "s4", text: "Prepare responsive variants", done: false },
    ],
    acceptance: [
      { id: "a1", text: "Hero loads under 1.5s LCP", done: false },
      { id: "a2", text: "Mobile + desktop layouts approved", done: false },
      { id: "a3", text: "Copy approved by brand lead", done: false },
    ],
    area: "design",
    status: "working",
    assigneeId: "d1",
    iteration: 1,
    agentRuns: [
      {
        id: "r1",
        agent: { id: "p1", name: "Scoper", role: "Scoper" },
        station: "Project Management",
        stationId: "pm",
        iteration: 1,
        status: "approved",
        startedAt: "5 days ago",
        endedAt: "4 days ago",
        entries: [
          { kind: "thinking", text: "Need to clarify the desired outcome. Conversion is the headline metric — let me dig into the analytics to set a baseline." },
          { kind: "tool", tool: "read", path: "analytics/landing-conversion-2026q1.json" },
          { kind: "tool", tool: "read", path: "docs/marketing/north-star.md" },
          { kind: "thinking", text: "Baseline conversion 2.1%. North-star doc names hero clarity as the largest unblocked lever. Scoping a 4-subtask plan with explicit acceptance." },
          { kind: "tool", tool: "write", path: "tasks/t-102-scope.md", content: "# Hero Section Redesign — Scope\n\n## Why now\nConversion baseline 2.1%. Hero clarity is the highest-impact lever per north-star doc.\n\n## Subtasks\n1. Audit current hero copy + metrics\n2. Sketch 3 hero compositions\n3. Build hi-fi for top pick\n4. Prepare responsive variants\n\n## Acceptance\n- Hero loads under 1.5s LCP\n- Mobile + desktop layouts approved\n- Copy approved by brand lead\n" },
          { kind: "thinking", text: "Submitting for validation. PM Lead should sign off given metrics + clear acceptance." },
        ],
      },
      {
        id: "r2",
        agent: { id: "d1", name: "UI Designer", role: "UI Designer" },
        station: "Product Design",
        stationId: "design",
        iteration: 1,
        status: "active",
        startedAt: "1 day ago",
        endedAt: null,
        entries: [
          { kind: "thinking", text: "Auditing existing hero — need to know baseline metrics before proposing alternatives." },
          { kind: "tool", tool: "read", path: "src/components/Hero.tsx" },
          { kind: "tool", tool: "read", path: "analytics/hero-metrics-2026q1.json" },
          { kind: "thinking", text: "LCP is 2.1s — image is the long pole. Three composition directions worth sketching: image-led, type-led, split with embedded demo." },
          { kind: "tool", tool: "grep", query: "Hero|hero", path: "src/" },
          { kind: "tool", tool: "read", path: "docs/brand/voice.md" },
          { kind: "thinking", text: "Voice doc says \u201cconfident, never breathless.\u201d Will draft 3 headline candidates aligned to that." },
          { kind: "tool", tool: "write", path: "src/components/HeroV2.tsx", content: "// HeroV2 \u2014 type-led composition, deferred image, two-line headline.\nexport function HeroV2() {\n  return (\n    <section className=\"hero hero--type-led\">\n      <h1 className=\"hero__headline\">Ship work that lasts.</h1>\n      <p className=\"hero__sub\">A studio for teams who care about the long arc.</p>\n      <CTA primary>Start a project</CTA>\n    </section>\n  );\n}\n" },
          { kind: "thinking", text: "Type-led version is fastest to LCP. Drafting hi-fi next \u2014 will mark sub-task 3 once committed." },
          { kind: "tool", tool: "write", path: "design/hero-v2.fig-export.svg", content: "<svg ...> <!-- 1440\u00d7900 hero hi-fi, type-led variant --> </svg>" },
        ],
      },
    ],
    events: [
      { id: "e1", phase: "created", iteration: 1, actor: null, when: "5 days ago", message: "Created in PM Backlog." },
      { id: "e2", phase: "approved", iteration: 1, actor: { name: "PM Lead", role: "PM Lead" }, when: "4 days ago", message: "PM validation passed. Routed to Design.", station: "Project Management" },
      { id: "e3", phase: "assigned", iteration: 1, actor: { name: "UI Designer", role: "UI Designer" }, when: "1 day ago", message: "UI Designer picked up the task.", station: "Product Design" },
      { id: "e4", phase: "working", iteration: 1, actor: { name: "UI Designer", role: "UI Designer" }, when: "6 hours ago", message: "In progress — hero exploration underway.", station: "Product Design",
        snapshot: { subtasks: [
          { id: "s1", text: "Audit current hero copy + metrics", done: true },
          { id: "s2", text: "Sketch 3 hero compositions", done: true },
          { id: "s3", text: "Build hi-fi for top pick", done: false },
          { id: "s4", text: "Prepare responsive variants", done: false },
        ] } },
    ],
  },
  {
    id: "t-103",
    title: "Database Migration",
    source: { kind: "teamwork", ref: "TW-4877", url: "https://example.teamwork.com/#/tasks/4877" },
    description: "Migrate users table to v2 schema with backwards-compatible reads.",
    objective: "Move every existing user row to v2 with zero downtime and no broken sessions.",
    relevantFiles: ["migrations/2026_05_users_v2.sql", "src/db/users.ts", "docs/migration-runbook.md"],
    subtasks: [
      { id: "s1", text: "Write up + down migration", done: false },
      { id: "s2", text: "Dual-read shim for v1 callers", done: false },
      { id: "s3", text: "Backfill script with progress meter", done: false },
      { id: "s4", text: "Production dry-run on staging snapshot", done: false },
    ],
    acceptance: [
      { id: "a1", text: "Zero failed reads during cutover", done: false },
      { id: "a2", text: "Rollback plan tested", done: false },
      { id: "a3", text: "Backfill verified row-count parity", done: false },
    ],
    area: "dev",
    status: "queue",
    assigneeId: null,
    iteration: 1,
    events: [
      { id: "e1", phase: "created", iteration: 1, actor: null, when: "1 week ago", message: "Created in PM queue." },
      { id: "e2", phase: "approved", iteration: 1, actor: { name: "PM Lead", role: "PM Lead" }, when: "4 days ago", message: "PM validation passed.", station: "Project Management" },
      { id: "e3", phase: "approved", iteration: 1, actor: { name: "Design Lead", role: "Design Lead" }, when: "1 day ago", message: "Design validation passed. Routed to Development.", station: "Product Design" },
    ],
  },
  {
    id: "t-104",
    title: "Search Filters",
    source: { kind: "teamwork", ref: "TW-4961", url: "https://example.teamwork.com/#/tasks/4961" },
    description: "Add multi-faceted filtering with chips and a saved-presets dropdown.",
    objective: "Power users can narrow a 10k-result list to <50 in under 4 interactions.",
    relevantFiles: ["src/search/Filters.tsx", "src/search/PresetMenu.tsx", "src/search/types.ts"],
    subtasks: [
      { id: "s1", text: "Filter chip component with remove affordance", done: true },
      { id: "s2", text: "Preset save + recall flow", done: true },
      { id: "s3", text: "URL state sync", done: true },
      { id: "s4", text: "Empty state copy + illustration", done: true },
    ],
    acceptance: [
      { id: "a1", text: "All filter combos tested", done: true },
      { id: "a2", text: "Keyboard nav covers chip dismiss", done: false },
      { id: "a3", text: "Presets persist across sessions", done: false },
    ],
    area: "design",
    status: "human-review",
    assigneeId: "d2",
    iteration: 1,
    humanReviewThread: [
      {
        id: "hr-1",
        author: "Maya P.",
        avatar: "M",
        when: "1 hour ago",
        text: "The chip dismiss interaction skips keyboard users — Tab lands on the chip but Enter doesn't remove it. Please add Backspace + Delete handlers and confirm screen-reader announces the removal.",
      },
    ],
    agentRuns: [
      {
        id: "r1",
        agent: { id: "p2", name: "Planner", role: "Planner" },
        station: "Project Management",
        stationId: "pm",
        iteration: 1,
        status: "approved",
        startedAt: "5 days ago",
        endedAt: "5 days ago",
        entries: [
          { kind: "thinking", text: "Power users want fewer clicks to narrow down. Defining the success metric tightly: 10k → <50 results in ≤4 interactions." },
          { kind: "tool", tool: "read", path: "analytics/search-funnel.json" },
          { kind: "tool", tool: "write", path: "tasks/t-104-scope.md", content: "# Search Filters — Scope\n\n## Goal\nPower users narrow 10k results to <50 in <4 interactions.\n\n## Subtasks\n- Filter chip component\n- Preset save + recall\n- URL state sync\n- Empty state\n\n## Acceptance\n- All filter combos tested\n- Keyboard nav covers chip dismiss\n- Presets persist across sessions\n" },
        ],
      },
      {
        id: "r2",
        agent: { id: "d2", name: "UX Researcher", role: "UX Researcher" },
        station: "Product Design",
        stationId: "design",
        iteration: 1,
        status: "submitted",
        startedAt: "4 days ago",
        endedAt: "2 hours ago",
        entries: [
          { kind: "thinking", text: "Starting from the chip component since it\u2019s the visible primitive. Will sketch presets next." },
          { kind: "tool", tool: "read", path: "src/search/Filters.tsx" },
          { kind: "tool", tool: "read", path: "src/search/types.ts" },
          { kind: "tool", tool: "write", path: "src/search/FilterChip.tsx", content: "// FilterChip — pill with label + remove affordance + keyboard handler.\nexport function FilterChip({ label, onRemove }) {\n  return (\n    <span className=\"chip\" role=\"listitem\">\n      <span>{label}</span>\n      <button aria-label={`Remove ${label}`} onClick={onRemove}>×</button>\n    </span>\n  );\n}\n" },
          { kind: "thinking", text: "Presets need to round-trip through the URL so users can share filtered views. Encoding as compact base64 of the filter object." },
          { kind: "tool", tool: "write", path: "src/search/PresetMenu.tsx", content: "// Saved-presets dropdown with localStorage + URL sync.\nexport function PresetMenu({ filters, onApply }) {\n  /* ... */\n}\n" },
          { kind: "tool", tool: "write", path: "src/search/urlState.ts", content: "export function encodeFilters(f) { return btoa(JSON.stringify(f)); }\nexport function decodeFilters(s) { try { return JSON.parse(atob(s)); } catch { return null; } }\n" },
          { kind: "thinking", text: "All four sub-tasks done. Submitting to Design Lead for validation." },
        ],
      },
    ],
    events: [
      { id: "e1", phase: "created", iteration: 1, actor: null, when: "6 days ago", message: "Created in Backlog." },
      { id: "e2", phase: "approved", iteration: 1, actor: { name: "PM Lead", role: "PM Lead" }, when: "5 days ago", message: "PM validation passed.", station: "Project Management" },
      { id: "e3", phase: "assigned", iteration: 1, actor: { name: "UX Researcher", role: "UX Researcher" }, when: "4 days ago", message: "UX Researcher picked up the task.", station: "Product Design" },
      { id: "e4", phase: "submitted", iteration: 1, actor: { name: "UX Researcher", role: "UX Researcher" }, when: "2 hours ago", message: "All sub-tasks complete — submitted to Design Lead for validation.", station: "Product Design",
        snapshot: { subtasks: [
          { id: "s1", text: "Filter chip component with remove affordance", done: true },
          { id: "s2", text: "Preset save + recall flow", done: true },
          { id: "s3", text: "URL state sync", done: true },
          { id: "s4", text: "Empty state copy + illustration", done: true },
        ] } },
    ],
  },
  {
    id: "t-105",
    title: "Notifications v2",
    source: { kind: "teamwork", ref: "TW-5012", url: "https://example.teamwork.com/#/tasks/5012" },
    description: "Rework notification grouping and digest emails so users aren't drowned.",
    objective: "Cut notification volume per user by 40% without losing critical-event coverage.",
    relevantFiles: ["src/notifications/Group.ts", "src/notifications/Digest.ts", "docs/notification-policy.md"],
    subtasks: [
      { id: "s1", text: "Audit current notification taxonomy", done: true },
      { id: "s2", text: "Define grouping rules", done: false },
      { id: "s3", text: "Wireframe digest email", done: false },
    ],
    acceptance: [
      { id: "a1", text: "Volume reduction modeled on real data", done: false },
      { id: "a2", text: "Critical events still trip immediately", done: false },
      { id: "a3", text: "Digest opt-out is one click", done: false },
    ],
    area: "pm",
    status: "rejected",
    assigneeId: "p1",
    iteration: 2,
    agentRuns: [
      {
        id: "r1",
        agent: { id: "p1", name: "Scoper", role: "Scoper" },
        station: "Project Management",
        stationId: "pm",
        iteration: 1,
        status: "rejected",
        startedAt: "3 days ago",
        endedAt: "1 day ago",
        entries: [
          { kind: "thinking", text: "Volume is the headline complaint. Pulling current taxonomy + last 30 days of send data to model what could be grouped." },
          { kind: "tool", tool: "read", path: "src/notifications/Group.ts" },
          { kind: "tool", tool: "read", path: "docs/notification-policy.md" },
          { kind: "tool", tool: "grep", query: "send_notification", path: "src/notifications/" },
          { kind: "thinking", text: "If I window-group everything into a 15-minute bucket, modeled volume drops 47%. Drafting v1 scope around that single rule." },
          { kind: "tool", tool: "write", path: "tasks/t-105-scope-v1.md", content: "# Notifications v2 — Scope (v1)\n\n## Approach\nWindow-group every notification into a 15-minute bucket. Single, simple rule.\n\n## Acceptance\n- Volume reduction ≥ 40% on real data\n- Critical events still trip immediately\n- Digest opt-out is one click\n" },
          { kind: "thinking", text: "Submitting v1 to PM Lead. Volume math is solid." },
        ],
      },
      {
        id: "r2",
        agent: { id: "p1", name: "Scoper", role: "Scoper" },
        station: "Project Management",
        stationId: "pm",
        iteration: 2,
        status: "active",
        startedAt: "3 hours ago",
        endedAt: null,
        entries: [
          { kind: "thinking", text: "Re-reading Alice\u2019s feedback. Billing alerts must still trip immediately \u2014 my v1 grouping rule swept them up. Need a critical-event allow-list." },
          { kind: "tool", tool: "read", path: "src/notifications/Group.ts" },
          { kind: "tool", tool: "read", path: "docs/notification-policy.md" },
          { kind: "tool", tool: "grep", query: "billing|critical|urgent", path: "src/notifications/" },
          { kind: "thinking", text: "Eight event types qualify as critical (billing, security, account-lock, payout-failed). Carving them out of the grouping pipeline." },
          { kind: "tool", tool: "write", path: "src/notifications/Group.ts", content: "// v2 grouping with critical-event bypass.\nexport const CRITICAL_EVENTS = new Set([\n  'billing.charge_failed',\n  'billing.subscription_canceled',\n  'security.signin_anomaly',\n  'security.password_reset',\n  'account.locked',\n  'payouts.failed',\n  'payouts.held',\n  'compliance.action_required',\n]);\n\nexport function groupNotifications(events) {\n  const critical = events.filter(e => CRITICAL_EVENTS.has(e.type));\n  const groupable = events.filter(e => !CRITICAL_EVENTS.has(e.type));\n  return [...critical, ...windowGroup(groupable, '15m')];\n}\n" },
          { kind: "thinking", text: "Need to update the policy doc so the rule is canonical \u2014 future changes should reference the allow-list." },
          { kind: "tool", tool: "write", path: "docs/notification-policy.md", content: "# Notification Policy v2\n\n## Critical events bypass grouping\nEvents in `CRITICAL_EVENTS` (see `src/notifications/Group.ts`) are delivered immediately and never digested.\n\n## Everything else groups on a 15-minute window\n...\n" },
          { kind: "thinking", text: "Need to validate volume-reduction still holds after carve-out. Pulling last 30 days of data to model." },
        ],
      },
    ],
    events: [
      { id: "e1", phase: "created", iteration: 1, actor: null, when: "4 days ago", message: "Created in PM intake." },
      { id: "e2", phase: "assigned", iteration: 1, actor: { name: "Scoper", role: "Scoper" }, when: "3 days ago", message: "Scoper picked up the task.", station: "Project Management" },
      { id: "e3", phase: "submitted", iteration: 1, actor: { name: "Scoper", role: "Scoper" }, when: "2 days ago", message: "Submitted v1 scope to PM Lead.", station: "Project Management",
        snapshot: { subtasks: [
          { id: "s1", text: "Audit current notification taxonomy", done: true },
          { id: "s2", text: "Define grouping rules", done: true },
          { id: "s3", text: "Wireframe digest email", done: true },
        ] } },
      { id: "e4", phase: "rejected", iteration: 1, actor: { name: "PM Lead", role: "PM Lead" }, when: "1 day ago", message: "Rejected — grouping rules don't account for billing alerts. Please revise.", station: "Project Management",
        snapshot: { acceptance: [
          { id: "a1", text: "Volume reduction modeled on real data", done: true },
          { id: "a2", text: "Critical events still trip immediately", done: false },
          { id: "a3", text: "Digest opt-out is one click", done: true },
        ] },
        feedback: "Grouping rules don't account for billing alerts. Please ensure critical events still trip immediately." },
      { id: "e5", phase: "working", iteration: 2, actor: { name: "Scoper", role: "Scoper" }, when: "3 hours ago", message: "Scoper re-opened the task to address feedback (iteration 2).", station: "Project Management",
        snapshot: { subtasks: [
          { id: "s1", text: "Audit current notification taxonomy", done: true },
          { id: "s2", text: "Define grouping rules", done: false },
          { id: "s3", text: "Wireframe digest email", done: false },
        ] } },
    ],
  },
];

// --- Source chip: shows where the task came from (Teamwork) ---
// Clickable; opens the original record in a new tab. Stops propagation so
// clicks don't also open the task modal. Tasks enter the line from Teamwork
// only; unknown kinds (future producers) get a neutral chip so the format's
// free-form `source.kind` still degrades gracefully.
const TeamworkLogo = ({ size = 12 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" aria-hidden>
    <circle cx="12" cy="12" r="10" />
    <path d="M7 12.5l3 3 7-7" stroke="white" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);
const GenericSourceLogo = ({ size = 12 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" aria-hidden>
    <circle cx="12" cy="12" r="5" />
  </svg>
);

const SOURCE_META = {
  teamwork: { label: "Teamwork", logo: TeamworkLogo, cls: "bg-orange-50 text-orange-700 border-orange-200 hover:bg-orange-100" },
};

const SourceChip = ({ source, size = "sm" }) => {
  if (!source) return null;
  const meta = SOURCE_META[source.kind]
    ?? { label: source.kind, logo: GenericSourceLogo, cls: "bg-neutral-100 text-neutral-800 border-neutral-300 hover:bg-neutral-200" };
  const Logo = meta.logo;
  const sizing = size === "lg"
    ? "text-xs px-2.5 py-1 gap-1.5"
    : "text-[10px] px-1.5 py-0.5 gap-1";
  return (
    <a
      href={source.url}
      target="_blank"
      rel="noreferrer"
      onClick={(e) => e.stopPropagation()}
      title={source.ref ? `Open ${meta.label} ${source.ref}` : `Open in ${meta.label}`}
      className={`inline-flex items-center font-mono font-semibold border rounded-md leading-none transition-colors ${meta.cls} ${sizing}`}
    >
      <Logo size={size === "lg" ? 14 : 11} />
      {source.ref || meta.label}
    </a>
  );
};

window.SourceChip = SourceChip;

// --- Task Card (same one used in the live line + the gallery) ---
const TaskCard = ({ task, station, onAssign, onSubmit, onApprove, onReject, onOpen, onOpenAgent, onTogglePause, onTerminalRoute, demo = false, borderColor = null }) => {
  const isWorking = task.status === "working" || task.status === "rejected";
  const isValidating = task.status === "validating";
  const isHumanReview = task.status === "human-review";
  const isStuck = task.status === "stuck";
  // Effective border color: stuck → red and human-review → orange both override
  // any agent color; otherwise honor borderColor; fall back to neutral. Stuck
  // uses a red literal (TOKENS has no `red`, only `rose`) to match the badge/ring.
  const { TOKENS } = (typeof window !== 'undefined' ? window.AgentColors : null) || { TOKENS: {} };
  const colorName = isHumanReview ? 'orange' : (borderColor || 'neutral');
  const borderToken = isStuck
    ? { border: 'border-red-400' }
    : (TOKENS[colorName] || { border: 'border-neutral-300' });
  const assignedAgent = station?.agents.find((a) => a.id === task.assigneeId);
  const manager = window.Bridge?.primaryValidator(station);
  const isShipped = task.area === "done";
  const isPaused = !!task.paused;
  const lastComment = (task.humanReviewThread || [])[task.humanReviewThread?.length - 1];

  return (
    <div className="relative shrink-0 flex flex-col items-center w-[280px]">

      {/* Task card body */}
      <motion.div
        layout
        layoutId={`task-${task.id}`}
        initial={{ opacity: 0, scale: 0.9 }}
        animate={{ opacity: 1, scale: 1 }}
        onClick={demo || !onOpen ? undefined : () => onOpen(task)}
        className={`shrink-0 w-full relative z-10 bg-white rounded-xl shadow-md border-2 overflow-hidden transition-colors ${demo || !onOpen ? "" : "cursor-pointer hover:shadow-lg transition-shadow"} ${borderToken.border} ${isHumanReview ? 'ring-2 ring-orange-300 ring-offset-1' : ''} ${isStuck ? 'ring-2 ring-red-300 ring-offset-1' : ''}`}
      >
        <div className="p-4">
          {isHumanReview && (
            <div className="mb-2 inline-flex items-center gap-1.5 rounded-md bg-orange-100 border border-orange-300 px-2 py-1 text-[10px] font-bold uppercase tracking-widest text-orange-800">
              <span>⚠</span> Awaiting review
            </div>
          )}
          {isStuck && (
            <div className="mb-2 inline-flex items-center gap-1.5 rounded-md bg-red-100 border border-red-300 px-2 py-1 text-[10px] font-bold uppercase tracking-widest text-red-800">
              <span>⚠</span> Stuck — needs a human
            </div>
          )}
          <div className="flex justify-between items-start mb-2 gap-2 flex-wrap">
            <span className="text-xs font-mono text-neutral-500 bg-neutral-100 px-2 py-1 rounded-md inline-block">
              {task.id}
            </span>
            {task.source && <SourceChip source={task.source} />}
          </div>
          <h4 className="font-bold text-neutral-800 text-base leading-tight mb-2">{task.title}</h4>
          <p className="text-sm text-neutral-500 line-clamp-3 leading-relaxed">{task.description}</p>
          {isHumanReview && lastComment && (
            <div className="mt-3 bg-orange-50 border border-orange-200 rounded-lg p-2.5 flex items-start gap-2">
              <div className="w-6 h-6 rounded-full bg-orange-200 text-orange-800 flex items-center justify-center text-[11px] font-bold shrink-0">{lastComment.avatar || lastComment.author.charAt(0)}</div>
              <div className="min-w-0">
                <div className="flex items-center gap-1.5 mb-0.5">
                  <span className="text-[11px] font-bold text-orange-900 leading-none">{lastComment.author}</span>
                  <span className="text-[10px] text-orange-700/70 leading-none">· {lastComment.when}</span>
                </div>
                <p className="text-[12px] text-neutral-700 leading-snug line-clamp-2">{lastComment.text}</p>
              </div>
            </div>
          )}
          {isStuck && task.stuckReason && (
            <div className="mt-3 bg-red-50 border border-red-200 rounded-lg p-2.5">
              <div className="text-[10px] font-bold uppercase tracking-widest text-red-700 mb-1">
                Orchestrator diagnosis
              </div>
              <p className="text-[12px] text-neutral-700 leading-snug whitespace-pre-wrap line-clamp-4">{task.stuckReason}</p>
            </div>
          )}
        </div>

        <div className="bg-neutral-50 p-3 border-t border-neutral-100 flex items-center justify-between gap-2" onClick={(e) => e.stopPropagation()}>
          {isShipped ? (
            <div className="flex flex-col gap-1.5 w-full">
              <span className="text-xs font-bold text-emerald-600 w-full text-center py-2 bg-emerald-50 rounded-lg border border-emerald-100 flex items-center justify-center gap-1">
                <CheckCircle size={13} /> Shipped
              </span>
              {!demo && !task.readOnly && onTerminalRoute && (
                <div className="flex flex-col gap-1">
                  <div className="text-[9px] font-bold uppercase tracking-widest text-neutral-400 text-center mt-0.5">Route feedback</div>
                  <button
                    onClick={(e) => { e.stopPropagation(); onTerminalRoute(task, 'wrong-implementation'); }}
                    className="text-[11px] font-semibold text-neutral-700 bg-white border border-neutral-200 hover:border-red-300 hover:text-red-700 hover:bg-red-50 px-2 py-1.5 rounded-lg transition w-full flex items-center justify-center gap-1.5"
                  >
                    <RefreshCw size={10} /> Wrong implementation
                  </button>
                  <button
                    onClick={(e) => { e.stopPropagation(); onTerminalRoute(task, 'wrong-design'); }}
                    className="text-[11px] font-semibold text-neutral-700 bg-white border border-neutral-200 hover:border-orange-300 hover:text-orange-700 hover:bg-orange-50 px-2 py-1.5 rounded-lg transition w-full flex items-center justify-center gap-1.5"
                  >
                    <RefreshCw size={10} /> Wrong design
                  </button>
                  <button
                    onClick={(e) => { e.stopPropagation(); onTerminalRoute(task, 'scope-changed'); }}
                    className="text-[11px] font-semibold text-neutral-700 bg-white border border-neutral-200 hover:border-indigo-300 hover:text-indigo-700 hover:bg-indigo-50 px-2 py-1.5 rounded-lg transition w-full flex items-center justify-center gap-1.5"
                  >
                    <ArrowLeft size={10} /> Scope changed → Architect
                  </button>
                </div>
              )}
            </div>
          ) : task.readOnly ? (
            // Snapshot-mode viewer. When the viewer-server has Teamwork
            // write-back configured, human-review tickets carry action verbs
            // and the decision buttons act for real (move stage / tag in
            // Teamwork). Otherwise show a passive status — decisions happen
            // in Teamwork and arrive via the next sync.
            isStuck && (task.actions || []).includes('approve') ? (
              <div className="flex flex-col gap-2 w-full">
                <div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-red-800 bg-red-50 border border-red-200 rounded-md px-2 py-1.5">
                  <span>⚠</span> Stuck — your decision needed
                </div>
                <div className="flex gap-2 w-full">
                  <button
                    disabled={!!task.actionPending}
                    onClick={(e) => { e.stopPropagation(); onReject && onReject(task, station); }}
                    className="flex-1 text-xs font-bold bg-white text-red-600 border-2 border-red-200 px-2 py-2.5 rounded-lg hover:bg-red-50 transition disabled:opacity-40 disabled:cursor-not-allowed"
                  >
                    Rework
                  </button>
                  <button
                    disabled={!!task.actionPending}
                    onClick={(e) => { e.stopPropagation(); onApprove && onApprove(task); }}
                    className="flex-[2] text-xs font-bold bg-red-600 text-white px-2 py-2.5 rounded-lg hover:bg-red-700 transition flex items-center justify-center gap-1 disabled:opacity-40 disabled:cursor-not-allowed"
                  >
                    {task.actionPending ? 'Retrying…' : (<>Retry here <RefreshCw size={14} /></>)}
                  </button>
                </div>
              </div>
            ) : isStuck ? (
              <div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-red-800 bg-red-50 border border-red-200 rounded-md px-2 py-1.5 w-full">
                <span>⚠</span> Stuck — resolve in Teamwork
              </div>
            ) : isHumanReview && (task.actions || []).includes('approve') ? (
              <div className="flex flex-col gap-2 w-full">
                <div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-orange-800 bg-orange-50 border border-orange-200 rounded-md px-2 py-1.5">
                  <UserCircle size={12} /> Awaiting your review
                </div>
                <div className="flex gap-2 w-full">
                  <button
                    disabled={!!task.actionPending}
                    onClick={(e) => { e.stopPropagation(); onReject && onReject(task, station); }}
                    className="flex-1 text-xs font-bold bg-white text-red-600 border-2 border-red-200 px-2 py-2.5 rounded-lg hover:bg-red-50 transition disabled:opacity-40 disabled:cursor-not-allowed"
                  >
                    Reject
                  </button>
                  <button
                    disabled={!!task.actionPending}
                    onClick={(e) => { e.stopPropagation(); onApprove && onApprove(task); }}
                    className="flex-[2] text-xs font-bold bg-orange-500 text-white px-2 py-2.5 rounded-lg hover:bg-orange-600 transition flex items-center justify-center gap-1 disabled:opacity-40 disabled:cursor-not-allowed"
                  >
                    {task.actionPending ? (
                      <>
                        <svg className="animate-spin" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" aria-hidden>
                          <path d="M21 12a9 9 0 1 1-6.219-8.56" />
                        </svg>
                        Approving…
                      </>
                    ) : (
                      <>Approve <CheckCircle size={14} /></>
                    )}
                  </button>
                </div>
              </div>
            ) : isHumanReview ? (
              <div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-orange-800 bg-orange-50 border border-orange-200 rounded-md px-2 py-1.5 w-full">
                <UserCircle size={12} /> Awaiting review in Teamwork
              </div>
            ) : (
              <div className="flex items-center justify-center text-[10px] font-bold uppercase tracking-widest text-neutral-500 bg-white border border-neutral-200 rounded-md px-2 py-1.5 w-full">
                Read-only view
              </div>
            )
          ) : isHumanReview ? (
            // Human review — every task at this station needs a human approve/reject.
            <div className="flex flex-col gap-2 w-full">
              <div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-orange-800 bg-orange-50 border border-orange-200 rounded-md px-2 py-1.5">
                <UserCircle size={12} /> Awaiting your review
              </div>
              <div className="flex gap-2 w-full">
                <button
                  onClick={demo ? undefined : (e) => { e.stopPropagation(); onReject && onReject(task, station); }}
                  className="flex-1 text-xs font-bold bg-white text-red-600 border-2 border-red-200 px-2 py-2.5 rounded-lg hover:bg-red-50 transition"
                >
                  Reject
                </button>
                <button
                  onClick={demo ? undefined : (e) => { e.stopPropagation(); onApprove && onApprove(task); }}
                  className="flex-[2] text-xs font-bold bg-orange-500 text-white px-2 py-2.5 rounded-lg hover:bg-orange-600 transition flex items-center justify-center gap-1"
                >
                  Approve <CheckCircle size={14} />
                </button>
              </div>
              <button
                onClick={demo ? undefined : (e) => { e.stopPropagation(); onOpen?.(task, { tab: "humanReview" }); }}
                className="text-[11px] font-semibold text-orange-800 bg-white border border-orange-200 px-3 py-1.5 rounded-lg hover:bg-orange-50 transition w-full flex items-center justify-center gap-2"
              >
                <MessageSquare size={12} /> Open thread{(task.humanReviewThread || []).length > 0 ? ` (${task.humanReviewThread.length})` : ""}
              </button>
            </div>
          ) : isStuck ? (
            <div className="flex flex-col gap-2 w-full">
              <div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-red-800 bg-red-50 border border-red-200 rounded-md px-2 py-1.5">
                <span>⚠</span> Stuck — your decision needed
              </div>
              <div className="flex gap-2 w-full">
                <button
                  onClick={demo ? undefined : (e) => { e.stopPropagation(); onReject && onReject(task, station); }}
                  className="flex-1 text-xs font-bold bg-white text-red-600 border-2 border-red-200 px-2 py-2.5 rounded-lg hover:bg-red-50 transition"
                >
                  Rework
                </button>
                <button
                  onClick={demo ? undefined : (e) => { e.stopPropagation(); onApprove && onApprove(task); }}
                  className="flex-[2] text-xs font-bold bg-red-600 text-white px-2 py-2.5 rounded-lg hover:bg-red-700 transition flex items-center justify-center gap-1"
                >
                  Retry here <RefreshCw size={14} />
                </button>
              </div>
            </div>
          ) : isPaused ? (
            // Paused — reveal manual override buttons + Resume.
            <div className="flex flex-col gap-2 w-full">
              <div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-amber-700 bg-amber-50 border border-amber-200 rounded-md px-2 py-1.5">
                <PauseIcon /> Paused — manual control
              </div>

              {task.status === "queue" && station && (
                <button
                  onClick={demo ? undefined : () => onAssign(task, station)}
                  className="text-xs font-bold bg-neutral-900 text-white px-4 py-2.5 rounded-lg hover:bg-neutral-800 transition w-full shadow-sm"
                >
                  Work Task
                </button>
              )}

              {isWorking && station && (
                <button
                  onClick={demo ? undefined : () => onSubmit(task)}
                  className={`text-xs font-bold text-white px-4 py-2.5 rounded-lg transition w-full shadow-sm ${task.status === "rejected" ? "bg-red-600 hover:bg-red-700" : "bg-indigo-600 hover:bg-indigo-700"}`}
                >
                  Send to Validator
                </button>
              )}

              {task.status === "validating" && station && (
                <div className="flex gap-2 w-full">
                  <button
                    onClick={demo ? undefined : () => onReject(task, station)}
                    className="flex-1 text-xs font-bold bg-white text-red-600 border-2 border-red-200 px-2 py-2.5 rounded-lg hover:bg-red-50 transition"
                  >
                    Reject
                  </button>
                  <button
                    onClick={demo ? undefined : () => onApprove(task)}
                    className="flex-[2] text-xs font-bold bg-amber-500 text-white px-2 py-2.5 rounded-lg hover:bg-amber-600 transition flex items-center justify-center gap-1"
                  >
                    Approve <CheckCircle size={14} />
                  </button>
                </div>
              )}

              <button
                onClick={demo ? undefined : () => onTogglePause && onTogglePause(task)}
                className="text-xs font-semibold text-neutral-700 bg-white border border-neutral-300 px-3 py-2 rounded-lg hover:bg-neutral-100 transition w-full flex items-center justify-center gap-2"
              >
                <PlayIcon /> Resume auto
              </button>
            </div>
          ) : (
            // Default: line is running. Single Pause control to take over.
            <button
              onClick={demo ? undefined : (e) => { e.stopPropagation(); onTogglePause && onTogglePause(task); }}
              className="text-xs font-semibold text-neutral-600 bg-white border border-neutral-200 hover:border-amber-300 hover:text-amber-700 hover:bg-amber-50 px-3 py-2 rounded-lg transition w-full flex items-center justify-center gap-2"
              title="Pause this task to take manual control"
            >
              <PauseIcon /> Pause &amp; take over
            </button>
          )}
        </div>
      </motion.div>

      {/* Actor attachment (below) — agent for working/rejected, manager
          for validating. Card tops align in the row; the pill hangs below
          via a short tether line. */}
      {(isWorking && assignedAgent) || (isValidating && manager) ? (
        <div className="w-full flex flex-col items-center relative z-20 pointer-events-none">
          {isWorking && assignedAgent && (
            <motion.div
              initial={{ opacity: 0, y: -10 }}
              animate={{ opacity: 1, y: 0 }}
              className="pointer-events-auto flex flex-col items-center"
            >
              <div className={`w-1 h-8 ${task.status === "rejected" ? "bg-red-400" : "bg-indigo-400"}`}></div>
              <button
                type="button"
                onClick={(e) => {
                  e.stopPropagation();
                  if (onOpenAgent) onOpenAgent(assignedAgent, { station, task });
                  else if (!demo) onOpen?.(task, { tab: "agentLog" });
                }}
                title="View agent details"
                className={`px-4 py-2 bg-white rounded-full shadow-lg border-[2px] flex items-center gap-2 transition-all ${
                  task.status === "rejected" ? "border-red-400 text-red-700" : "border-indigo-400 text-indigo-700"
                } hover:scale-[1.03] hover:shadow-xl cursor-pointer`}
              >
                <div className={`w-8 h-8 rounded-full flex items-center justify-center font-bold text-white ${task.status === "rejected" ? "bg-red-500" : "bg-indigo-500"} ring-2 ring-white`}>
                  {assignedAgent.name.charAt(0)}
                </div>
                <div className="flex flex-col pr-1 items-start">
                  <span className="text-sm font-bold leading-none">{assignedAgent.name}</span>
                  <span className="text-[10px] font-semibold uppercase tracking-wider opacity-80">{task.status === "rejected" ? "Fixing" : "Working"}</span>
                </div>
                <span className={`ml-0.5 text-[10px] font-bold uppercase tracking-widest px-1.5 py-0.5 rounded ${task.status === "rejected" ? "bg-red-50 text-red-600 border border-red-200" : "bg-indigo-50 text-indigo-600 border border-indigo-200"}`}>
                  Info
                </span>
              </button>
            </motion.div>
          )}
          {isValidating && manager && (
            <motion.div
              initial={{ opacity: 0, y: -10 }}
              animate={{ opacity: 1, y: 0 }}
              className="pointer-events-auto flex flex-col items-center"
            >
              <div className="w-1 h-8 bg-amber-400"></div>
              <button
                type="button"
                onClick={(e) => { e.stopPropagation(); if (onOpenAgent) onOpenAgent(manager, { station, task }); }}
                title="View agent details"
                className="px-4 py-2 bg-white rounded-full shadow-lg border-[2px] border-amber-400 text-amber-700 flex items-center gap-2 hover:scale-[1.03] hover:shadow-xl cursor-pointer transition-all"
              >
                <div className="w-8 h-8 rounded-full flex items-center justify-center font-bold text-white bg-amber-500 ring-2 ring-white">
                  {manager.name.charAt(0)}
                </div>
                <div className="flex flex-col pr-1 items-start">
                  <span className="text-sm font-bold leading-none">{manager.name}</span>
                  <span className="text-[10px] font-semibold uppercase tracking-wider opacity-80">Validating</span>
                </div>
                <span className="ml-0.5 text-[10px] font-bold uppercase tracking-widest px-1.5 py-0.5 rounded bg-amber-50 text-amber-600 border border-amber-200">
                  Info
                </span>
              </button>
            </motion.div>
          )}
        </div>
      ) : null}
    </div>
  );
};

window.TaskCard = TaskCard;
window.STATIONS = STATIONS;
window.INITIAL_TASKS = INITIAL_TASKS;
