// Teklens.AI — Dashboard area (replaces Initiatives).
// Home/overview surface: Initiatives (accordion) → Folders → Desktop, + a collapsible Pulse panel.
// Reuses the global `Icon`, `ResizeHandle`, fonts and palette. Shares atoms/data with dashboard-detail.jsx.

// ============================================================
//  MODEL
// ============================================================
const DASH_PHASES = [
  { id: "discover", name: "Discover", color: "#3b5cf6" },
  { id: "define",   name: "Define",   color: "#8b5cf6" },
  { id: "build",    name: "Build",    color: "#0e7c66" },
  { id: "operate",  name: "Operate",  color: "#d97706" },
];
const dashPhaseIndex = (id) => DASH_PHASES.findIndex((p) => p.id === id);

// Artefact lifecycle states
const DASH_LIFE = {
  draft:     { label: "Draft",     bg: "#f3f1ec", text: "#6b6b6b", dot: "#a8a29a", border: "#e6e2d6" },
  published: { label: "Published", bg: "#e9f6ed", text: "#15803d", dot: "#16A34A", border: "#cdead5" },
  done:      { label: "Done",      bg: "#e4f5ef", text: "#0e7c66", dot: "#0e7c66", border: "#c4ead8" },
};
const DASH_TYPE_ICON = {
  Story: "story", PRD: "file", ADR: "shield", Epic: "hierarchy",
  "Feedback Summary": "search", "Feature analysis": "search", "Market scan": "globe",
  "Sprint report": "calendar", "Release notes": "file", "Decision log": "book",
  "Component audit": "hierarchy", "Tokens spec": "grid", Note: "edit", Spec: "file",
};

// People (humans) — Simon has a photo, others use tinted initials
const DASH_PEOPLE = {
  petra: { name: "Petra Bär",     initials: "PB", tint: "#b6892e" },
  lena:  { name: "Lena Bär",      initials: "LB", tint: "#c2410c" },
  simon: { name: "Simon Sutter",  initials: "SS", tint: "#3b5cf6", photo: "person-simon.webp" },
  maya:  { name: "Maya Costa",    initials: "MC", tint: "#8b5cf6" },
  tomas: { name: "Tomas Brunner", initials: "TB", tint: "#0e7c66" },
};

// Named automation agents (appear in feeds) — monogram badge in their colour
const DASH_AGENTS = {
  echo:   { name: "Echo",   color: "#16A34A" },
  scout:  { name: "Scout",  color: "#d6409f" },
  nox:    { name: "Nox",    color: "#d97706" },
  atlas:  { name: "Atlas",  color: "#3b5cf6" },
  sentry: { name: "Sentry", color: "#8b5cf6" },
  quill:  { name: "Quill",  color: "#7c5cf6" },
};

// Role agents (illustrated personas) — surfaced on the detail page per stage
const DASH_ROLES = {
  pm:  { title: "Product Manager",  avatar: "avatars/product-manager.webp" },
  po:  { title: "Product Owner",    avatar: "avatars/product-owner.webp" },
  ba:  { title: "Business Analyst", avatar: "avatars/business-analyst.webp" },
  ss:  { title: "Support Specialist", avatar: "avatars/support-specialist.webp" },
  pmm: { title: "Product Marketing", avatar: "avatars/product-marketeer.webp" },
};
const DASH_STAGE_ROLES = { discover: ["ba", "pm"], define: ["ba", "pm"], build: ["pm", "po"], operate: ["ss", "pmm"] };

// Capability presets per stage (the "examples of what you could now do").
// picker => the action needs a dropdown of entities before it runs.
const DASH_CAPS = {
  build: [
    { label: "Analyze current sprint progress", icon: "calendar", agent: "pm" },
    { label: "Detect dependency conflicts across squads", icon: "hierarchy", agent: "pm" },
    { label: "Propose next top 3 roadmap priorities", icon: "story", agent: "pm" },
    { label: "Run risk pass on epic", icon: "search", agent: "pm", picker: { ph: "Choose an epic…", opts: ["TEK-040 · Visual refresh", "TEK-052 · Reporting export", "TEK-061 · Broker dashboard"] } },
    { label: "Create release notes", icon: "file", agent: "pm" },
    { label: "Draft executive update — this sprint", icon: "message", agent: "pm" },
    { label: "Verify implementation completeness", icon: "check", agent: "po", picker: { ph: "Choose a story…", opts: ["TEK-041 · Add dark mode", "TEK-042 · Empty & loading states", "TEK-047 · Density toggle"] } },
    { label: "Highlight blocked stories in this sprint", icon: "circle-dot", agent: "po" },
    { label: "Generate sprint review summary", icon: "grid", agent: "po" },
    { label: "Estimate idea → draft spec", icon: "lab", agent: "po", picker: { ph: "Story in state Idea", opts: ["TEK-049 · Keyboard shortcuts", "TEK-051 · Theme presets"] } },
  ],
  define: [
    { label: "Split epic into draft stories", icon: "hierarchy", agent: "ba", picker: { ph: "Choose an epic…", opts: ["TEK-090 · Consent ledger", "TEK-093 · Revocation flow"] } },
    { label: "Generate acceptance criteria for story", icon: "check", agent: "ba", picker: { ph: "Choose a story…", opts: ["TEK-094 · Consent banner", "TEK-095 · Audit export"] } },
    { label: "Draft Confluence spec page from epic", icon: "file", agent: "ba" },
    { label: "List edge cases for current story", icon: "search", agent: "ba" },
    { label: "Run risk pass on epic", icon: "search", agent: "pm", picker: { ph: "Choose an epic…", opts: ["TEK-090 · Consent ledger", "TEK-093 · Revocation flow"] } },
    { label: "Score backlog ideas by RICE", icon: "story", agent: "pm" },
    { label: "Draft ADR for the data-retention decision", icon: "shield", agent: "ba" },
    { label: "Map requirement → story → test", icon: "circle-dot", agent: "ba" },
    { label: "Propose next top 3 roadmap priorities", icon: "story", agent: "pm" },
  ],
  discover: [
    { label: "Gather top 10 current feature requests", icon: "search", agent: "ba" },
    { label: "Summarize new support tickets this week", icon: "message", agent: "ba" },
    { label: "Estimate idea → draft spec", icon: "lab", agent: "pm", picker: { ph: "Story in state Idea", opts: ["TEK-140 · pain.001 builder", "TEK-141 · Payout schedule"] } },
    { label: "Draft opportunity canvas", icon: "grid", agent: "pm" },
    { label: "Summarize last month's competitor releases", icon: "globe", agent: "pm" },
    { label: "Condense research into a brief", icon: "file", agent: "ba" },
    { label: "Map the broker payout journey", icon: "hierarchy", agent: "ba" },
    { label: "List open questions for the epic", icon: "circle-dot", agent: "ba" },
    { label: "Propose top 3 roadmap candidates", icon: "story", agent: "pm" },
  ],
  operate: [
    { label: "Cluster this week's tickets by theme", icon: "grid", agent: "ss" },
    { label: "Draft customer reply for ticket", icon: "message", agent: "ss", picker: { ph: "Choose a ticket…", opts: ["#2291 · Export fails", "#2304 · Slow dashboard"] } },
    { label: "Suggest workaround from known issues", icon: "wand", agent: "ss" },
    { label: "Propose top 3 product changes from patterns", icon: "story", agent: "ss" },
    { label: "Compare adoption per persona for last launch", icon: "circle-dot", agent: "pmm" },
    { label: "Write customer changelog", icon: "edit", agent: "pmm" },
    { label: "Draft weekly support digest", icon: "file", agent: "ss" },
    { label: "Score message resonance from inbound", icon: "search", agent: "pmm" },
    { label: "Surface tickets blocked on missing docs", icon: "circle-dot", agent: "ss" },
  ],
};

// ---------- INITIATIVES ----------
const DASH_INITIATIVES = [
  {
    id: "lookfeel", code: "INI-31", name: "Improve overall look and feel", phase: "build", owner: "petra",
    domain: ["Teklens.AI", "design quick-wins"],
    desc: "Quick-wins to improve overall look and feel and implement the top 5 user feedbacks regarding UX and UI handling.",
    artefacts: [
      { id: "a-fa", type: "Feedback Summary", name: "Feature analysis", life: "published", stage: "discover" },
      { id: "a-41", type: "Story", name: "TEK-041 · Add dark mode", life: "draft", stage: "build" },
      { id: "a-42", type: "Story", name: "TEK-042 · Empty & loading states", life: "done", stage: "build" },
      { id: "a-47", type: "Story", name: "TEK-047 · Density toggle", life: "draft", stage: "define" },
    ],
    convos: [
      { id: "lf-c1", title: "Top-5 UX feedback synthesis", subtitle: "Density and dark mode dominate the requests; the five themes together cover roughly 70% of recent UX complaints.", time: "today · 11:20", people: ["petra"], agents: ["atlas"] },
      { id: "lf-c2", title: "Dark mode — token strategy", subtitle: "Settled on semantic tokens over per-component overrides; elevation and shadow handling are still open questions.", time: "today · 09:40", people: ["petra", "simon"], agents: ["quill"] },
      { id: "lf-c3", title: "Empty-state copy review", subtitle: "Copy is approved for all six empty states; nothing blocking, so this can ship together with TEK-042.", time: "yesterday", people: ["maya"], agents: ["quill"] },
      { id: "lf-c4", title: "Density toggle proposal", subtitle: "Leaning toward a per-user comfortable/compact setting behind a flag, watching task-time before any wider rollout.", time: "2d ago", people: ["petra"], agents: ["atlas"] },
    ],
    runs: [
      { id: "lf-r1", trigger: "schedule", title: "Daily standup summary", subtitle: "Build is tracking to the sprint goal; dark-mode tokens are the only item still at real risk of slipping.", time: "today · 17:00", status: "ok", artefact: { type: "Sprint report", name: "Standup summary · day 42" } },
      { id: "lf-r2", trigger: "event", title: "Story TEK-042 → Done", subtitle: "Empty & loading states satisfy every acceptance criterion; nothing left to verify, so the story is safe to close.", time: "today · 14:42", status: "ok", artefact: { type: "Note", name: "TEK-042 outcome" } },
      { id: "lf-r3", trigger: "schedule", title: "Daily bug summary", subtitle: "Two critical regressions are still open on the release; both now have owners and are expected to clear today.", time: "today · 09:00", status: "attention", artefact: { type: "Note", name: "Bug summary · day 42" } },
      { id: "lf-r4", trigger: "event", title: "New idea posted in Productboard", subtitle: "A new onboarding idea looks like a duplicate of an existing story, so it was linked rather than created fresh.", time: "yesterday · 16:18", status: "ok", artefact: { type: "Note", name: "Idea outcome · onboarding" } },
    ],
    memories: [
      { id: "lf-m1", title: "Top-5 feedback ranking", snippet: "Density, dark mode, empty states, keyboard nav, theme presets — ranked by ticket volume." },
      { id: "lf-m2", title: "Token naming agreed", snippet: "Surface / on-surface / accent tiers; semantic over literal. Two themes for now." },
      { id: "lf-m3", title: "A/B guardrail", snippet: "Ship behind a flag; roll back if task-time regresses > 5%." },
    ],
  },
  {
    id: "clientportal", code: "INI-21", name: "Client Portal Analytics & Reporting", phase: "build", owner: "lena",
    domain: ["Insurance platform", "broker self-service"],
    desc: "Self-serve reporting and analytics for brokers — portfolio KPIs, exports, and scheduled digests inside the client portal.",
    artefacts: [
      { id: "cp-prd", type: "PRD", name: "Reporting PRD", life: "published", stage: "define" },
      { id: "cp-52", type: "Story", name: "TEK-052 · CSV / XLSX export", life: "done", stage: "build" },
      { id: "cp-61", type: "Story", name: "TEK-061 · Broker KPI dashboard", life: "draft", stage: "build" },
      { id: "cp-adr", type: "ADR", name: "Aggregation store decision", life: "published", stage: "define" },
    ],
    convos: [
      { id: "cp-c1", title: "Broker KPI set — what matters", subtitle: "Brokers care most about renewals and payout timing; we settled on twelve KPIs with retention leading the set.", time: "today · 10:15", people: ["lena"], agents: ["atlas"] },
      { id: "cp-c2", title: "Export performance review", subtitle: "The slow export is fixed — a full year of data now returns in about a second, comfortably within budget.", time: "yesterday", people: ["lena", "tomas"], agents: ["sentry"] },
      { id: "cp-c3", title: "Scheduled digest opt-in flow", subtitle: "Opt-in at first export tested best of three variants — clearest consent and the least support friction.", time: "2d ago", people: ["maya"], agents: ["quill"] },
    ],
    runs: [
      { id: "cp-r1", trigger: "schedule", title: "Weekly reporting health", subtitle: "Reporting is healthy overall, but two dashboard queries are slow enough to put the latency budget at risk.", time: "today · 08:00", status: "attention", artefact: { type: "Sprint report", name: "Reporting health · wk22" } },
      { id: "cp-r2", trigger: "event", title: "Story TEK-052 → Done", subtitle: "Export latency stays within budget even on the largest broker accounts, so the story is ready to close.", time: "yesterday · 15:10", status: "ok", artefact: { type: "Note", name: "TEK-052 outcome" } },
    ],
    memories: [
      { id: "cp-m1", title: "Latency budget", snippet: "Export must complete < 2s p95 for 12-month range." },
      { id: "cp-m2", title: "KPI definitions", snippet: "Retention = renewed / due in window; agreed with broker council." },
    ],
  },
  {
    id: "consent", code: "INI-09", name: "Consent & data-privacy flow", phase: "define", owner: "maya",
    domain: ["Insurance platform", "compliance"],
    desc: "Consent capture, ledger and revocation across the platform — auditable and regulator-ready.",
    artefacts: [
      { id: "co-prd", type: "PRD", name: "Consent PRD", life: "draft", stage: "define" },
      { id: "co-adr", type: "ADR", name: "Consent-ledger store", life: "published", stage: "define" },
      { id: "co-90", type: "Epic", name: "TEK-090 · Consent ledger", life: "draft", stage: "define" },
    ],
    convos: [
      { id: "co-c1", title: "Revocation flow — PRD §4 challenge", subtitle: "Nox argued revocation has to purge derived data, not just flag it — the current §4 wording is too weak.", time: "12s ago", people: ["maya"], agents: ["nox"] },
      { id: "co-c2", title: "Decision log — retention window", subtitle: "Settled on a ten-year window for policy consent and two years for marketing; recorded in the ADR.", time: "24s ago", people: ["maya"], agents: ["quill"] },
      { id: "co-c3", title: "Regulator mapping workshop", subtitle: "Mapped six FINMA controls onto the consent design; two of them still need explicit audit-export evidence.", time: "yesterday", people: ["maya", "tomas"], agents: ["atlas"] },
    ],
    runs: [
      { id: "co-r1", trigger: "event", title: "PRD §4 updated", subtitle: "The retention rules in §4 now match the agreed decision, and the change is captured in the decision log.", time: "24s ago", status: "ok", artefact: { type: "Decision log", name: "ADR-09 · retention" } },
      { id: "co-r2", trigger: "schedule", title: "Weekly spec-drift scan", subtitle: "Spec and code disagree in two places on consent; both look like wording gaps rather than logic gaps.", time: "today · 10:00", status: "attention", artefact: { type: "Note", name: "Spec-drift · consent" } },
    ],
    memories: [
      { id: "co-m1", title: "Revocation is hard-delete", snippet: "Nox: revocation must purge derived data, not just flag it. Open." },
      { id: "co-m2", title: "Retention window", snippet: "10 years for policy consent per FINMA; 2 years for marketing." },
    ],
  },
  {
    id: "payments", code: "INI-14", name: "SEPA pain.001 payout builder", phase: "discover", owner: "tomas",
    domain: ["Payments", "broker payouts"],
    desc: "ISO 20022 pain.001 credit-transfer builder for broker commission payouts, with validation and dry-run.",
    artefacts: [
      { id: "pa-brief", type: "Feature analysis", name: "Payout discovery brief", life: "draft", stage: "discover" },
      { id: "pa-note", type: "Note", name: "ISO 20022 field map", life: "draft", stage: "discover" },
    ],
    convos: [
      { id: "pa-c1", title: "pain.001 edge-case sweep", subtitle: "Sentry surfaced fourteen edge cases; rounding and missing IBANs are the ones most likely to cause failures.", time: "32s ago", people: ["tomas"], agents: ["sentry"] },
      { id: "pa-c2", title: "Rappen rounding rule", subtitle: "Agreed CHF amounts round half-up to the nearest 0.05 before export, matching what the bank expects.", time: "today · 09:30", people: ["tomas", "simon"], agents: ["atlas"] },
    ],
    runs: [
      { id: "pa-r1", trigger: "event", title: "Edge-case tests generated", subtitle: "Fourteen pain.001 cases were generated; the rounding and IBAN cases are flagged as the highest risk.", time: "32s ago", status: "ok", artefact: { type: "Note", name: "pain.001 test outcome" } },
    ],
    memories: [
      { id: "pa-m1", title: "Rounding decision", snippet: "CHF amounts round half-up to 0.05 (Rappen) before pain.001 export." },
    ],
  },
  {
    id: "deflect", code: "INI-05", name: "Broker support deflection", phase: "operate", owner: "simon",
    domain: ["Support", "self-service"],
    desc: "Reduce broker ticket volume with in-product answers, fix-it suggestions and a smarter help surface.",
    artefacts: [
      { id: "de-dig", type: "Sprint report", name: "Weekly support digest", life: "published", stage: "operate" },
      { id: "de-note", type: "Note", name: "Deflection playbook", life: "published", stage: "operate" },
    ],
    convos: [
      { id: "de-c1", title: "Ticket #2291 — export fix", subtitle: "Echo traced the failure to a timeout and drafted a file-level fix; it just needs a quick review before merge.", time: "40s ago", people: ["simon"], agents: ["echo"] },
      { id: "de-c2", title: "Top deflection candidates", subtitle: "Five themes drive about 38% of tickets; export and login issues are the best in-product fix targets.", time: "today · 09:00", people: ["simon"], agents: ["echo", "scout"] },
    ],
    runs: [
      { id: "de-r1", trigger: "event", title: "Fix drafted for ticket #2291", subtitle: "A file-level fix and the customer reply are both ready; they are waiting only on a human sign-off.", time: "40s ago", status: "ok", artefact: { type: "Note", name: "#2291 outcome" } },
      { id: "de-r2", trigger: "schedule", title: "Weekly support digest", subtitle: "Ticket volume held flat; export issues remain the top theme and the clearest deflection opportunity.", time: "Mon · 09:00", status: "ok", artefact: { type: "Sprint report", name: "Support digest · wk22" } },
    ],
    memories: [
      { id: "de-m1", title: "Deflection target", snippet: "Cut export-related tickets 30% this quarter via in-product fix-it." },
    ],
  },
];
const dashInitById = (id) => DASH_INITIATIVES.find((i) => i.id === id);

// ---------- FOLDERS (no stages, no skills) ----------
const DASH_FOLDERS = [
  {
    id: "f-ds", code: "FLD-02", name: "Design system refresh", owner: "petra",
    desc: "Working container for the visual refresh — tokens, components and audits. No phase; grouped for convenience.",
    artefacts: [
      { id: "fd-tok", type: "Tokens spec", name: "Colour & spacing tokens", life: "published" },
      { id: "fd-aud", type: "Component audit", name: "Component inventory", life: "draft" },
      { id: "fd-adr", type: "ADR", name: "Icon library decision", life: "published" },
    ],
    convos: [
      { id: "fd-c1", title: "Token migration plan", subtitle: "Forty-two components mapped onto the new tokens; a handful still need manual review for custom shadows.", time: "today · 13:00", people: ["petra"], agents: ["quill"] },
      { id: "fd-c2", title: "Icon set consolidation", subtitle: "Three icon libraries collapse into one with no visual regressions, trimming about 40KB from initial load.", time: "yesterday", people: ["maya"], agents: ["atlas"] },
    ],
    runs: [
      { id: "fd-r1", trigger: "event", title: "Audit refreshed", subtitle: "The component inventory is current again; six components still rely on legacy spacing values.", time: "today · 13:05", status: "ok", artefact: { type: "Component audit", name: "Inventory outcome · v4" } },
    ],
  },
  {
    id: "f-cr", code: "FLD-05", name: "Competitive research", owner: "maya",
    desc: "Ongoing market and competitor scans. Not tied to a single initiative.",
    artefacts: [
      { id: "cr-scan", type: "Market scan", name: "Q2 competitor scan", life: "published" },
      { id: "cr-note", type: "Note", name: "Feature gap matrix", life: "draft" },
    ],
    convos: [
      { id: "cr-c1", title: "Competitor X — reporting launch", subtitle: "Competitor X reached rough parity on reporting; we still lead clearly on scheduled digests and exports.", time: "Mon · 08:00", people: ["maya"], agents: ["scout"] },
    ],
    runs: [
      { id: "cr-r1", trigger: "schedule", title: "Weekly market scan", subtitle: "Three competitor moves this week; only the reporting launch is material to our roadmap.", time: "Mon · 08:00", status: "ok", artefact: { type: "Market scan", name: "Scan outcome · wk22" } },
    ],
  },
];
const dashFolderById = (id) => DASH_FOLDERS.find((f) => f.id === id);

// ---------- DESKTOP (everything un-assigned) ----------
const DASH_DESKTOP = {
  id: "desktop", name: "Desktop", desc: "Everything not assigned to an Initiative or Folder lands here. Artefacts, plus the events and conversations that belong to them.",
  artefacts: [
    { id: "dk-a1", type: "Note", name: "Pricing experiment — back-of-envelope", life: "draft" },
    { id: "dk-a2", type: "ADR", name: "Draft: queue vs. cron for digests", life: "draft" },
    { id: "dk-a3", type: "Spec", name: "Webhook signature scheme", life: "published" },
  ],
  convos: [
    { id: "dk-c1", title: "Quick check — webhook retry policy", subtitle: "Agreed on a 3/3/7-day backoff before giving up, which matches what most partners already expect.", time: "today · 15:30", people: ["simon"], agents: ["atlas"] },
    { id: "dk-c2", title: "Pricing experiment framing", subtitle: "Compared usage-based and flat pricing; usage-based captures the upside better but adds billing complexity.", time: "3d ago", people: ["maya"], agents: ["atlas"] },
  ],
  runs: [
    { id: "dk-r1", trigger: "event", title: "Tag created · v2026.6-rc1", subtitle: "A release candidate was cut; the changelog stub is in place and waiting for notes to be filled in.", time: "today · 12:00", status: "ok", artefact: { type: "Note", name: "rc1 outcome" } },
  ],
};

// ---------- PULSE (live roll-up of EVERY conversation & run across all containers) ----------
// Not a separate data source — Pulse derives from the same convos/runs shown inside each
// initiative, folder and the desktop, merged into one continuous stream.
function dashPulseSources() {
  const out = [];
  DASH_INITIATIVES.forEach((it) => out.push({ code: it.code, name: it.name, phase: it.phase, kind: "initiative", convos: it.convos, runs: it.runs }));
  DASH_FOLDERS.forEach((f) => out.push({ code: f.code, name: f.name, phase: null, kind: "folder", convos: f.convos, runs: f.runs }));
  out.push({ code: "DESK", name: "Desktop", phase: null, kind: "desktop", convos: DASH_DESKTOP.convos, runs: DASH_DESKTOP.runs });
  return out;
}
// Coarse recency rank from a human time string ("12s", "today · 17:00", "yesterday", "Mon · 09:00"…)
function dashTimeRank(t) {
  const s = (t || "").toLowerCase();
  if (s.includes("now")) return 0;
  let m = s.match(/(\d+)\s*s\b/); if (m) return 1 + (+m[1]) / 1000;
  m = s.match(/(\d+)\s*m\b/); if (m && !s.includes("·")) return 2 + (+m[1]) / 1000;
  if (s.startsWith("today") || s.includes("today")) return 3 + (24 - (parseInt((s.match(/(\d{1,2}):/) || [])[1] || "0", 10))) / 100;
  if (s.includes("yesterday")) return 5;
  m = s.match(/(\d+)d/); if (m) return 6 + (+m[1]) / 100;
  return 8; // weekday / wk references
}
function dashPulseFeed() {
  const items = [];
  dashPulseSources().forEach((src) => {
    const container = { code: src.code, name: src.name, phase: src.phase, kind: src.kind };
    (src.convos || []).forEach((c) => items.push({ id: "pc-" + c.id, type: "conversation", title: c.title, subtitle: c.subtitle, time: c.time, people: c.people, agents: c.agents, container, joinable: true }));
    (src.runs || []).forEach((r) => items.push({ id: "pr-" + r.id, type: r.trigger === "event" ? "event" : "schedule", title: r.title, subtitle: r.subtitle, time: r.time, status: r.status, artefact: r.artefact, agents: [], people: [], container, joinable: r.trigger === "event" }));
  });
  return items.sort((a, b) => dashTimeRank(a.time) - dashTimeRank(b.time));
}
const DASH_PULSE_TYPE = {
  conversation: { label: "Conversation", color: "#d97706", icon: "message" },
  event:        { label: "Event",        color: "#2657c4", icon: "circle-dot" },
  schedule:     { label: "Schedule",     color: "#0e7c66", icon: "calendar" },
};

// ============================================================
//  ATOMS
// ============================================================
function PersonAvatar({ who, size = 22, ring = false }) {
  const p = DASH_PEOPLE[who] || { initials: "?", tint: "#9aa0a6" };
  const cls = ring ? "ring-2 ring-white rounded-full" : "rounded-full";
  if (p.photo) return <img src={window.__res ? window.__res(p.photo) : p.photo} alt={p.name} title={p.name} className={`${cls} object-cover shrink-0`} style={{ width: size, height: size }} />;
  return <span title={p.name} className={`inline-flex items-center justify-center text-white font-medium shrink-0 ${cls}`} style={{ width: size, height: size, background: p.tint, fontSize: size * 0.4 }}>{p.initials}</span>;
}
function AgentBadge({ id, size = 22, ring = false }) {
  const a = DASH_AGENTS[id] || { name: "Agent", color: "#9aa0a6" };
  const cls = ring ? "ring-2 ring-white" : "";
  return <span title={a.name} className={`inline-flex items-center justify-center text-white font-semibold shrink-0 rounded-[6px] ${cls}`} style={{ width: size, height: size, background: a.color, fontSize: size * 0.42, borderRadius: size * 0.3 }}>{a.name[0]}</span>;
}
// stacked avatars: agents (square) first, then people (round)
function DashAvatarStack({ agents = [], people = [], size = 22 }) {
  const items = [...agents.map((a) => ({ t: "a", id: a })), ...people.map((p) => ({ t: "p", id: p }))];
  return (
    <div className="flex items-center">
      {items.map((it, i) => (
        <span key={i} style={{ marginLeft: i === 0 ? 0 : -size * 0.34, zIndex: items.length - i }}>
          {it.t === "a" ? <AgentBadge id={it.id} size={size} ring /> : <PersonAvatar who={it.id} size={size} ring />}
        </span>
      ))}
    </div>
  );
}
function LifeBadge({ life, sm = false }) {
  const l = DASH_LIFE[life] || DASH_LIFE.draft;
  return (
    <span className="inline-flex items-center gap-1 rounded-full border font-medium whitespace-nowrap"
      style={{ background: l.bg, borderColor: l.border, color: l.text, fontSize: sm ? 10 : 10.5, padding: sm ? "1px 6px 1px 5px" : "2px 8px 2px 6px" }}>
      <span className="rounded-full" style={{ width: sm ? 5 : 5.5, height: sm ? 5 : 5.5, background: l.dot }} />{l.label}
    </span>
  );
}

// Stage stepper — strong emphasis on the active phase, no percentages.
function StageStepper({ phase, compact = false }) {
  const active = dashPhaseIndex(phase);
  return (
    <div className="flex items-stretch w-full select-none" style={{ height: compact ? 30 : 40 }}>
      {DASH_PHASES.flatMap((p, i) => {
        const done = i < active, cur = i === active;
        const node = (
          <div key={p.id} className="relative flex items-center gap-1.5 px-2.5 rounded-lg transition-colors"
            style={cur ? { background: p.color, color: "#fff", boxShadow: "0 1px 2px rgba(0,0,0,0.12)" } : {}}>
            <span className="rounded-full shrink-0" style={{ width: cur ? 7 : 6, height: cur ? 7 : 6, background: cur ? "#fff" : done ? p.color : "#c3bdac" }} />
            <span className="font-semibold whitespace-nowrap" style={{ fontSize: compact ? 11.5 : 12.5, color: cur ? "#fff" : done ? "#3a3a3a" : "#9aa0a6", fontWeight: cur ? 700 : done ? 600 : 500 }}>{p.name}</span>
            {done && <Icon name="check" size={compact ? 11 : 12} stroke={2.6} style={{ color: p.color }} />}
          </div>
        );
        const bar = i < DASH_PHASES.length - 1
          ? <div key={p.id + "-bar"} className="flex-1 self-center mx-0.5 rounded-full" style={{ height: 3, background: i < active ? DASH_PHASES[i].color : "#e6e2d6" }} />
          : null;
        return bar ? [node, bar] : [node];
      })}
    </div>
  );
}

// Advance-to-next-stage control (the exit gate)
function AdvanceStage({ phase, name }) {
  const [open, setOpen] = useState(false);
  const i = dashPhaseIndex(phase);
  const next = DASH_PHASES[i + 1];
  const cur = DASH_PHASES[i];
  if (!next) return (
    <span className="shrink-0 inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-[11.5px] font-medium text-[#15803d] bg-[#e9f6ed] border border-[#cdead5]" title="Operate is the final phase"><Icon name="check" size={13} /> Final phase</span>
  );
  return (
    <>
      <button onClick={() => setOpen(true)} title={`Exit-gate decision — promote this phase's artefacts and advance to ${next.name}`}
        className="shrink-0 inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-[11.5px] font-medium border border-[var(--accent)]/35 text-[var(--accent)] bg-[var(--accent)]/[0.07] hover:bg-[var(--accent)]/[0.14] whitespace-nowrap">
        Advance to {next.name} <Icon name="chevron-right" size={13} />
      </button>
      {open && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 p-6" onClick={() => setOpen(false)}>
          <div className="w-[440px] bg-white rounded-2xl border border-[#ece7da] shadow-2xl overflow-hidden" onClick={(e) => e.stopPropagation()}>
            <div className="px-5 py-3.5 border-b border-[#f0ebdd] flex items-center gap-2">
              <Icon name="chevron-right" size={16} className="text-[var(--accent)]" />
              <span className="text-[14px] font-semibold text-[#0e0e10]">Advance the phase</span>
              <button onClick={() => setOpen(false)} className="ml-auto p-1 rounded hover:bg-black/[0.05] text-[#9aa0a6]"><Icon name="x" size={15} /></button>
            </div>
            <div className="px-5 py-4">
              <div className="flex items-center gap-2.5">
                <span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md border" style={{ background: cur.color + "12", borderColor: cur.color + "33", color: cur.color }}><span className="rounded-full" style={{ width: 6, height: 6, background: cur.color }} /><span className="text-[11.5px] font-semibold">{cur.name}</span></span>
                <Icon name="chevron-right" size={15} className="text-[#cbcbcb]" />
                <span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md border" style={{ background: next.color + "12", borderColor: next.color + "33", color: next.color }}><span className="rounded-full" style={{ width: 6, height: 6, background: next.color }} /><span className="text-[11.5px] font-semibold">{next.name}</span></span>
              </div>
              <p className="mt-3.5 text-[12.5px] text-[#3a3a3a] leading-relaxed">Closing <span className="font-medium text-[#0e0e10]">{cur.name}</span>{name ? <> on <span className="font-medium text-[#0e0e10]">{name}</span></> : null} is the <span className="font-medium">exit-gate decision</span>. The phase's approved artefacts are promoted to their source systems, and <span className="font-medium text-[#0e0e10]">{next.name}</span> becomes the active phase.</p>
              <div className="mt-3.5 flex items-start gap-2 text-[11.5px] text-[#9a6516] bg-[#fdf3e3] border border-[#f2dfbe] rounded-lg px-3 py-2">
                <Icon name="shield" size={14} className="mt-0.5 shrink-0" />
                <span>Any project member can approve the gate. Draft artefacts stay draft until promoted.</span>
              </div>
            </div>
            <div className="px-5 py-3 border-t border-[#f0ebdd] bg-[#faf8f3] flex items-center justify-end gap-2">
              <button onClick={() => setOpen(false)} className="px-3 py-1.5 rounded-md text-[12.5px] text-[#5a5a5a] hover:bg-black/[0.05]">Cancel</button>
              <button onClick={() => setOpen(false)} className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-md bg-[var(--accent)] text-white text-[12.5px] font-medium hover:brightness-110"><Icon name="check" size={14} /> Confirm &amp; advance</button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

// Active-phase pill (collapsed line item)
function PhaseChip({ phase }) {
  const p = DASH_PHASES[dashPhaseIndex(phase)];
  return (
    <span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md border whitespace-nowrap"
      style={{ background: p.color + "12", borderColor: p.color + "33", color: p.color }}>
      <span className="rounded-full" style={{ width: 6, height: 6, background: p.color }} />
      <span className="text-[11.5px] font-semibold">{p.name}</span>
    </span>
  );
}

// Artefact count summary — published+done over total
function ArtefactCount({ artefacts, onClick }) {
  const ready = artefacts.filter((a) => a.life === "published" || a.life === "done").length;
  return (
    <span onClick={onClick} className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-[#f4f1ea] text-[#5a5a5a]" title={`${artefacts.length} artefacts · ${ready} published or done`}>
      <Icon name="check" size={12} className="text-[var(--accent)]" />
      <span className="text-[11.5px] font-medium tabular-nums">{ready}<span className="text-[#9aa0a6]"> / {artefacts.length}</span></span>
    </span>
  );
}

// Stage pip — a badge in the phase colour (matches the stepper)
function StagePip({ phase }) {
  const p = DASH_PHASES[dashPhaseIndex(phase)];
  if (!p) return null;
  return (
    <span className="inline-flex items-center gap-1 rounded-full border font-medium whitespace-nowrap shrink-0" style={{ background: p.color + "14", borderColor: p.color + "3a", color: p.color, fontSize: 10.5, padding: "1px 7px 1px 6px" }} title={`Phase · ${p.name}`}>
      <span className="rounded-full" style={{ width: 5, height: 5, background: p.color }} />{p.name}
    </span>
  );
}

// One artefact row (draggable). Used in expanded/folders/desktop.
function ArtefactRow({ a, onOpen, onDragStart }) {
  return (
    <div draggable onDragStart={onDragStart}
      className="group flex items-center gap-2.5 px-3 py-2 rounded-lg border border-[#ece7da] bg-white hover:border-[var(--accent)]/45 hover:shadow-[0_1px_3px_rgba(0,0,0,0.05)] transition-all cursor-grab active:cursor-grabbing">
      <span className="w-7 h-7 rounded-md flex items-center justify-center shrink-0 bg-[#f4f1ea] text-[#6b6b6b]"><Icon name={DASH_TYPE_ICON[a.type] || "file"} size={14} /></span>
      <div className="min-w-0 flex-1">
        <div className="text-[13px] font-medium text-[#0e0e10] truncate leading-tight">{a.name}</div>
        <div className="text-[11px] text-[#9aa0a6] truncate">{a.type}</div>
      </div>
      <LifeBadge life={a.life} />
      {a.stage && <StagePip phase={a.stage} />}
      <button onClick={onOpen} className="opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded text-[#9aa0a6] hover:text-[var(--accent)] hover:bg-black/[0.04]" title="Open in conversation (context)"><Icon name="external" size={14} /></button>
    </div>
  );
}

// Conversation card (joinable) — agents + people avatars
function ConvoCard({ c, onJoin, dense = false }) {
  return (
    <button onClick={onJoin} className="group w-full text-left flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-[#faf8f3] border border-transparent hover:border-[#ece7da] transition-colors">
      <DashAvatarStack agents={c.agents} people={c.people} size={dense ? 20 : 24} />
      <div className="min-w-0 flex-1">
        <div className="text-[13px] font-medium text-[#0e0e10] truncate leading-tight">{c.title}</div>
        {c.subtitle && <div className="text-[11.5px] text-[#9aa0a6] line-clamp-2 mt-0.5 leading-snug">{c.subtitle}</div>}
      </div>
      <span className="text-[10.5px] text-[#9aa0a6] shrink-0 group-hover:hidden tabular-nums self-start mt-0.5">{c.time}</span>
      <span className="hidden group-hover:inline-flex items-center gap-1 text-[11.5px] font-medium text-[var(--accent)] shrink-0 px-2 py-1 rounded-md bg-[var(--accent)]/[0.08]"><Icon name="plus" size={12} /> Join</span>
    </button>
  );
}

// Run card (schedule/event) — produces an artefact
function RunCard({ r, onOpen, dense = false }) {
  const isEvent = r.trigger === "event";
  const dot = r.status === "attention" ? "#d97706" : "#16A34A";
  return (
    <button onClick={onOpen} className="group w-full text-left flex items-start gap-2.5 px-3 py-2.5 rounded-lg hover:bg-[#faf8f3] border border-transparent hover:border-[#ece7da] transition-colors">
      <span className="mt-1.5 rounded-full shrink-0" style={{ width: 7, height: 7, background: dot }} />
      <div className="min-w-0 flex-1">
        <div className="flex items-center gap-1.5">
          <span className="text-[9px] font-bold uppercase tracking-[0.07em] px-1.5 py-0.5 rounded shrink-0"
            style={{ background: isEvent ? "#eef3fe" : "#f1efe8", color: isEvent ? "#2657c4" : "#6b6b6b" }}>{isEvent ? "Event" : "Schedule"}</span>
          <span className="text-[13px] font-medium text-[#0e0e10] truncate">{r.title}</span>
          <span className="text-[10.5px] text-[#9aa0a6] shrink-0 ml-auto tabular-nums">{r.time}</span>
        </div>
        <div className="text-[11.5px] text-[#9aa0a6] line-clamp-2 mt-0.5 leading-snug">{r.subtitle}</div>
        {r.artefact && (
          <div className="mt-1 inline-flex items-center gap-1 text-[10.5px] text-[#6b6b6b]">
            <Icon name={DASH_TYPE_ICON[r.artefact.type] || "file"} size={11} className="text-[#9b8866]" />
            <span className="truncate">{r.artefact.name}</span>
          </div>
        )}
      </div>
    </button>
  );
}

// Capability card (the "what you could do" items). picker => shows a dropdown.
function CapabilityCard({ cap, onRun }) {
  const [open, setOpen] = useState(false);
  const [picked, setPicked] = useState(null);
  if (!cap.picker) {
    return (
      <button onClick={() => onRun(cap.label)} className="group w-full flex items-center gap-3 px-3.5 py-3 rounded-xl border border-[#ece7da] bg-white hover:border-[var(--accent)]/45 hover:shadow-[0_1px_3px_rgba(0,0,0,0.05)] transition-all text-left">
        <span className="w-8 h-8 rounded-lg flex items-center justify-center shrink-0 bg-[#f4f1ea] text-[#5a5a5a] group-hover:text-[var(--accent)]"><Icon name={cap.icon} size={16} /></span>
        <span className="text-[13px] font-medium text-[#0e0e10] flex-1">{cap.label}</span>
        <Icon name="chevron-right" size={16} className="text-[#cbcbcb] group-hover:text-[var(--accent)]" />
      </button>
    );
  }
  return (
    <div className="relative">
      <button onClick={() => setOpen((o) => !o)} className={`group w-full flex items-center gap-3 px-3.5 py-2.5 rounded-xl border bg-white hover:shadow-[0_1px_3px_rgba(0,0,0,0.05)] transition-all text-left ${open ? "border-[var(--accent)] ring-2 ring-[var(--accent)]/15" : "border-[var(--accent)]/30 hover:border-[var(--accent)]/55"}`}>
        <span className="w-8 h-8 rounded-lg flex items-center justify-center shrink-0 bg-[var(--accent)]/[0.08] text-[var(--accent)]"><Icon name={cap.icon} size={16} /></span>
        <span className="flex-1 min-w-0">
          <span className="block text-[13px] font-medium text-[#0e0e10] truncate">{cap.label}</span>
          <span className="block text-[11px] mt-0.5 truncate font-medium text-[var(--accent)]">{picked || cap.picker.ph}</span>
        </span>
        <span className="shrink-0 inline-flex items-center gap-1 px-2 py-1 rounded-md border border-[var(--accent)]/35 bg-[var(--accent)]/[0.07] text-[var(--accent)] text-[11px] font-semibold">{picked ? "Change" : "Select"} <Icon name="chevron-down" size={13} /></span>
      </button>
      {open && (
        <>
          <div className="fixed inset-0 z-30" onClick={() => setOpen(false)} />
          <div className="absolute left-0 right-0 top-full mt-1 bg-white rounded-xl border border-[#ece7da] shadow-xl z-40 py-1 overflow-hidden">
            <div className="px-3 py-1.5 text-[10.5px] uppercase tracking-[0.06em] text-[#9aa0a6] font-semibold">{cap.picker.ph}</div>
            {cap.picker.opts.map((o) => (
              <button key={o} onClick={() => { setPicked(o); setOpen(false); onRun(`${cap.label}: ${o}`); }}
                className="w-full flex items-center gap-2 px-3 py-1.5 text-[12.5px] text-[#1f1f1f] hover:bg-black/[0.04] text-left">
                <Icon name={cap.icon} size={12} className="text-[#9aa0a6] shrink-0" /><span className="truncate">{o}</span>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// Pinned-initiative composer (Ask Teklens, light, matches the app)
function PinnedComposer({ pinLabel, pinIcon = "grid", model = "Gemini 3 Flash", onSubmit }) {
  const [val, setVal] = useState("");
  return (
    <div className="bg-white rounded-2xl border border-[#ece7da] shadow-[0_1px_2px_rgba(0,0,0,0.04)] focus-within:border-[var(--accent)]">
      <div className="flex flex-wrap items-center gap-1.5 px-3 pt-2.5">
        <span className="inline-flex items-center gap-1.5 pl-1.5 pr-2 py-0.5 rounded-md border text-[12px] border-[var(--accent)]/30 bg-[var(--accent)]/[0.06] text-[var(--accent)]">
          <Icon name="pin" size={12} /> <Icon name={pinIcon} size={12} /> {pinLabel}
        </span>
      </div>
      <textarea value={val} onChange={(e) => setVal(e.target.value)} rows={1} placeholder="Ask Teklens anything…"
        className="w-full resize-none px-4 pt-2 pb-1.5 text-[14px] outline-none bg-transparent text-[#0e0e10] placeholder:text-[#9aa0a6]" style={{ minHeight: 38 }} />
      <div className="flex items-center gap-1 px-2.5 pb-2">
        <button className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04]"><Icon name="plus" size={16} /></button>
        <button className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04]"><Icon name="paperclip" size={16} /></button>
        <button className="p-1.5 rounded-md text-[var(--accent)] hover:bg-black/[0.04]"><Icon name="panel-right" size={16} /></button>
        <div className="flex-1" />
        <button className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04]"><Icon name="mic" size={16} /></button>
        <button className="flex items-center gap-1.5 px-2 py-1 rounded-md text-[12.5px] text-[#5a5a5a] hover:bg-black/[0.04]"><Icon name="model" size={14} className="text-[var(--accent)]" /> {model} <Icon name="chevron-down" size={12} className="text-[#9aa0a6]" /></button>
        <button onClick={() => onSubmit && onSubmit(val)} className="w-8 h-8 rounded-full bg-[var(--accent)] text-white flex items-center justify-center hover:brightness-110"><Icon name="send" size={15} /></button>
      </div>
    </div>
  );
}

// Section header with a "+ new" action floated to the right (board sections)
function BoardSection({ title, subtitle, action, children }) {
  return (
    <section className="rounded-2xl border border-[#e8e3d8] bg-white">
      <div className="flex items-start gap-3 px-5 pt-4 pb-3 border-b border-[#f0ebdd]">
        <div className="min-w-0 flex-1">
          <h2 className="text-[15px] font-semibold text-[#0e0e10]">{title}</h2>
          {subtitle && <p className="text-[12px] text-[#9aa0a6] mt-0.5 leading-snug">{subtitle}</p>}
        </div>
        {action}
      </div>
      <div className="p-3.5">{children}</div>
    </section>
  );
}

// ============================================================
//  DASHBOARD BOARD
// ============================================================
function DashboardView({ openInitiative, onOpenContext, artefactHome, moveArtefact }) {
  const [openId, setOpenId] = useState("lookfeel"); // accordion: one open
  const [dragArt, setDragArt] = useState(null);     // {artId, from}

  const onArtDragStart = (artId, from) => (e) => { setDragArt({ artId, from }); e.dataTransfer.effectAllowed = "move"; };
  const dropOn = (target) => (e) => {
    e.preventDefault();
    if (dragArt && dragArt.from !== target) moveArtefact(dragArt.artId, dragArt.from, target, e.altKey);
    setDragArt(null);
  };

  return (
    <div className="flex-1 min-w-0 flex flex-col bg-[#f4f1ea]">
      {/* header */}
      <div className="shrink-0 bg-white border-b border-[#e8e3d8] px-6 py-3.5 flex items-center gap-3">
        <h1 className="text-[18px] font-semibold text-[#0e0e10]">Dashboard</h1>
        <span className="text-[12px] text-[#9aa0a6] hidden sm:flex items-center gap-1.5"><Icon name="info" size={13} /> Drag an artefact between containers to move it · ⌥-drag to copy as a reference</span>
      </div>

      {/* scroll body */}
      <div className="flex-1 overflow-y-auto">
        <div className="max-w-[1080px] mx-auto px-6 py-6 flex flex-col gap-7">

          {/* ===== INITIATIVES (accordion) ===== */}
          <BoardSection title="Initiatives"
            action={<button className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-[#ece7da] bg-white text-[13px] font-medium text-[#3a3a3a] hover:border-[var(--accent)]/50 hover:text-[var(--accent)]"><Icon name="plus" size={15} /> New initiative</button>}>
            <div className="flex flex-col gap-2.5">
              {DASH_INITIATIVES.map((it) => (
                <InitiativeAccordionItem key={it.id} it={it} open={openId === it.id}
                  onToggle={() => setOpenId((o) => (o === it.id ? null : it.id))}
                  onOpen={() => openInitiative(it.id)} onOpenContext={onOpenContext}
                  artHome={artefactHome} onArtDragStart={onArtDragStart} onDropArt={dropOn({ kind: "initiative", id: it.id })} dragActive={!!dragArt} />
              ))}
            </div>
          </BoardSection>

          {/* ===== FOLDERS ===== */}
          <BoardSection title="Folders"
            action={<button className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-[#ece7da] bg-white text-[13px] font-medium text-[#3a3a3a] hover:border-[var(--accent)]/50 hover:text-[var(--accent)]"><Icon name="plus" size={15} /> New folder</button>}>
            <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
              {DASH_FOLDERS.map((f) => (
                <SimpleContainer key={f.id} c={f} kind="folder" onOpenContext={onOpenContext}
                  artHome={artefactHome} onArtDragStart={onArtDragStart} onDropArt={dropOn({ kind: "folder", id: f.id })} dragActive={!!dragArt} />
              ))}
            </div>
          </BoardSection>

          {/* ===== DESKTOP ===== */}
          <BoardSection title="Desktop">
            <SimpleContainer c={DASH_DESKTOP} kind="desktop" onOpenContext={onOpenContext}
              artHome={artefactHome} onArtDragStart={onArtDragStart} onDropArt={dropOn({ kind: "desktop", id: "desktop" })} dragActive={!!dragArt} full />
          </BoardSection>

          <div className="h-2" />
        </div>
      </div>
    </div>
  );
}

// Resolve which artefacts currently live in a container (respecting moves/copies)
function artefactsFor(it, kind, artefactHome) {
  // base artefacts of the container, minus any that were moved away, plus any moved/copied in
  const baseIds = it.artefacts.map((a) => a.id);
  const here = it.artefacts.filter((a) => {
    const home = artefactHome[a.id];
    return !home || home.some((h) => h.kind === kind && h.id === it.id);
  });
  // artefacts moved IN from elsewhere
  const incoming = [];
  Object.entries(artefactHome).forEach(([artId, homes]) => {
    if (baseIds.includes(artId)) return;
    if (homes.some((h) => h.kind === kind && h.id === it.id)) {
      const found = dashFindArtefact(artId);
      if (found) incoming.push(found);
    }
  });
  return [...here, ...incoming];
}
function dashFindArtefact(artId) {
  for (const it of DASH_INITIATIVES) { const a = it.artefacts.find((x) => x.id === artId); if (a) return a; }
  for (const f of DASH_FOLDERS) { const a = f.artefacts.find((x) => x.id === artId); if (a) return a; }
  const d = DASH_DESKTOP.artefacts.find((x) => x.id === artId); if (d) return d;
  return null;
}

// ---------- Initiative accordion line item (collapsed + expanded) ----------
function InitiativeAccordionItem({ it, open, onToggle, onOpen, onOpenContext, artHome, onArtDragStart, onDropArt, dragActive }) {
  const phase = DASH_PHASES[dashPhaseIndex(it.phase)];
  const arts = artefactsFor(it, "initiative", artHome);
  const [dragOver, setDragOver] = useState(false);
  const [tab, setTab] = useState("artefacts");
  return (
    <div onDragOver={(e) => { if (dragActive) { e.preventDefault(); setDragOver(true); } }} onDragLeave={() => setDragOver(false)} onDrop={(e) => { setDragOver(false); onDropArt(e); }}
      className={`rounded-xl border transition-all ${open ? "border-[var(--accent)]/55 bg-[var(--accent)]/[0.05] shadow-[0_2px_14px_rgba(31,61,246,0.12)]" : "border-[#ece7da] bg-white hover:border-[#ddd6c6]"} ${dragOver ? "ring-2 ring-[var(--accent)]/40" : ""}`}>
      {/* ---- collapsed header (always visible) ---- */}
      <button onClick={onToggle} className="w-full flex items-center gap-3.5 px-4 py-3 text-left">
        <Icon name={open ? "chevron-down" : "chevron-right"} size={16} className="text-[#9aa0a6] shrink-0" />
        {!open && <span className="shrink-0"><PhaseChip phase={it.phase} /></span>}
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-2">
            <span className="text-[14px] font-semibold text-[#0e0e10] truncate">{it.name}</span>
            <span className="text-[10.5px] font-mono text-[#9aa0a6] shrink-0">{it.code}</span>
          </div>
          {!open && <div className="text-[12px] text-[#9aa0a6] truncate mt-0.5">{it.desc}</div>}
        </div>
        {/* collapsed signals: active chats, artefact count, owner */}
        <span className="hidden md:inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-[#f4f1ea] text-[#5a5a5a]" title={`${it.convos.length} active conversations`}>
          <Icon name="message" size={12} className="text-[#d97706]" /><span className="text-[11.5px] font-medium tabular-nums">{it.convos.length}</span>
        </span>
        <span className="hidden md:inline-flex"><ArtefactCount artefacts={arts} /></span>
        <span className="shrink-0"><PersonAvatar who={it.owner} size={26} /></span>
      </button>

      {/* ---- expanded body ---- */}
      {open && (
        <div className="px-4 pb-4 pt-1">
          {/* description + accountable */}
          <div className="flex items-start gap-3 mb-3.5">
            <div className="min-w-0 flex-1">
              <p className="text-[13px] text-[#3a3a3a] leading-relaxed max-w-[640px]">{it.desc}</p>
              <div className="mt-1.5 flex items-center gap-1.5 text-[12px] text-[#6b6b6b]"><PersonAvatar who={it.owner} size={18} /> Accountable · <span className="font-medium text-[#0e0e10]">{DASH_PEOPLE[it.owner].name}</span></div>
            </div>
            <button onClick={onOpen} className="shrink-0 flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[var(--accent)] text-white text-[12.5px] font-medium hover:brightness-110 shadow-[0_1px_0_rgba(0,0,0,0.08)]">Open initiative <Icon name="external" size={13} /></button>
          </div>

          {/* stages */}
          <div className="mb-3.5 p-2 rounded-xl bg-[#faf8f3] border border-[#f0ebdd] flex items-center gap-2 flex-wrap">
            <div className="flex-1 min-w-[260px]"><StageStepper phase={it.phase} /></div>
            <AdvanceStage phase={it.phase} name={it.name} />
          </div>

          {/* tabs: Artefacts · Activity · Actions */}
          <div className="flex items-center gap-1 mb-3 border-b border-[#f0ebdd]">
            {[["artefacts", "Artefacts", arts.length], ["activity", "Activity", it.convos.length + it.runs.length], ["actions", "Actions", (DASH_CAPS[it.phase] || []).length]].map(([id, label, n]) => (
              <button key={id} onClick={() => setTab(id)} className={`flex items-center gap-1.5 px-3 py-2 -mb-px border-b-2 text-[12.5px] font-medium ${tab === id ? "border-[var(--accent)] text-[#0e0e10]" : "border-transparent text-[#6b6b6b] hover:text-[#0e0e10]"}`}>{label}<span className="text-[10.5px] text-[#9aa0a6] tabular-nums">{n}</span></button>
            ))}
          </div>

          {tab === "artefacts" && (
            <div className="grid grid-cols-1 lg:grid-cols-2 gap-1.5">
              {arts.map((a) => <ArtefactRow key={a.id} a={a} onOpen={() => onOpenContext({ kind: "artefact", label: a.name })} onDragStart={onArtDragStart(a.id, { kind: "initiative", id: it.id })} />)}
            </div>
          )}
          {tab === "activity" && (
            <div className="grid grid-cols-1 lg:grid-cols-2 gap-x-5">
              <div>
                <div className="text-[11px] uppercase tracking-[0.05em] text-[#9aa0a6] font-semibold px-1 mb-1">Conversations</div>
                {it.convos.map((c) => <ConvoCard key={c.id} c={c} dense onJoin={() => onOpenContext({ kind: "conversation", label: c.title })} />)}
              </div>
              <div>
                <div className="text-[11px] uppercase tracking-[0.05em] text-[#9aa0a6] font-semibold px-1 mb-1 mt-2 lg:mt-0">Runs</div>
                {it.runs.map((r) => <RunCard key={r.id} r={r} dense onOpen={() => onOpenContext({ kind: "run", label: r.title })} />)}
              </div>
            </div>
          )}
          {tab === "actions" && (
            <div>
              <div className="text-[11.5px] text-[#9aa0a6] mb-2">Adapted to the {DASH_PHASES[dashPhaseIndex(it.phase)].name} phase · {it.name}</div>
              <div className="grid grid-cols-1 lg:grid-cols-2 gap-2">
                {(DASH_CAPS[it.phase] || []).map((cap, i) => <CapabilityCard key={i} cap={cap} onRun={(label) => onOpenContext({ kind: "skill", label })} />)}
              </div>
            </div>
          )}

          <div className="mt-4">
            <button onClick={onOpen} className="flex items-center gap-1.5 px-3 py-2 rounded-lg border border-[#ece7da] text-[12.5px] font-medium text-[#3a3a3a] hover:border-[var(--accent)]/50 hover:text-[var(--accent)]"><Icon name="message" size={14} /> Start conversation</button>
          </div>
        </div>
      )}
    </div>
  );
}

// ---------- Simple container (folders + desktop): no stages, no skills ----------
function SimpleContainer({ c, kind, onOpenContext, artHome, onArtDragStart, onDropArt, dragActive, full = false }) {
  const arts = artefactsFor(c, kind, artHome);
  const [tab, setTab] = useState("artefacts");
  const [dragOver, setDragOver] = useState(false);
  return (
    <div onDragOver={(e) => { if (dragActive) { e.preventDefault(); setDragOver(true); } }} onDragLeave={() => setDragOver(false)} onDrop={(e) => { setDragOver(false); onDropArt(e); }}
      className={`rounded-xl border bg-white flex flex-col transition-all ${dragOver ? "ring-2 ring-[var(--accent)]/40 border-[var(--accent)]/40" : "border-[#ece7da]"}`}>
      {/* header */}
      <div className="flex items-center gap-2.5 px-3.5 pt-3 pb-2.5 border-b border-[#f0ebdd]">
        <span className="w-7 h-7 rounded-md flex items-center justify-center shrink-0 bg-[#f4f1ea] text-[#9b8866]"><Icon name={kind === "desktop" ? "home" : "folder"} size={15} /></span>
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-2"><span className="text-[13.5px] font-semibold text-[#0e0e10] truncate">{c.name}</span></div>
          {c.owner && <div className="text-[11px] text-[#9aa0a6] truncate">Owner · {DASH_PEOPLE[c.owner].name}</div>}
        </div>
        {c.owner && <PersonAvatar who={c.owner} size={22} />}
      </div>
      {/* tabs */}
      <div className="flex items-center gap-1 px-3 pt-2">
        {[["artefacts", "Artefacts", arts.length], ["activity", "Activity", c.convos.length + c.runs.length]].map(([id, label, n]) => (
          <button key={id} onClick={() => setTab(id)} className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[12px] font-medium ${tab === id ? "bg-[#f4f1ea] text-[#0e0e10]" : "text-[#6b6b6b] hover:text-[#0e0e10]"}`}>{label}<span className="text-[10.5px] text-[#9aa0a6] tabular-nums">{n}</span></button>
        ))}
      </div>
      {/* body */}
      <div className="p-3 flex flex-col gap-1.5">
        {tab === "artefacts" ? (
          arts.length ? arts.map((a) => <ArtefactRow key={a.id} a={a} onOpen={() => onOpenContext({ kind: "artefact", label: a.name })} onDragStart={onArtDragStart(a.id, { kind, id: c.id })} />)
            : <EmptyDrop />
        ) : (
          <>
            {c.convos.map((cv) => <ConvoCard key={cv.id} c={cv} dense onJoin={() => onOpenContext({ kind: "conversation", label: cv.title })} />)}
            {c.runs.map((r) => <RunCard key={r.id} r={r} dense onOpen={() => onOpenContext({ kind: "run", label: r.title })} />)}
          </>
        )}
      </div>
      {/* footer */}
      <div className="px-3 pb-3 mt-auto">
        <button onClick={() => onOpenContext({ kind: "conversation", label: c.name })} className="w-full flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg border border-[#ece7da] text-[12.5px] font-medium text-[#3a3a3a] hover:border-[var(--accent)]/50 hover:text-[var(--accent)]"><Icon name="message" size={14} /> Start conversation</button>
      </div>
    </div>
  );
}
function EmptyDrop() {
  return <div className="rounded-lg border border-dashed border-[#ddd8c9] px-3 py-6 text-center text-[12px] text-[#9aa0a6]">No artefacts · drop one here to move it</div>;
}

// ============================================================
//  PULSE PANEL (collapsible)
// ============================================================
function PulsePanel({ onClose, onOpenContext }) {
  const feed = useMemo(() => dashPulseFeed(), []);
  const [filter, setFilter] = useState("all"); // all | conversation | run
  const shown = feed.filter((p) => filter === "all" ? true : filter === "run" ? p.type !== "conversation" : p.type === "conversation");
  const counts = { all: feed.length, conversation: feed.filter((p) => p.type === "conversation").length, run: feed.filter((p) => p.type !== "conversation").length };
  return (
    <div className="w-full h-full flex flex-col bg-[#faf8f3] border-l border-[#e8e3d8]">
      <div className="shrink-0 h-11 px-3.5 flex items-center gap-2 border-b border-[#e8e3d8]">
        <span className="relative flex items-center justify-center w-2 h-2">
          <span className="absolute inline-flex w-2 h-2 rounded-full bg-[#16A34A] opacity-60 init-pulse" />
          <span className="relative inline-flex w-1.5 h-1.5 rounded-full bg-[#16A34A]" />
        </span>
        <span className="text-[13px] font-semibold text-[#0e0e10]">Pulse</span>
        <span className="text-[11px] text-[#9aa0a6]">everything, live</span>
        <div className="flex-1" />
        <button onClick={onClose} className="p-1 rounded hover:bg-black/[0.05] text-[#9aa0a6]" title="Collapse"><Icon name="panel-right" size={16} /></button>
      </div>
      {/* filter */}
      <div className="shrink-0 flex items-center gap-1 px-2.5 py-2 border-b border-[#e8e3d8]">
        {[["all", "All"], ["conversation", "Conversations"], ["run", "Events & runs"]].map(([id, label]) => (
          <button key={id} onClick={() => setFilter(id)} className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11.5px] font-medium ${filter === id ? "bg-white border border-[#e6dfcd] text-[#0e0e10] shadow-[0_1px_2px_rgba(0,0,0,0.04)]" : "text-[#6b6b6b] hover:text-[#0e0e10]"}`}>{label}<span className="text-[10px] text-[#9aa0a6] tabular-nums">{counts[id]}</span></button>
        ))}
      </div>
      <div className="flex-1 overflow-y-auto px-2.5 py-2.5 flex flex-col gap-1.5">
        {shown.map((p) => <PulseItem key={p.id} p={p} onOpenContext={onOpenContext} />)}
      </div>
    </div>
  );
}
function PulseItem({ p, onOpenContext }) {
  const k = DASH_PULSE_TYPE[p.type];
  const c = p.container;
  const dotColor = c.kind === "initiative" ? (DASH_PHASES[dashPhaseIndex(c.phase)] || {}).color : "#9b8866";
  const onOpen = () => onOpenContext({ kind: p.type === "conversation" ? "conversation" : "run", label: p.title });
  return (
    <div className="group rounded-lg border border-[#ece7da] bg-white px-3 py-2.5 hover:border-[var(--accent)]/35 transition-colors">
      {/* line 1: type + time */}
      <div className="flex items-center gap-1.5">
        <span className="rounded-md flex items-center justify-center shrink-0" style={{ width: 15, height: 15, background: k.color + "1c", color: k.color }}><Icon name={k.icon} size={9.5} stroke={2.4} /></span>
        <span className="text-[11px] font-semibold truncate" style={{ color: k.color }}>{k.label}</span>
        {p.status === "attention" && <span className="w-1.5 h-1.5 rounded-full bg-[#d97706] shrink-0" title="Needs attention" />}
        <span className="text-[10.5px] text-[#9aa0a6] shrink-0 ml-auto tabular-nums">{p.time}</span>
      </div>
      {/* line 2: title */}
      <div className="mt-1.5 text-[12.5px] text-[#1a1a1a] leading-snug">{p.title}</div>
      {p.subtitle && <div className="mt-0.5 text-[11px] text-[#9aa0a6] leading-snug truncate">{p.subtitle}</div>}
      {/* line 3: container ref + avatars/artefact + join */}
      <div className="mt-2 flex items-center gap-1.5">
        <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-[#f4f1ea] shrink-0" title={c.name}>
          <span className="rounded-full" style={{ width: 6, height: 6, background: dotColor }} />
          <span className="text-[10px] font-mono text-[#6b6b6b]">{c.code}</span>
        </span>
        <span className="text-[10.5px] text-[#9aa0a6] truncate min-w-0">{c.name}</span>
        {p.type === "conversation"
          ? <span className="ml-auto shrink-0"><DashAvatarStack agents={p.agents} people={p.people} size={16} /></span>
          : p.artefact && <span className="ml-auto shrink-0 inline-flex items-center gap-1 text-[10.5px] text-[#6b6b6b]"><Icon name={DASH_TYPE_ICON[p.artefact.type] || "file"} size={11} className="text-[#9b8866]" /><span className="truncate max-w-[90px]">{p.artefact.name}</span></span>}
      </div>
      {p.joinable && (
        <div className="mt-2 -mb-0.5 flex justify-end">
          <button onClick={onOpen} className="opacity-0 group-hover:opacity-100 transition-opacity inline-flex items-center gap-1 text-[11px] font-medium text-[var(--accent)] px-2 py-0.5 rounded-md bg-[var(--accent)]/[0.08] hover:bg-[var(--accent)]/[0.14]"><Icon name="plus" size={11} /> Join</button>
        </div>
      )}
    </div>
  );
}

// Collapsed Pulse rail (when panel closed)
function PulseRail({ onExpand }) {
  return (
    <button onClick={onExpand} title="Show Pulse" className="shrink-0 w-10 h-full bg-[#faf8f3] border-l border-[#e8e3d8] flex flex-col items-center pt-3 gap-2 text-[#6b6b6b] hover:text-[var(--accent)]">
      <Icon name="panel-left-open" size={18} />
      <span className="relative flex items-center justify-center w-2.5 h-2.5 mt-1">
        <span className="absolute inline-flex w-2.5 h-2.5 rounded-full bg-[#16A34A] opacity-50 init-pulse" />
        <span className="relative inline-flex w-1.5 h-1.5 rounded-full bg-[#16A34A]" />
      </span>
      <span className="text-[10px] font-semibold uppercase tracking-[0.1em] text-[#9aa0a6] mt-1" style={{ writingMode: "vertical-rl" }}>Pulse</span>
    </button>
  );
}
