// shell.jsx — Opareto Platform prototype shell.
//
// Three tenants in the switcher (sidebar header):
//   - Opareto · admin       → our own product+agency operations
//   - Atlantic Mechanical   → operations / Action-shaped
//   - European BrainTech Fund → knowledge work / Query-shaped
//   - MC Home Improvements & Construction → residential construction / mixed workflow
//
// Each tenant has a flat primary nav of real product destinations — NOT a
// catalog of mock files. The 19 canon mock files are reachable via natural
// drill-downs (Settings hub iframes the canon settings mocks as tabs;
// Estimate detail / Take-off / Pricing land as tabs of a job; Cmd+K and
// Chat surface are keyboard-invokable surfaces).
//
// On iframe load: apply current theme, inject the prototype banner.
// postMessage navigation lets surfaces drive shell from inside the iframe.

(function () {
  'use strict';
  const { useState, useEffect, useRef, useMemo } = React;
  const Icons = window.Icons || {};
  const TITLE_BY_TENANT = {
    opareto: 'Opareto',
    atlantic: 'Atlantic Mechanical',
    ebtf: 'European BrainTech Fund',
    mchome: 'MC Home',
    envirocon: 'Envirocon',
    pandair: 'Pandair',
    parkview: 'Parkview Dental',
    quality: 'Quality Refrigeration',
  };

  // ─────────────────────────────────────────────────────────────
  // Per-company nav. Common skeleton across tenants:
  //   Inbox (top, common)
  //   <tenant-specific workflow>
  //   Settings (bottom, common)
  //
  // Chat is NOT in the sidebar — it's a global overlay invokable from
  // anywhere via the FAB (bottom-right) or ⌘J (matches live AppShell;
  // see apps/platform/src/lib/sidebar-nav.ts comment "Chat is intentionally
  // NOT in this nav — chat-as-overlay surfaces from anywhere").
  // ─────────────────────────────────────────────────────────────
  const NAV_BY_TENANT = {
    opareto: [
      { id: 'inbox',    title: 'Inbox',       screen: 'screens/proposed/m7-agent-inbox.html',   proposed: true },
      { id: 'pipeline', title: 'Engagements', screen: 'screens/proposed/opareto-pipeline.html', proposed: true },
      { id: 'feedback', title: 'Feedback',    screen: 'screens/proposed/opareto-feedback.html', proposed: true },
      { id: 'admin',    title: 'Admin',       screen: 'screens/op-tenants.html',                proposed: true },
      { id: 'sec-agents', title: 'Agents', _section: true },
      { id: 'agent-working', title: 'Assistant',   screen: 'screens/agent-working.html', proposed: true },
      { id: 'knowledge',     title: 'Knowledge',   screen: 'screens/knowledge.html',     proposed: true },
      { id: 'automations',   title: 'Automations', screen: 'screens/automations.html',   proposed: true },
      { id: 'settings', title: 'Settings', _drillIn: true, screen: 'screens/proposed/settings-hub.html' },

      // ── Hidden: the review kit lives inside an engagement; chat is ambient (⌘J) ──
      { id: 'review-estimate', title: 'Review (kit)',        screen: 'screens/kit-review.html',    _hidden: true, _parent: 'pipeline' },
      // ── Hidden: operator drill-downs, reached from Admin (the company detail tabs to come) ──
      { id: 'op-provision',        title: 'Turn on a company',   screen: 'screens/op-provision.html',        _hidden: true, _parent: 'admin' },
      { id: 'op-agents-models',    title: 'Agents & models',    screen: 'screens/op-agents-models.html',    _hidden: true, _parent: 'admin' },
      { id: 'op-flags-connectors', title: 'Flags & connectors', screen: 'screens/op-flags-connectors.html', _hidden: true, _parent: 'admin' },
      { id: 'op-observability',    title: 'Runs & observability', screen: 'screens/op-observability.html',  _hidden: true, _parent: 'admin' },
      { id: 'op-branding',         title: 'Branding',           screen: 'screens/op-branding.html',         _hidden: true, _parent: 'admin' },
    ],
    atlantic: [
      // Customer-facing work destinations stay direct and visible. The
      // deeper prototype screens remain hidden behind these entry points.
      // All 14 demo screens still exist — they're hidden drill-downs
      // reachable via inline click-throughs and the in-page tab strips.
      { id: 'tasks',         title: 'My Tasks',    screen: 'screens/atlantic-tasks.html' },
      { id: 'wo-dashboard',  title: 'Work orders', screen: 'screens/22-wo-overview.html', proposed: true },
      { id: 'pipeline',      title: 'Bidding',     screen: 'screens/joe-pipeline.html', proposed: true },
      { id: 'sec-agents',    title: 'Agents',             _section: true },
      { id: 'agent-working', title: 'Assistant',   screen: 'screens/agent-working.html', proposed: true },
      { id: 'knowledge',     title: 'Knowledge',   screen: 'screens/knowledge.html',     proposed: true },
      { id: 'automations',   title: 'Automations', screen: 'screens/automations.html',   proposed: true },
      { id: 'sec-config',    title: 'Config',             _section: true },
      { id: 'settings',      title: 'Settings',  _drillIn: true, screen: 'screens/proposed/settings-hub.html' },

      // ── Hidden drill-downs · reached via in-page clicks, ⌘K, or chat ──
      // Bidding side
      { id: 'inbox-canon',     title: 'Inbox (canon)',          screen: 'screens/07-inbox-approval.html',               _hidden: true, _parent: 'pipeline' },
      { id: 'agent-inbox',     title: 'Agent inbox (legacy)',   screen: 'screens/15-agent-inbox.html',                  _hidden: true, _parent: 'pipeline' },
      { id: 'margin-watcher',  title: 'Margin watcher',         screen: 'screens/17-margin-watcher-proposal.html',      _hidden: true, _parent: 'pipeline' },
      { id: 'pipeline-canon',  title: 'Pipeline (canon)',       screen: 'screens/01-job-pipeline.html',                 _hidden: true, _parent: 'pipeline' },
      // The "create new estimate" hero in 01-job-pipeline posts navigate→'upload';
      // wire that id to the upload-flow screen so the demo's primary CTA works.
      { id: 'upload',          title: 'Upload drawings',        screen: 'screens/06-upload-flow.html',                  _hidden: true, _parent: 'pipeline' },
      { id: 'detail',          title: 'Estimate detail',        screen: 'screens/02-estimate-detail.html',              _hidden: true, _parent: 'pipeline' },
      { id: 'review-estimate', title: 'Review estimate (kit)',  screen: 'screens/kit-review.html',                      _hidden: true, _parent: 'pipeline' },
      { id: 'pricing',         title: 'Pricing card',           screen: 'screens/04-pricing-card.html',                 _hidden: true, _parent: 'pipeline' },
      { id: 'outputs',         title: 'Outputs',                screen: 'screens/09-outputs-generate.html',             _hidden: true, _parent: 'pipeline' },
      { id: 'edit',            title: 'Edit roundtrip',         screen: 'screens/08-edit-roundtrip.html',               _hidden: true, _parent: 'pipeline' },
      { id: 'gc-suffolk',      title: 'GC · Suffolk',           screen: 'screens/18-gc-intelligence-suffolk.html',      _hidden: true, _parent: 'pipeline' },
      { id: 'lost-pm',         title: 'Lost-bid post-mortem',   screen: 'screens/20-lost-bid-postmortem.html',          _hidden: true, _parent: 'pipeline' },
      { id: 'bidding-email',   title: 'Monday bidding briefing', screen: 'screens/21-bidding-briefing-email.html',      _hidden: true, _parent: 'pipeline' },
      { id: 'takeoff-canon',   title: 'Take-off (plain)',       screen: 'screens/03-takeoff-table.html',                _hidden: true, _parent: 'takeoffs' },
      // WO side — the 6 dashboard views; each carries its own in-page tab strip.
      { id: 'wo-reasons',      title: 'WO · why incomplete',    screen: 'screens/23-wo-reasons.html',                   _hidden: true, _parent: 'wo-dashboard' },
      { id: 'wo-parts',        title: 'WO · parts + call types', screen: 'screens/24-wo-parts-and-calls.html',          _hidden: true, _parent: 'wo-dashboard' },
      { id: 'wo-techs',        title: 'WO · technicians',       screen: 'screens/25-wo-technicians.html',               _hidden: true, _parent: 'wo-dashboard' },
      { id: 'wo-reportcard',   title: 'WO · tech report card',  screen: 'screens/26-wo-tech-report-card.html',          _hidden: true, _parent: 'wo-dashboard' },
      { id: 'wo-recs',         title: 'WO · AI recommendations', screen: 'screens/27-wo-ai-recommendations.html',       _hidden: true, _parent: 'wo-dashboard' },
      { id: 'wo-email',        title: 'WO · Monday briefing',   screen: 'screens/28-wo-monday-briefing-email.html',     _hidden: true, _parent: 'wo-dashboard' },
      // Chat is ambient via ⌘J — kept reachable as a hidden screen for direct URLs.
      { id: 'chat-rendering',  title: 'Chat + rendered cards',  screen: 'screens/16-chat-rendering.html',               _hidden: true },
    ],
    mchome: [
      { id: 'inbox',         title: 'Inbox',     screen: 'screens/mchome-inbox.html',     proposed: true },
      { id: 'projects',      title: 'Projects',  screen: 'screens/mchome-projects.html',  proposed: true },
      { id: 'sec-agents',    title: 'Agents',    _section: true },
      { id: 'agent-working', title: 'Assistant',   screen: 'screens/agent-working.html', proposed: true },
      { id: 'knowledge',     title: 'Knowledge',   screen: 'screens/knowledge.html',     proposed: true },
      { id: 'automations',   title: 'Automations', screen: 'screens/automations.html',   proposed: true },
      { id: 'settings',      title: 'Settings',  _drillIn: true, screen: 'screens/proposed/settings-hub.html' },

      // ── Hidden: secondary surfaces + the review kit (inside a project) ──
      { id: 'estimates',     title: 'Estimates',     screen: 'screens/mchome-estimates.html',     _hidden: true, _parent: 'projects' },
      { id: 'field-updates', title: 'Field updates', screen: 'screens/mchome-field-updates.html', _hidden: true, _parent: 'projects' },
      { id: 'review-estimate', title: 'Review (kit)',        screen: 'screens/kit-review.html',    _hidden: true, _parent: 'projects' },
      { id: 'estimate-proposal',  title: 'Change order proposal', screen: 'screens/mchome-estimate-proposal.html',  _hidden: true, _parent: 'estimates' },
      { id: 'schedule-proposal',  title: 'Schedule proposal',     screen: 'screens/mchome-rebalance-proposal.html', _hidden: true, _parent: 'projects' },
      { id: 'subcontractors',     title: 'Subcontractors',        screen: 'screens/mchome-subcontractors.html',     _hidden: true, _parent: 'projects' },
      { id: 'daily-briefing',     title: 'Daily briefing',        screen: 'screens/mchome-daily-briefing.html',     _hidden: true, _parent: 'inbox' },
      { id: 'mobile-demo',        title: 'Mobile demo',           screen: 'screens/mchome-mobile-demo.html',        _hidden: true, _parent: 'inbox' },
    ],
    ebtf: [
      { id: 'inbox',    title: 'Inbox',    screen: 'screens/proposed/m7-agent-inbox.html',  proposed: true },
      { id: 'deals',    title: 'Deals',    screen: 'screens/proposed/ebtf-deal-flow.html',  proposed: true },
      { id: 'sec-agents', title: 'Agents', _section: true },
      { id: 'agent-working', title: 'Assistant',   screen: 'screens/agent-working.html', proposed: true },
      { id: 'knowledge',     title: 'Knowledge',   screen: 'screens/knowledge.html',     proposed: true },
      { id: 'automations',   title: 'Automations', screen: 'screens/automations.html',   proposed: true },
      { id: 'settings',      title: 'Settings', _drillIn: true, screen: 'screens/proposed/settings-hub.html' },

      // ── Hidden: deal detail + the review kit (inside a deal); chat ambient (⌘J) ──
      { id: 'deal-detail',     title: 'Deal detail',        screen: 'screens/proposed/ebtf-deal-detail.html', _hidden: true, _parent: 'deals' },
      { id: 'review-estimate', title: 'Review (kit)',        screen: 'screens/kit-review.html',               _hidden: true, _parent: 'deals' },
    ],
    envirocon: [
      { id: 'sec-command',  title: 'Command center', _section: true },
      { id: 'today',        title: 'Today',       screen: 'screens/envirocon-command-brief.html', proposed: true },
      { id: 'exceptions',   title: 'Exceptions',  screen: 'screens/envirocon-exceptions.html',    proposed: true },
      { id: 'sec-ops',      title: 'Operations', _section: true },
      { id: 'work-orders',  title: 'Work Orders', screen: 'screens/envirocon-work-orders.html',   proposed: true },
      { id: 'schedule',     title: 'Schedule',    screen: 'screens/envirocon-schedule.html',      proposed: true },
      { id: 'hours',        title: 'Hours',       screen: 'screens/envirocon-hours.html',         proposed: true },
      { id: 'agreements',   title: 'Service Agreements', screen: 'screens/envirocon-service-agreements.html', proposed: true },
      { id: 'sec-agents',   title: 'Agents', _section: true },
      { id: 'agent-working', title: 'Assistant',  screen: 'screens/agent-working.html',           proposed: true },
      { id: 'knowledge',    title: 'Knowledge',   screen: 'screens/knowledge.html',               proposed: true },
      { id: 'automations',  title: 'Automations', screen: 'screens/automations.html',             proposed: true },
      { id: 'settings',     title: 'Settings',    _drillIn: true, screen: 'screens/proposed/settings-hub.html' },
      // Hidden: exception detail (trust surface) reached by clicking a row on Today / Exceptions
      { id: 'exception-detail', title: 'Exception detail', screen: 'screens/envirocon-exception-detail.html', _hidden: true, _parent: 'exceptions' },
      // Hidden: the review kit lives inside a work order; chat ambient (⌘J)
      { id: 'review-estimate', title: 'Review invoice (kit)', screen: 'screens/kit-review.html',    _hidden: true, _parent: 'work-orders' },
    ],
    pandair: [
      { id: 'inbox',        title: 'Inbox',           screen: 'screens/pandair-inbox.html',          proposed: true },
      { id: 'turnarounds',  title: 'Turnarounds',     screen: 'screens/pandair-turnarounds.html',    proposed: true },
      { id: 'network',      title: 'Network',         screen: 'screens/pandair-network.html',        proposed: true },
      { id: 'permits',      title: 'Permits & slots', screen: 'screens/pandair-permits.html',        proposed: true },
      { id: 'sec-agents',   title: 'Agents', _section: true },
      { id: 'agent-working', title: 'Assistant',  screen: 'screens/agent-working.html',             proposed: true },
      { id: 'knowledge',    title: 'Knowledge',   screen: 'screens/knowledge.html',                  proposed: true },
      { id: 'automations',  title: 'Automations', screen: 'screens/automations.html',                proposed: true },
      { id: 'settings',     title: 'Settings',    _drillIn: true, screen: 'screens/proposed/settings-hub.html' },
      // Hidden drill-downs · reached via inline clicks, ⌘K, or chat; kit ambient
      { id: 'turnaround-detail', title: 'Turnaround detail', screen: 'screens/pandair-turnaround-detail.html', _hidden: true, _parent: 'turnarounds' },
      { id: 'ramp-agent',        title: 'Ramp crew agent',   screen: 'screens/pandair-ramp-agent.html',        _hidden: true, _parent: 'turnarounds' },
      { id: 'permit-proposal',   title: 'Permit draft',      screen: 'screens/pandair-permit-proposal.html',   _hidden: true, _parent: 'permits' },
      { id: 'morning-briefing',  title: 'Morning briefing',  screen: 'screens/pandair-morning-briefing.html',  _hidden: true, _parent: 'inbox' },
      { id: 'mobile-demo',       title: 'Mobile demo',       screen: 'screens/pandair-mobile-demo.html',       _hidden: true, _parent: 'inbox' },
      { id: 'review-estimate', title: 'Review (kit)',        screen: 'screens/kit-review.html',    _hidden: true, _parent: 'turnarounds' },
    ],
    parkview: [
      { id: 'verifications', title: 'Verifications', screen: 'screens/parkview-demo.html',           proposed: true },
      { id: 'sec-agents',    title: 'Agents', _section: true },
      { id: 'agent-working', title: 'Assistant',   screen: 'screens/agent-working.html',            proposed: true },
      { id: 'knowledge',     title: 'Knowledge',   screen: 'screens/knowledge.html',                 proposed: true },
      { id: 'automations',   title: 'Automations', screen: 'screens/automations.html',               proposed: true },
      { id: 'settings',      title: 'Settings',    _drillIn: true, screen: 'screens/proposed/settings-hub.html' },
      // Hidden: the review kit lives inside a verification; chat ambient (⌘J)
      { id: 'review-estimate', title: 'Review (kit)',        screen: 'screens/kit-review.html',    _hidden: true, _parent: 'verifications' },
    ],
    quality: [
      // Mirrors the canonical Envirocon command-center structure.
      { id: 'sec-command',   title: 'Command center', _section: true },
      { id: 'today',         title: 'Today',       screen: 'screens/quality-command-brief.html', proposed: true },
      { id: 'sec-ops',       title: 'Operations', _section: true },
      { id: 'field',         title: 'Field',       screen: 'screens/quality-field.html',         proposed: true },
      { id: 'connectors',    title: 'Connectors',  screen: 'screens/quality-connectors.html',    proposed: true },
      { id: 'sec-agents',    title: 'Agents', _section: true },
      { id: 'agent-working', title: 'Assistant',   screen: 'screens/agent-working.html',          proposed: true },
      // Hidden: exception detail (trust surface) reached by clicking a row on Today.
      { id: 'exception-detail', title: 'Exception detail', screen: 'screens/quality-exception-detail.html', _hidden: true, _parent: 'today' },
      // Interactive service workspace (its own chrome + assistant). Kept as a
      // direct-link deep demo, out of the shell nav to avoid double chrome.
      { id: 'service',       title: 'Service workspace', screen: 'screens/quality-app.html',     _hidden: true },
      // Standalone walkthrough kept for the recorded video; reachable directly.
      { id: 'demo',          title: 'Walkthrough', screen: 'screens/quality-demo.html',           _hidden: true },
    ],
  };

  // Settings is a drill-in: clicking it swaps the sidebar to these sections
  // (with a back button), matching envirocon-cf / Linear / Vercel. Each loads
  // the settings hub at the right section (chromeless — the sidebar is the nav).
  const SETTINGS_NAV = [
    { id: 'set-workspace', title: 'Workspace', screen: 'screens/proposed/settings-hub.html?tab=config&chromeless=1' },
    { id: 'set-members',   title: 'Members',   screen: 'screens/proposed/settings-hub.html?tab=members&chromeless=1' },
    { id: 'set-connect',   title: 'Connectors', screen: 'screens/proposed/settings-hub.html?tab=connectors&chromeless=1' },
    { id: 'set-activity',  title: 'Activity',  screen: 'screens/proposed/settings-hub.html?tab=runs&chromeless=1' },
  ];
  const SETTINGS_NAV_OPARETO = [
    { id: 'set-managed', title: 'Managed companies', screen: 'screens/proposed/settings-hub.html?tab=managed&chromeless=1' },
    ...SETTINGS_NAV,
  ];
  function settingsNavFor(slug) {
    return slug === 'opareto' ? SETTINGS_NAV_OPARETO : SETTINGS_NAV;
  }

  function findItem(items, needle) {
    if (!needle) return null;
    return items.find((i) => i.id === needle) || null;
  }

  // ─────────────────────────────────────────────────────────────
  // ⌘K Command Palette
  // Centered modal · 520px (per packages/ui/docs/inventory.md §3) ·
  // search-filtered list of nav destinations + tenant switches +
  // "Ask the assistant (⌘J)". ↑/↓ navigate, Enter selects, Esc closes.
  // ─────────────────────────────────────────────────────────────
  function CommandPalette({ tenant, tenants, items, onSelectNav, onSwitchTenant, onOpenChat, onClose }) {
    const [q, setQ] = useState('');
    const [idx, setIdx] = useState(0);
    const inputRef = useRef(null);

    useEffect(() => { setTimeout(() => inputRef.current && inputRef.current.focus(), 30); }, []);

    // Build the command list. Tenant nav items first, then tenant switches,
    // then global actions.
    const commands = useMemo(() => {
      const navCmds = items.filter((i) => !i._hidden && !i._section && !i._landingOnly).map((i) => ({
        kind: 'nav', label: i.title, sub: i.subtitle, action: () => onSelectNav(i.id),
      }));
      const allowed = (window.OparetoAuth && window.OparetoAuth.user && window.OparetoAuth.user.tenants) || window.OparetoTenant.list();
      const switchCmds = window.OparetoTenant.list()
        .filter((s) => s !== tenant.slug && allowed.indexOf(s) >= 0)
        .map((s) => ({
          kind: 'tenant', label: 'Switch to ' + tenants[s].name, sub: tenants[s].domainShape,
          action: () => onSwitchTenant(s),
        }));
      const assistantName = tenant.slug === 'atlantic' ? 'Atlantic Mechanical Assistant' : tenant.name + ' Assistant';
      const globalCmds = [
        { kind: 'action', label: 'Ask the assistant', sub: '⌘J · ' + assistantName, action: () => onOpenChat() },
      ];
      return [...navCmds, ...switchCmds, ...globalCmds];
    }, [items, tenant, tenants, onSelectNav, onSwitchTenant, onOpenChat]);

    const filtered = useMemo(() => {
      if (!q.trim()) return commands;
      const needle = q.toLowerCase();
      return commands.filter((c) => c.label.toLowerCase().includes(needle) || (c.sub || '').toLowerCase().includes(needle));
    }, [q, commands]);

    useEffect(() => { setIdx(0); }, [q]);

    function onKey(e) {
      if (e.key === 'ArrowDown') { e.preventDefault(); setIdx((i) => Math.min(i + 1, filtered.length - 1)); }
      else if (e.key === 'ArrowUp') { e.preventDefault(); setIdx((i) => Math.max(i - 1, 0)); }
      else if (e.key === 'Enter') {
        e.preventDefault();
        const cmd = filtered[idx];
        if (cmd) { cmd.action(); onClose(); }
      } else if (e.key === 'Escape') { e.preventDefault(); onClose(); }
    }

    const KIND_LABEL = { nav: 'Go to', tenant: 'Company', action: 'Action' };
    const KIND_TONE  = { nav: 'accent',  tenant: 'info',   action: 'success' };

    return (
      <>
        {/* Backdrop */}
        <div onClick={onClose} style={{
          position: 'fixed', inset: 0, zIndex: 80,
          background: 'rgba(15,17,21,0.32)',
          animation: 'op-fade-in 120ms ease-out',
        }} />

        {/* Centered panel */}
        <div role="dialog" aria-label="Command palette" style={{
          position: 'fixed', top: '18vh', left: '50%',
          transform: 'translateX(-50%)',
          width: 520, maxWidth: '90vw', maxHeight: '64vh',
          zIndex: 81,
          background: 'var(--op-surface-overlay)',
          border: '0.5px solid var(--op-border-strong)',
          borderRadius: 10, boxShadow: 'var(--op-shadow-overlay)',
          display: 'flex', flexDirection: 'column',
        }}>
          {/* Search input + scope chip */}
          <div style={{
            display: 'flex', alignItems: 'center', gap: 8,
            padding: '10px 12px', borderBottom: '0.5px solid var(--op-border)',
          }}>
            <span style={{ color: 'var(--op-text-tertiary)' }}>{Icons.Search ? <Icons.Search /> : '⌕'}</span>
            <input
              ref={inputRef}
              value={q}
              onChange={(e) => setQ(e.target.value)}
              onKeyDown={onKey}
              placeholder={'Search ' + tenant.name + '…'}
              style={{
                flex: 1, height: 26, border: 'none', outline: 'none',
                background: 'transparent', color: 'var(--op-text)',
                fontFamily: 'inherit', fontSize: 14,
              }}
            />
            <span className="op-pill op-pill--accent" style={{ height: 18, fontSize: 10.5 }}>
              in {tenant.shortLabel}
            </span>
            <kbd className="op-kbd">esc</kbd>
          </div>

          {/* Results */}
          <div style={{ flex: 1, overflowY: 'auto', padding: '4px 0' }}>
            {filtered.length === 0 && (
              <div style={{ padding: 24, fontSize: 13, color: 'var(--op-text-tertiary)', textAlign: 'center' }}>
                No matches. Try a page name, "settings", or "open chat"…
              </div>
            )}
            {filtered.map((c, i) => {
              const active = i === idx;
              return (
                <button key={c.kind + '-' + c.label} onMouseEnter={() => setIdx(i)}
                  onClick={() => { c.action(); onClose(); }}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 10,
                    width: '100%', padding: '8px 12px', border: 0,
                    background: active ? 'var(--op-row-selected-strong)' : 'transparent',
                    color: 'var(--op-text)', fontFamily: 'inherit', fontSize: 13,
                    cursor: 'pointer', textAlign: 'left',
                  }}>
                  <span className={'op-pill op-pill--' + KIND_TONE[c.kind]}
                    style={{ height: 18, fontSize: 10.5, padding: '0 6px', textTransform: 'uppercase', letterSpacing: 0.04 }}>
                    {KIND_LABEL[c.kind]}
                  </span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontWeight: active ? 600 : 500, color: active ? 'var(--op-accent-text)' : 'var(--op-text)' }}>{c.label}</div>
                    {c.sub && <div style={{ fontSize: 11, color: 'var(--op-text-tertiary)' }}>{c.sub}</div>}
                  </div>
                  {active && (Icons.ArrowRight ? <Icons.ArrowRight /> : '↵')}
                </button>
              );
            })}
          </div>

          {/* Footer hints */}
          <div style={{
            display: 'flex', alignItems: 'center', gap: 12,
            padding: '8px 12px', borderTop: '0.5px solid var(--op-border)',
            fontSize: 11, color: 'var(--op-text-tertiary)',
            fontFamily: 'var(--op-font-mono)',
          }}>
            <span><kbd className="op-kbd">↑</kbd><kbd className="op-kbd" style={{ marginLeft: 2 }}>↓</kbd> navigate</span>
            <span><kbd className="op-kbd">↵</kbd> select</span>
            <span><kbd className="op-kbd">esc</kbd> close</span>
            <span style={{ flex: 1 }} />
            <span>{filtered.length} result{filtered.length === 1 ? '' : 's'}</span>
          </div>
        </div>
      </>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Tenant switcher (sidebar header)
  // ─────────────────────────────────────────────────────────────
  function TenantSwitcher({ tenants, currentSlug, onSwitch, allowedTenants }) {
    const [open, setOpen] = useState(false);
    const current = tenants[currentSlug];
    const allowed = allowedTenants || window.OparetoTenant.list();
    const others  = window.OparetoTenant.list()
      .filter((s) => s !== currentSlug && allowed.indexOf(s) >= 0)
      .map((s) => tenants[s]);
    const canSwitch = others.length > 0;

    return (
      <div style={{ position: 'relative' }}>
        <button onClick={() => { if (canSwitch) setOpen((v) => !v); }}
          title={canSwitch ? 'Switch company' : 'Signed in to ' + current.name + ' only'}
          style={{
            display: 'flex', alignItems: 'center', gap: 9,
            width: '100%', padding: '8px 8px', border: 0,
            borderRadius: 6, background: open ? 'var(--op-row-selected)' : 'transparent',
            color: 'var(--op-text)', fontSize: 13, fontWeight: 600,
            fontFamily: 'inherit', cursor: canSwitch ? 'pointer' : 'default', textAlign: 'left',
            transition: 'background var(--op-duration-fast) var(--op-ease)',
          }}>
          <span style={{
            width: 24, height: 24, borderRadius: 'var(--op-radius-2)',
            background: 'var(--op-accent)', color: 'var(--op-text-inverse)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontFamily: 'var(--op-font-mono)', fontSize: 12, fontWeight: 700,
            flexShrink: 0,
          }}>{current.shortLabel}</span>
          <span style={{ lineHeight: 1.2, flex: 1, minWidth: 0, overflow: 'hidden' }}>
            {/* Tenant name in the Humanist display face (Bricolage Grotesque). */}
            <div className="op-display" style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontWeight: 700, fontSize: 14 }}>{current.name}</div>
            <div className="op-mono" style={{ fontSize: 10, color: 'var(--op-text-tertiary)', letterSpacing: 0.06, textTransform: 'uppercase', fontWeight: 500 }}>
              {current.domainShape}
            </div>
          </span>
          {canSwitch && Icons.ChevronDown && <Icons.ChevronDown size={12} />}
        </button>

        {open && (
          <div style={{
            position: 'absolute', top: '100%', left: 0, right: 0,
            marginTop: 4, padding: 4, zIndex: 10,
            background: 'var(--op-surface-overlay)',
            border: '0.5px solid var(--op-border-strong)',
            borderRadius: 7, boxShadow: 'var(--op-shadow-overlay)',
          }}>
            {others.map((t) => (
              <button key={t.slug} onClick={() => { onSwitch(t.slug); setOpen(false); }} style={{
                display: 'flex', alignItems: 'center', gap: 9,
                width: '100%', padding: '7px 8px', border: 0, borderRadius: 5,
                background: 'transparent', color: 'var(--op-text)',
                fontSize: 12.5, fontFamily: 'inherit', cursor: 'pointer', textAlign: 'left',
              }}
              onMouseEnter={(e) => e.currentTarget.style.background = 'var(--op-row-hover)'}
              onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                <span style={{
                  width: 24, height: 24, borderRadius: 5,
                  background: 'var(--op-bg-muted)', color: 'var(--op-text-secondary)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontFamily: 'var(--op-font-mono)', fontSize: 12, fontWeight: 700,
                }}>{t.shortLabel}</span>
                <span style={{ flex: 1, lineHeight: 1.2, minWidth: 0, overflow: 'hidden' }}>
                  <div style={{ fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.name}</div>
                  <div style={{ fontSize: 10.5, color: 'var(--op-text-tertiary)' }}>{t.domainShape}</div>
                </span>
              </button>
            ))}
            <div style={{ borderTop: '0.5px solid var(--op-border)', margin: '4px 0', padding: '6px 8px 2px',
              fontSize: 10.5, color: 'var(--op-text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.06,
            }}>
              Same shell · same primitives
            </div>
          </div>
        )}
      </div>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Sidebar
  // ─────────────────────────────────────────────────────────────
  function Sidebar({ tenants, tenantSlug, currentId, onSelect, onSwitchTenant, items, user, allowedTenants, settingsMode, settingsItems, settingsId, onSelectSettings, onExitSettings }) {
    const t = tenants[tenantSlug];
    return (
      <aside style={{
        width: 256, flexShrink: 0,
        borderRight: '0.5px solid var(--op-border)',
        background: 'var(--op-bg-subtle)',
        display: 'flex', flexDirection: 'column',
        height: '100%', overflowY: 'auto',
      }}>
        <header style={{ padding: '10px 10px 10px', borderBottom: '0.5px solid var(--op-border)' }}>
          <TenantSwitcher tenants={tenants} currentSlug={tenantSlug} onSwitch={onSwitchTenant} allowedTenants={allowedTenants} />
        </header>

        {settingsMode ? (
          <nav style={{ padding: '12px 8px', flex: 1 }}>
            {/* Back to the company's main nav */}
            <button onClick={onExitSettings}
              style={{
                display: 'flex', alignItems: 'center', gap: 6, width: '100%',
                padding: '8px 10px', marginBottom: 6, border: 0, borderRadius: 6,
                background: 'transparent', color: 'var(--op-text-secondary)',
                fontFamily: 'inherit', fontSize: 12.5, cursor: 'pointer', textAlign: 'left',
              }}
              onMouseEnter={(e) => e.currentTarget.style.background = 'var(--op-row-hover)'}
              onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
              <span style={{ fontSize: 14 }}>‹</span><span>Back to {t.name}</span>
            </button>
            <div style={{
              padding: '6px 10px 4px', fontSize: 10, fontWeight: 600, letterSpacing: 0.08,
              color: 'var(--op-text-tertiary)', textTransform: 'uppercase',
            }}>Settings</div>
            {settingsItems.map((s) => {
              const active = s.id === settingsId;
              return (
                <button key={s.id} onClick={() => onSelectSettings(s.id)}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8, width: '100%',
                    padding: '8px 10px', marginBottom: 2, border: 0, borderRadius: 6,
                    background: active ? 'var(--op-row-selected-strong)' : 'transparent',
                    color: active ? 'var(--op-accent-text)' : 'var(--op-text)',
                    fontFamily: 'inherit', fontSize: 13.5, fontWeight: active ? 600 : 500,
                    cursor: 'pointer', textAlign: 'left',
                  }}
                  onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--op-row-hover)'; }}
                  onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}>
                  {s.title}
                </button>
              );
            })}
          </nav>
        ) : (
        <nav style={{ padding: '12px 8px', flex: 1 }}>
          {items.filter((i) => !i._landingOnly && (!i._hidden
            // Experimental surfaces stay out of the sidebar until they're the
            // active screen, then reveal themselves (plus their section header).
            || (i._revealWhenActive && i.id === currentId)
            || (i._section && i._revealFor && i._revealFor === currentId)
          )).map((item) => {
            // Section header (non-clickable divider).
            if (item._section) {
              return (
                <div key={'sec-' + item.id} style={{
                  padding: '14px 10px 4px',
                  fontSize: 10, fontWeight: 600, letterSpacing: 0.08,
                  color: 'var(--op-text-tertiary)', textTransform: 'uppercase',
                }}>{item.title}</div>
              );
            }
            // If the active item is a hidden drill-down, highlight its parent
            // so the sidebar shows where you "came from."
            const activeItem = items.find((x) => x.id === currentId);
            const effectiveActive = activeItem && activeItem._hidden && activeItem._parent
              ? activeItem._parent
              : currentId;
            const active = item.id === effectiveActive;
            return (
              <button
                key={item.id}
                onClick={() => onSelect(item.id)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 8,
                  width: '100%',
                  padding: '8px 10px', marginBottom: 2,
                  border: 0, borderRadius: 6,
                  background: active ? 'var(--op-row-selected-strong)' : 'transparent',
                  color: active ? 'var(--op-text)' : 'var(--op-text-secondary)',
                  fontFamily: 'inherit',
                  cursor: 'pointer', textAlign: 'left',
                  transition: 'background var(--op-duration-fast) var(--op-ease)',
                }}
                onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--op-row-hover)'; }}
                onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}
              >
                <div style={{ flex: 1, minWidth: 0, lineHeight: 1.25 }}>
                  <div style={{
                    fontSize: 13.5, fontWeight: active ? 600 : 500,
                    color: active ? 'var(--op-accent-text)' : 'var(--op-text)',
                  }}>{item.title}</div>
                  {item.subtitle && (
                    <div style={{ fontSize: 11, color: 'var(--op-text-tertiary)', marginTop: 1 }}>{item.subtitle}</div>
                  )}
                </div>
                {item._drillIn && (
                  <span style={{ color: 'var(--op-text-tertiary)', fontSize: 14, flexShrink: 0 }}>›</span>
                )}
              </button>
            );
          })}
        </nav>
        )}

        <footer style={{
          borderTop: '0.5px solid var(--op-border)',
          padding: '10px 12px',
          display: 'flex', alignItems: 'center', gap: 8,
          fontSize: 11.5, color: 'var(--op-text-tertiary)',
        }}>
          <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {user ? user.name : t.primaryContacts[0].name}
          </span>
          {user && (
            <button onClick={() => { if (window.OparetoAuth) window.OparetoAuth.logout(); }}
              title="Sign out"
              style={{
                background: 'transparent', border: 0, padding: '2px 6px', borderRadius: 4,
                fontSize: 11, color: 'var(--op-text-tertiary)', cursor: 'pointer', fontFamily: 'inherit',
              }}
              onMouseEnter={(e) => e.currentTarget.style.color = 'var(--op-danger-text)'}
              onMouseLeave={(e) => e.currentTarget.style.color = 'var(--op-text-tertiary)'}>
              Sign out
            </button>
          )}
          {tenantSlug === 'opareto' && <span className="op-mono" style={{ fontSize: 10.5 }}>v0.3</span>}
        </footer>
      </aside>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Header
  // ─────────────────────────────────────────────────────────────
  function Header({ tenant, item, isDark, onToggleTheme, onOpenPalette, parentCrumb, crumbTitle }) {
    const title = crumbTitle || (item ? item.title : 'Untitled');
    return (
      <header style={{
        height: 44, flexShrink: 0,
        borderBottom: '0.5px solid var(--op-border)',
        display: 'flex', alignItems: 'center',
        padding: '0 16px', gap: 14,
        background: 'var(--op-surface)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
          <span style={{ color: 'var(--op-text-tertiary)' }}>{tenant.name}</span>
          <span style={{ color: 'var(--op-text-tertiary)' }}>›</span>
          {parentCrumb && (
            <React.Fragment>
              <span style={{ color: 'var(--op-text-tertiary)' }}>{parentCrumb}</span>
              <span style={{ color: 'var(--op-text-tertiary)' }}>›</span>
            </React.Fragment>
          )}
          <span className="op-display" style={{ color: 'var(--op-text)', fontWeight: 700 }}>{title}</span>
        </div>

        <div style={{ flex: 1 }} />

        {/* Search-button trigger for the palette — matches the live AppShell
            affordance (header search button alongside ⌘K shortcut). */}
        <button onClick={onOpenPalette} title="Open command palette"
          style={{
            display: 'inline-flex', alignItems: 'center', gap: 8,
            height: 26, padding: '0 8px 0 10px',
            border: '0.5px solid var(--op-border)', borderRadius: 6,
            background: 'var(--op-surface)', color: 'var(--op-text-tertiary)',
            fontFamily: 'inherit', fontSize: 12, cursor: 'pointer',
          }}
          onMouseEnter={(e) => e.currentTarget.style.background = 'var(--op-bg-subtle)'}
          onMouseLeave={(e) => e.currentTarget.style.background = 'var(--op-surface)'}>
          {Icons.Search ? <Icons.Search size={12} /> : '⌕'}
          <span>Search…</span>
          <span style={{ marginLeft: 6 }}>
            <kbd className="op-kbd" style={{ fontSize: 9.5 }}>⌘</kbd><kbd className="op-kbd" style={{ marginLeft: 2, fontSize: 9.5 }}>K</kbd>
          </span>
        </button>

        {tenant.slug === 'opareto' && (
          <a href="https://github.com/Opareto/opareto-platform" target="_blank" rel="noreferrer"
             style={{ fontSize: 12, color: 'var(--op-text-tertiary)', textDecoration: 'none' }}>
            Source
          </a>
        )}

        <button className="op-btn op-btn--icon op-btn--sm" onClick={onToggleTheme} aria-label="Toggle theme"
                style={{ color: 'var(--op-text-secondary)' }}>
          {isDark ? (Icons.Sun ? <Icons.Sun /> : '☀') : (Icons.Moon ? <Icons.Moon /> : '☾')}
        </button>
      </header>
    );
  }

  function MobileHeader({ tenant, item, items, onSelect }) {
    const visible = (items || []).filter((i) => !i._section && !i._hidden && !i._landingOnly).slice(0, 5);
    return (
      <header style={{
        flexShrink: 0,
        background: 'var(--op-surface)',
        borderBottom: '0.5px solid var(--op-border)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 54, padding: '8px 12px' }}>
          <span className="op-avatar" style={{ width: 30, height: 30, fontSize: 11, background: tenant.color, color: 'var(--op-text-inverse)', flexShrink: 0 }}>{tenant.shortLabel}</span>
          <div style={{ minWidth: 0, flex: 1 }}>
            <div style={{ fontSize: 13, fontWeight: 600, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>{tenant.name}</div>
            <div style={{ marginTop: 2, fontSize: 11, color: 'var(--op-text-tertiary)', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>{item ? item.title : 'Prototype'}</div>
          </div>
        </div>
        <nav style={{ display: 'flex', gap: 6, overflowX: 'auto', padding: '0 10px 10px' }}>
          {visible.map((navItem) => {
            const active = item && (item.id === navItem.id || item._parent === navItem.id);
            return (
              <button key={navItem.id} type="button" onClick={() => onSelect(navItem.id)}
                style={{
                  flex: '0 0 auto',
                  height: 30,
                  padding: '0 10px',
                  borderRadius: 999,
                  border: '0.5px solid ' + (active ? 'var(--op-accent-border)' : 'var(--op-border)'),
                  background: active ? 'var(--op-accent-soft)' : 'var(--op-bg)',
                  color: active ? 'var(--op-accent-text)' : 'var(--op-text-secondary)',
                  fontFamily: 'inherit',
                  fontSize: 12,
                  fontWeight: active ? 600 : 500,
                  cursor: 'pointer',
                }}>{navItem.title}</button>
            );
          })}
        </nav>
      </header>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Main pane (iframe + chat FAB + chat overlay)
  // ─────────────────────────────────────────────────────────────
  function MainPane({ src, tenant, item, chatOpen, onToggleChat, mobile, recording }) {
    const ref = useRef(null);
    const build = window.__OPARETO_BUILD = window.__OPARETO_BUILD || Date.now();
    const separator = src.indexOf('?') >= 0 ? '&' : '?';
    const frameSrc = src + separator + 'tenant=' + encodeURIComponent(tenant.slug) + '&_v=' + build;
    function onLoad() {
      const ifr = ref.current;
      if (!ifr) return;
      try {
        if (window.OparetoTheme) window.OparetoTheme.applyToIframe(ifr);
        const doc = ifr.contentDocument;
        if (window.OparetoBanner && doc) window.OparetoBanner.inject(doc);
        // The shell owns the single ambient ⌘J launcher (the FAB below). Screens
        // render their own .launcher for standalone viewing; hide it in-shell so
        // we don't double up bottom-right.
        if (doc && !doc.getElementById('op-shell-dedupe')) {
          const st = doc.createElement('style');
          st.id = 'op-shell-dedupe';
          st.textContent = '.launcher,[class*="launcher"]{display:none !important;}';
          (doc.head || doc.documentElement).appendChild(st);
        }
      } catch (e) { console.warn('iframe post-load hook failed', e); }
    }
    return (
      <div style={{ flex: 1, minWidth: 0, background: 'var(--op-bg)', position: 'relative' }}>
        <iframe ref={ref} src={frameSrc} onLoad={onLoad} title="Prototype surface"
          style={{ width: '100%', height: '100%', border: 0, background: 'var(--op-bg)', display: 'block' }} />
        {!recording && !item._hideChat && <ChatFAB tenant={tenant} open={chatOpen} onClick={onToggleChat} mobile={mobile} />}
        {chatOpen && !recording && !item._hideChat && <ChatOverlay tenant={tenant} item={item} onClose={onToggleChat} />}
      </div>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Chat FAB — floating action button anchored bottom-right of the
  // main pane. Matches the live AppShell affordance (the FAB is the
  // discoverable trigger; ⌘J is the keyboard shortcut).
  // ─────────────────────────────────────────────────────────────
  function ChatFAB({ tenant, open, onClick, mobile }) {
    if (open) return null;
    const assistantName = tenant.slug === 'atlantic'
      ? 'Atlantic Mechanical Assistant'
      : tenant.name + ' Assistant';
    return (
      <button
        type="button"
        onClick={onClick}
        title={'Ask ' + assistantName + ' (⌘J)'}
        aria-label={'Ask ' + assistantName}
        style={{
          position: 'absolute', right: mobile ? 14 : 26, bottom: mobile ? 14 : 24, zIndex: 60,
          height: 46, padding: '0 8px 0 16px',
          display: 'inline-flex', alignItems: 'center', gap: 10,
          background: '#fff',
          color: 'var(--op-accent-text)',
          border: '1px solid var(--op-accent-border)', borderRadius: 999,
          fontFamily: 'inherit', fontSize: 13.5, fontWeight: 700,
          cursor: 'pointer',
          boxShadow: '0 2px 4px rgba(40,36,28,.06), 0 20px 40px -20px rgba(75,84,168,0.45)',
          transition: 'transform .16s, box-shadow .16s',
        }}
        onMouseEnter={(e) => e.currentTarget.style.transform = 'translateY(-2px)'}
        onMouseLeave={(e) => e.currentTarget.style.transform = 'translateY(0)'}>
        <span style={{
          width: 30, height: 30, borderRadius: 999, flexShrink: 0,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          background: 'linear-gradient(150deg,#6c79c9,#4f5db0)', color: '#fff',
        }}>{Icons.Sparkle ? <Icons.Sparkle size={15} /> : '✦'}</span>
        <span>Ask {assistantName}</span>
        <span style={{
          fontFamily: 'var(--op-font-mono)', fontSize: 11, fontWeight: 700,
          padding: '2px 6px', borderRadius: 7,
          background: 'var(--op-accent-soft)', color: 'var(--op-accent-text)',
          border: '1px solid var(--op-accent-border)',
        }}>⌘J</span>
      </button>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Chat Overlay — 460px right-anchored slide-in panel.
  // Mirrors apps/platform/src/components/chat-overlay/ChatOverlayPanel.tsx.
  // Two views: 'active' (current chat) and 'history' (prior thread list).
  // Reads conversations.byTenant[<slug>] for thread + messages + GenUI.
  // ─────────────────────────────────────────────────────────────
  const CONTEXTUAL_HINT = {
    inbox:     'the Inbox',
    pipeline:  'Bidding',
    tasks:     'My Tasks',
    'wo-dashboard': 'Work orders',
    feedback:  'the Feedback queue',
    bugs:      'the Bug tracker',
    deals:     'the Deal flow board',
    knowledge: 'the Knowledge view',
    settings:  'Settings',
    'field-updates': 'Field Updates',
    projects:  'Projects',
    estimates: 'Estimates',
  };

  function ChatOverlay({ tenant, item, onClose }) {
    const [view, setView] = useState('active');
    const [composer, setComposer] = useState('');
    const [activeThreadId, setActiveThreadId] = useState(null);

    const data = (window.OparetoMocks && window.OparetoMocks.get('conversations')) || {};
    const threads = (data.byTenant && data.byTenant[tenant.slug]) || [];
    useEffect(() => { if (!activeThreadId && threads.length > 0) setActiveThreadId(threads[0].id); }, [threads, activeThreadId]);
    const active = threads.find((t) => t.id === activeThreadId) || threads[0] || null;

    const agentName  = tenant.slug === 'atlantic' ? 'Atlantic Mechanical Assistant' : tenant.name + ' Assistant';
    const agentSub   = tenant.slug === 'atlantic' ? 'Grounded in your jobs, bids, and knowledge' : tenant.subtitle;
    const tenantColor = tenant.color || 'var(--op-accent)';
    const initials   = tenant.slug === 'opareto' ? 'OP' : tenant.slug === 'atlantic' ? 'AM' : tenant.slug === 'ebtf' ? 'EB' : tenant.shortLabel;
    const hint = CONTEXTUAL_HINT[item.id] || item.title;

    // Esc closes
    useEffect(() => {
      function onKey(e) { if (e.key === 'Escape') onClose(); }
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [onClose]);

    function newChat() { setActiveThreadId(null); setView('active'); }

    return (
      <aside style={{
        position: 'absolute', top: 0, right: 0, bottom: 0, width: 460, maxWidth: '100vw',
        background: 'var(--op-bg)',
        borderLeft: '0.5px solid var(--op-border)',
        boxShadow: '-12px 0 40px rgba(15,17,21,0.12)',
        display: 'flex', flexDirection: 'column', zIndex: 70,
        animation: 'op-slide-in 220ms ease-out',
      }}>
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '12px 14px', borderBottom: '0.5px solid var(--op-border)' }}>
          <span className="op-avatar" style={{
            width: 26, height: 26, fontSize: 11, fontWeight: 600,
            background: tenantColor, color: 'var(--op-text-inverse)',
          }}>{initials}</span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="op-display" style={{ fontSize: 13.5, fontWeight: 700 }}>{agentName}</div>
            <div style={{ fontSize: 10.5, color: 'var(--op-text-tertiary)', display: 'flex', alignItems: 'center', gap: 4 }}>
              <span className="op-dot op-dot--success" /><span>{agentSub}</span>
            </div>
          </div>
          <OverlayHeaderBtn label="⏱" title={view === 'history' ? 'Back to chat' : 'Conversation history'} active={view === 'history'} onClick={() => setView((v) => v === 'history' ? 'active' : 'history')} />
          <OverlayHeaderBtn label="+" title="New chat" onClick={newChat} />
          <OverlayHeaderBtn label="×" title="Close (Esc)" onClick={onClose} />
        </div>

        {/* Contextual hint */}
        {view === 'active' && hint && (
          <div style={{
            display: 'flex', alignItems: 'center', gap: 6,
            margin: '10px 14px 0', padding: '8px 12px',
            background: 'var(--op-accent-soft)', border: '0.5px solid var(--op-accent-border)',
            borderRadius: 6, fontSize: 11.5, color: 'var(--op-accent-text)',
          }}>
            <span style={{ flex: 1 }}>You're on <span className="op-mono" style={{ fontWeight: 600 }}>{hint}</span>. {agentName} can answer about this.</span>
          </div>
        )}

        {/* Body */}
        <div style={{ flex: 1, overflowY: 'auto', padding: '12px 0' }}>
          {view === 'history' ? (
            <HistoryList threads={threads} activeId={activeThreadId} onSelect={(id) => { setActiveThreadId(id); setView('active'); }} />
          ) : !active ? (
            <ChatEmptyState agentName={agentName} tenantSlug={tenant.slug} onClose={onClose} />
          ) : (
            active.turns.map((tr, i) => <ChatTurn key={i} turn={tr} initials={initials} color={tenantColor} />)
          )}
        </div>

        {/* Composer (hidden in history view) */}
        {view !== 'history' && (
          <div style={{ borderTop: '0.5px solid var(--op-border)', padding: 12, background: 'var(--op-bg)' }}>
            <div style={{ display: 'flex', gap: 10, alignItems: 'flex-end' }}>
              <div style={{ flex: 1, padding: '8px 10px', border: '0.5px solid var(--op-border)', borderRadius: 6, background: 'var(--op-surface)' }}>
                <textarea
                  value={composer}
                  onChange={(e) => setComposer(e.target.value)}
                  placeholder="Ask anything…"
                  rows={1}
                  style={{
                    width: '100%', border: 'none', outline: 'none', resize: 'none',
                    fontFamily: 'inherit', fontSize: 13, lineHeight: 1.5,
                    background: 'transparent', color: 'var(--op-text)', padding: 0,
                    height: composer.includes('\n') ? 60 : 22,
                  }}
                />
              </div>
              <button type="button" className="op-btn op-btn--primary op-btn--sm"
                style={{ height: 34, width: 34, padding: 0, justifyContent: 'center' }}
                aria-label="Send" disabled={!composer.trim()}>↑</button>
            </div>
            <div style={{
              display: 'flex', justifyContent: 'space-between', alignItems: 'center',
              marginTop: 8, fontSize: 10.5, color: 'var(--op-text-tertiary)', fontFamily: 'var(--op-font-mono)',
            }}>
              <span><kbd className="op-kbd" style={{ fontSize: 9.5 }}>↵</kbd> send · <kbd className="op-kbd" style={{ fontSize: 9.5 }}>⇧↵</kbd> newline · <kbd className="op-kbd" style={{ fontSize: 9.5 }}>Esc</kbd> close</span>
            </div>
          </div>
        )}
      </aside>
    );
  }

  function OverlayHeaderBtn({ label, title, active, onClick }) {
    return (
      <button type="button" onClick={onClick} title={title} aria-pressed={active || undefined}
        style={{
          width: 28, height: 28,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          background: active ? 'var(--op-accent-soft)' : 'transparent',
          color: active ? 'var(--op-accent-text)' : 'var(--op-text-secondary)',
          border: 'none', borderRadius: 4, cursor: 'pointer',
          fontSize: 16, lineHeight: 1, fontFamily: 'inherit',
        }}>{label}</button>
    );
  }

  function ChatEmptyState({ agentName, tenantSlug, onClose }) {
    const QUICK = {
      atlantic: ['Which change would improve PLNT Burger’s margin most?', 'What did we charge Suffolk for similar work?', 'Why is HOOD-1 quantity 10?'],
      ebtf:     ['Score the Cogenta Neural deck', 'Sci-scan TMS for treatment-resistant depression', 'Comp landscape for BCI consumer'],
      opareto:  ['What feedback came in today?', 'What bugs are open?', 'Help me prep for the eBTF close call'],
      mchome:   ['What changed across sites today?', 'Which change orders need review?', 'Which crews need a follow-up?'],
      envirocon:['What is still unscheduled today?', 'Draft the invoice for WO-4521', "Who hasn't logged hours?", "What's past due in AR?"],
      pandair:  ['Which turnarounds are slipping right now?', 'Draft the de-ice slot request for LH441', 'What needs a manager decision today?', "Which stands are over-committed at 14:00?"],
      parkview: ['Which verifications are due before tomorrow?', "Verify Aetna PPO for the 9:00 recall", 'What plans need a callback today?', "Which patients are missing a renewal?"],
      quality:  ['What did we miss yesterday?', 'Draft the Porky follow-up', "Who's open Thursday?"],
    };
    const actions = QUICK[tenantSlug] || [];
    function workWithAgent() {
      try { window.postMessage({ type: 'navigate', screen: 'agent-working' }, window.location.origin); } catch (_) {}
      if (onClose) onClose();
    }
    return (
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 12, padding: '12px 14px', color: 'var(--op-text-secondary)', fontSize: 13.5, lineHeight: 1.55 }}>
        <p style={{ margin: 0 }}>Ask anything, or give it a task to work through.</p>

        {/* Primary: hand the agent a task and watch it work */}
        <button type="button" onClick={workWithAgent}
          style={{
            display: 'flex', alignItems: 'flex-start', gap: 10, alignSelf: 'stretch',
            padding: '12px 13px', background: 'var(--op-accent-soft)',
            border: '0.5px solid var(--op-accent-border)', borderRadius: 8,
            color: 'var(--op-accent-text)', textAlign: 'left',
            cursor: 'pointer', fontFamily: 'inherit',
          }}>
          <span style={{ fontSize: 16, lineHeight: 1.2 }}>▸</span>
          <span style={{ flex: 1 }}>
            <span style={{ display: 'block', fontWeight: 600, fontSize: 13.5 }}>Work a task with the agent</span>
            <span style={{ display: 'block', fontSize: 12.5, color: 'var(--op-text-secondary)', marginTop: 2 }}>
              See its plan, guide it, and approve each step.
            </span>
          </span>
        </button>

        <div style={{ alignSelf: 'stretch', fontSize: 11.5, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.4, color: 'var(--op-text-tertiary)', marginTop: 2 }}>Or ask a quick question</div>
        <div style={{ display: 'flex', flexDirection: 'column', alignSelf: 'stretch', gap: 5 }}>
          {actions.map((q) => (
            <button key={q} type="button"
              style={{
                display: 'flex', alignItems: 'center', gap: 8,
                padding: '9px 12px', background: 'var(--op-bg)',
                border: '0.5px solid var(--op-border)', borderRadius: 999,
                fontSize: 13, color: 'var(--op-text)', textAlign: 'left',
                cursor: 'pointer', fontFamily: 'inherit',
              }}>
              <span style={{ color: 'var(--op-accent-text)', fontWeight: 600 }}>›</span>
              <span style={{ flex: 1 }}>{q}</span>
            </button>
          ))}
        </div>
        <p style={{ margin: '4px 0 0', fontSize: 12, color: 'var(--op-text-tertiary)' }}>
          Press <kbd className="op-kbd" style={{ fontSize: 10.5 }}>⌘J</kbd> from any page to bring this back.
        </p>
      </div>
    );
  }

  function HistoryList({ threads, activeId, onSelect }) {
    if (threads.length === 0) {
      return <div style={{ padding: '24px 14px', color: 'var(--op-text-tertiary)', fontSize: 12.5, textAlign: 'center' }}>No prior conversations yet.</div>;
    }
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 1, padding: '0 8px' }}>
        <div style={{ padding: '6px 8px 4px', fontSize: 10.5, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5, color: 'var(--op-text-tertiary)' }}>
          Recent conversations
        </div>
        {threads.map((c) => (
          <button key={c.id} type="button" onClick={() => onSelect(c.id)}
            style={{
              display: 'flex', alignItems: 'center', gap: 8,
              padding: '8px 10px',
              background: c.id === activeId ? 'var(--op-accent-soft)' : 'transparent',
              border: 'none', borderRadius: 5, fontSize: 12.5,
              color: c.id === activeId ? 'var(--op-accent-text)' : 'var(--op-text)',
              cursor: 'pointer', textAlign: 'left',
              fontWeight: c.id === activeId ? 500 : 400, fontFamily: 'inherit',
            }}>
            <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={c.title}>{c.title}</span>
          </button>
        ))}
      </div>
    );
  }

  function ChatTurn({ turn, initials, color }) {
    const isUser = turn.role === 'user';
    return (
      <div style={{ padding: '8px 14px' }}>
        <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
          {!isUser && (
            <span className="op-avatar" style={{
              width: 22, height: 22, fontSize: 10, fontWeight: 600,
              background: color, color: 'var(--op-text-inverse)', flexShrink: 0,
            }}>{initials}</span>
          )}
          {isUser && <div style={{ width: 22, flexShrink: 0 }} />}
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 12.5, color: 'var(--op-text)', lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>{turn.text}</div>
            {turn.tool && (
              <div style={{
                display: 'inline-flex', alignItems: 'center', gap: 6,
                marginTop: 8, padding: '4px 8px',
                background: 'var(--op-surface-inset)', border: '0.5px solid var(--op-border)',
                borderRadius: 5, fontSize: 11, color: 'var(--op-text-secondary)',
                fontFamily: 'var(--op-font-mono)',
              }}>
                <span className="op-dot op-dot--success" />
                <span>{turn.tool}</span>
              </div>
            )}
            {turn.evidence && turn.evidence.length > 0 && (
              <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {turn.evidence.map((ev, i) => (
                  <span key={i} title={ev.ref} style={{
                    display: 'inline-flex', alignItems: 'center', gap: 4,
                    padding: '0 6px', height: 18,
                    border: '0.5px solid var(--op-accent-border)',
                    borderRadius: 999, background: 'var(--op-accent-soft)',
                    color: 'var(--op-accent-text)', fontSize: 10.5, fontWeight: 500,
                  }}>
                    <span style={{
                      display: 'inline-flex', width: 13, height: 13, borderRadius: 999,
                      background: 'var(--op-accent)', color: 'var(--op-text-inverse)',
                      alignItems: 'center', justifyContent: 'center',
                      fontFamily: 'var(--op-font-mono)', fontSize: 9, fontWeight: 700,
                    }}>{i + 1}</span>
                    {ev.label}
                  </span>
                ))}
              </div>
            )}
          </div>
        </div>
      </div>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Login gate — gates the shell on first load. Three users:
  //   joe (Atlantic) · ugo (eBTF) · opareto (admin · all companies)
  // Pre-fill via ?u=<slug> in the URL.
  // ─────────────────────────────────────────────────────────────
  function LoginGate({ onLogin }) {
    const auth = window.OparetoAuth || { users: () => [], userMeta: () => null, login: () => ({ ok: false }) };
    const [slug, setSlug] = useState(auth.preferredSlug || '');
    const [pw, setPw]     = useState('');
    const [err, setErr]   = useState(null);
    const slugRef         = useRef(null);
    const pwRef           = useRef(null);

    useEffect(() => {
      // Focus the relevant field on mount.
      const target = slug ? pwRef.current : slugRef.current;
      if (target) setTimeout(() => target.focus(), 30);
    }, []);

    function submit(e) {
      e.preventDefault();
      const out = auth.login(slug.trim(), pw);
      if (out.ok) { setErr(null); onLogin(out.user); }
      else if (out.reason === 'unknown-user')   setErr('Unknown username.');
      else if (out.reason === 'wrong-password') setErr('Wrong password.');
      else                                       setErr('Sign-in failed.');
    }

    const slugMeta = slug ? auth.userMeta(slug.trim()) : null;

    return (
      <div style={{
        position: 'fixed', inset: 0, zIndex: 100,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        background: 'var(--op-bg)',
        backgroundImage: 'radial-gradient(circle at 30% 20%, rgba(91,106,191,0.08), transparent 55%), radial-gradient(circle at 70% 80%, rgba(126,58,242,0.06), transparent 55%)',
        fontFamily: 'inherit',
      }}>
        <div style={{
          width: 360, maxWidth: '92%',
          background: 'var(--op-surface-overlay)',
          border: '0.5px solid var(--op-border-strong)',
          borderRadius: 12, boxShadow: 'var(--op-shadow-overlay)',
          padding: 28,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 18 }}>
            <div style={{
              width: 36, height: 36, borderRadius: 8,
              background: 'var(--op-accent)', color: 'var(--op-text-inverse)',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              fontFamily: 'var(--op-font-mono)', fontSize: 16, fontWeight: 700,
            }}>O</div>
            <div>
              <div style={{ fontSize: 15, fontWeight: 600, letterSpacing: -0.2 }}>Opareto Platform</div>
              <div style={{ fontSize: 11, color: 'var(--op-text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.06, fontWeight: 500 }}>prototype · sign in</div>
            </div>
          </div>

          <form onSubmit={submit}>
            <label style={{ display: 'block', fontSize: 11, fontWeight: 500, color: 'var(--op-text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.06, marginBottom: 4 }}>Username</label>
            <input
              ref={slugRef}
              value={slug}
              onChange={(e) => { setSlug(e.target.value); setErr(null); }}
              placeholder="Username"
              autoComplete="username"
              spellCheck={false}
              style={{
                width: '100%', height: 32, padding: '0 10px', marginBottom: 12,
                border: '0.5px solid var(--op-border)', borderRadius: 5,
                background: 'var(--op-bg)', color: 'var(--op-text)',
                fontFamily: 'inherit', fontSize: 13.5, outline: 'none',
                boxSizing: 'border-box',
              }} />

            <label style={{ display: 'block', fontSize: 11, fontWeight: 500, color: 'var(--op-text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.06, marginBottom: 4 }}>Password</label>
            <input
              ref={pwRef}
              type="password"
              value={pw}
              onChange={(e) => { setPw(e.target.value); setErr(null); }}
              placeholder="••••••••"
              autoComplete="current-password"
              style={{
                width: '100%', height: 32, padding: '0 10px', marginBottom: 12,
                border: '0.5px solid var(--op-border)', borderRadius: 5,
                background: 'var(--op-bg)', color: 'var(--op-text)',
                fontFamily: 'inherit', fontSize: 13.5, outline: 'none',
                boxSizing: 'border-box',
              }} />

            {err && (
              <div style={{
                padding: '6px 10px', marginBottom: 10,
                background: 'var(--op-danger-soft)', color: 'var(--op-danger-text)',
                border: '0.5px solid var(--op-danger-border)', borderRadius: 5,
                fontSize: 12,
              }}>{err}</div>
            )}

            <button type="submit" className="op-btn op-btn--primary" style={{ width: '100%', height: 34, fontSize: 13 }}>
              Sign in
              {slugMeta && <span style={{ marginLeft: 6, opacity: 0.7, fontSize: 11 }}>→ {slugMeta.org}</span>}
            </button>
          </form>
        </div>
      </div>
    );
  }

  // ─────────────────────────────────────────────────────────────
  // Root — auth gate wrapper. Keeps rules-of-hooks clean by mounting
  // either <LoginGate> or <Shell>, never both inside the same component.
  // ─────────────────────────────────────────────────────────────
  // A fresh sign-in never lands on any future experimental
  // (_revealWhenActive) screen. Visible destinations such as
  // #atlantic/tasks and hidden drill-down demo routes remain shareable.
  // The tenant fallback mirrors Shell's initial-hash resolver so a malformed
  // tenant prefix (#ATLANTIC/tasks) can't sneak past the scrub.
  function scrubHiddenHash(user) {
    let h = '';
    try { h = decodeURIComponent((window.location.hash || '').slice(1)); } catch (_) {}
    if (!h) return;
    const [tPart, iPart] = h.includes('/') ? h.split('/') : [null, h];
    const nav = NAV_BY_TENANT[tPart] || (user && NAV_BY_TENANT[user.defaultTenant]) || [];
    const found = findItem(nav, iPart);
    if (found && found._revealWhenActive) {
      try { window.history.replaceState(null, '', window.location.pathname + window.location.search); } catch (_) {}
    }
  }

  function App() {
    const [user, setUser] = useState((window.OparetoAuth && window.OparetoAuth.user) || null);
    if (!user) {
      return <LoginGate onLogin={(u) => { scrubHiddenHash(u); setUser(u); }} />;
    }
    return <Shell user={user} />;
  }

  function Shell({ user }) {
    // A cold load honors the hash's tenant when the user has access to it
    // (matches what the hashchange listener does for live edits); otherwise
    // the user's default tenant wins.
    const [tenantSlug, setTenantSlug] = useState(() => {
      let h = '';
      try { h = decodeURIComponent((window.location.hash || '').slice(1)); } catch (_) {}
      const tPart = h.includes('/') ? h.split('/')[0] : null;
      return (tPart && NAV_BY_TENANT[tPart] && user.tenants.indexOf(tPart) >= 0) ? tPart : user.defaultTenant;
    });
    const recordingStarted = useRef(false);
    const [recordingMessage, setRecordingMessage] = useState('');
    const [isNarrow, setIsNarrow] = useState(() => window.matchMedia ? window.matchMedia('(max-width: 760px)').matches : false);
    const params = new URLSearchParams(window.location.search);
    const forceMobile = params.has('mobile');
    const isMobile = forceMobile || isNarrow;
    const isRecording = params.has('recording');

    const tenantsRaw = (window.OparetoMocks && window.OparetoMocks.get('tenants')) || {};
    const tenants = tenantsRaw.tenants || {
      opareto:  { slug: 'opareto',  name: 'Opareto · admin',           shortLabel: 'O', domainShape: 'platform-admin',   color: '#5b6abf', subtitle: 'Admin · cross-tenant',         primaryContacts: [{ name: 'Ajay Surie' }] },
      atlantic: { slug: 'atlantic', name: 'Atlantic Mechanical',       shortLabel: 'A', domainShape: 'operations',       color: '#1f6feb', subtitle: 'Estimating · commercial mech', primaryContacts: [{ name: 'Joe Gratta' }] },
      ebtf:     { slug: 'ebtf',     name: 'European BrainTech Fund',   shortLabel: 'E', domainShape: 'knowledge work',   color: '#7e3af2', subtitle: 'Brain-tech VC · diligence',    primaryContacts: [{ name: 'Ugo Tomasello' }] },
      mchome:   { slug: 'mchome',   name: 'MC Home Improvements & Construction', shortLabel: 'MC', domainShape: 'operations', color: '#2f7d57', subtitle: 'Residential construction · field ops', primaryContacts: [{ name: 'MC Home owner' }] },
      envirocon:{ slug: 'envirocon',name: 'Envirocon',                 shortLabel: 'EC', domainShape: 'operations',      color: '#0e7490', subtitle: 'HVAC service · dispatch + invoicing', primaryContacts: [{ name: 'Carolyn Videla' }] },
      pandair:  { slug: 'pandair',  name: 'Pandair',                   shortLabel: 'PA', domainShape: 'operations',      color: '#c2410c', subtitle: 'Ground handling · ramp + turnarounds', primaryContacts: [{ name: 'Marco Reyes' }] },
      parkview: { slug: 'parkview', name: 'Parkview Dental',           shortLabel: 'PV', domainShape: 'operations',      color: '#be185d', subtitle: 'Dental · insurance verification', primaryContacts: [{ name: 'Dana Brooks' }] },
      quality:  { slug: 'quality',  name: 'Quality Refrigeration',     shortLabel: 'QR', domainShape: 'operations',      color: '#0e7490', subtitle: 'Commercial refrigeration · 3rd-gen NJ shop', primaryContacts: [{ name: 'Fred Westphalen' }] },
    };

    const items = NAV_BY_TENANT[tenantSlug] || NAV_BY_TENANT.opareto;

    // Hash format: #<tenant>/<itemId>
    const initial = useMemo(() => {
      let h = '';
      try { h = decodeURIComponent((window.location.hash || '').slice(1)); } catch (_) {}
      const [tPart, iPart] = h.includes('/') ? h.split('/') : [tenantSlug, h];
      const tenantItems = NAV_BY_TENANT[tPart] || items;
      const found = findItem(tenantItems, iPart);
      const firstReal = tenantItems.find((i) => !i._section && !i._hidden) || tenantItems[0];
      return found && !found._section ? found.id : firstReal.id;
    }, []);

    const [currentId, setCurrentId] = useState(initial);
    const [isDark, setIsDark] = useState(
      window.OparetoTheme ? window.OparetoTheme.current() === 'dark' : false
    );

    // Settings drill-in: when on, the sidebar swaps to the settings sections.
    const [settingsMode, setSettingsMode] = useState(false);
    const settingsNav = settingsNavFor(tenantSlug);
    const [settingsId, setSettingsId] = useState(settingsNav[0].id);
    const settingsItem = settingsNav.find((s) => s.id === settingsId) || settingsNav[0];

    const item = findItem(items, currentId) || items.find((i) => !i._section && !i._hidden) || items[0];

    // Selecting a main-nav item. Drill-in items (Settings) open settings mode
    // on desktop (sidebar swap); on mobile there's no sidebar, so just load the
    // full tabbed settings screen.
    function selectMain(id) {
      const target = findItem(items, id);
      if (target && target._drillIn && !isMobile) {
        setSettingsMode(true);
        setSettingsId(settingsNav[0].id);
        return;
      }
      setSettingsMode(false);
      setCurrentId(id);
    }
    function exitSettings() { setSettingsMode(false); }

    // The screen + breadcrumb depend on whether we're in settings mode.
    const activeScreen = settingsMode ? settingsItem.screen : item.screen;

    useEffect(() => {
      if (!window.matchMedia) return undefined;
      const media = window.matchMedia('(max-width: 760px)');
      function update() { setIsNarrow(media.matches); }
      update();
      if (media.addEventListener) media.addEventListener('change', update);
      else media.addListener(update);
      return () => {
        if (media.removeEventListener) media.removeEventListener('change', update);
        else media.removeListener(update);
      };
    }, []);

    useEffect(() => {
      if (item) window.history.replaceState(null, '', window.location.pathname + window.location.search + '#' + tenantSlug + '/' + item.id);
    }, [item, tenantSlug]);

    // Manual hash edits navigate. replaceState (above) never fires
    // hashchange, so in-app navigation can't loop through this listener.
    // Ignored edits (gated tenant, unknown id) revert the address bar to the
    // canonical hash so the URL never shows a route the app isn't rendering.
    useEffect(() => {
      function revert() {
        if (item) { try { window.history.replaceState(null, '', window.location.pathname + window.location.search + '#' + tenantSlug + '/' + item.id); } catch (_) {} }
      }
      function onHashChange() {
        let h = '';
        try { h = decodeURIComponent((window.location.hash || '').slice(1)); } catch (_) {}
        if (!h) return revert();
        const [tPart, iPart] = h.includes('/') ? h.split('/') : [tenantSlug, h];
        if (tPart !== tenantSlug) {
          if (user.tenants.indexOf(tPart) < 0) return revert();  // gate: respect auth
          const nav = NAV_BY_TENANT[tPart] || [];
          const found = findItem(nav, iPart);
          if (!found || found._section) return revert();
          if (window.OparetoTenant) window.OparetoTenant.set(tPart);
          setTenantSlug(tPart);
          setSettingsMode(false);
          setSettingsId(settingsNavFor(tPart)[0].id);
          setCurrentId(found.id);
          return;
        }
        const found = findItem(items, iPart);
        if (!found || found._section) return revert();
        setSettingsMode(false);
        setCurrentId(found.id);
      }
      window.addEventListener('hashchange', onHashChange);
      return () => window.removeEventListener('hashchange', onHashChange);
    }, [tenantSlug, items, user, item]);

    useEffect(() => {
      document.title = (TITLE_BY_TENANT[tenantSlug] || tenants[tenantSlug]?.name || 'Opareto Platform') + ' · Prototype';
    }, [tenantSlug, tenants]);

    useEffect(() => {
      function onMsg(e) {
        if (e.origin !== window.location.origin) return;
        const data = e.data || {};
        if (data.type === 'navigate') {
          const target = findItem(items, data.screen);
          if (target) { setSettingsMode(false); setCurrentId(target.id); }
        }
      }
      window.addEventListener('message', onMsg);
      return () => window.removeEventListener('message', onMsg);
    }, [items]);

    function toggleTheme() {
      if (window.OparetoTheme) window.OparetoTheme.toggle();
      setIsDark(window.OparetoTheme ? window.OparetoTheme.current() === 'dark' : !isDark);
    }

    function switchTenant(slug) {
      if (slug === tenantSlug) return;
      if (user.tenants.indexOf(slug) < 0) return;     // gate: respect auth
      if (window.OparetoTenant) window.OparetoTenant.set(slug);
      setTenantSlug(slug);
      setSettingsMode(false);
      setSettingsId(settingsNavFor(slug)[0].id);
      const nav = NAV_BY_TENANT[slug] || NAV_BY_TENANT.opareto;
      const firstReal = nav.find((i) => !i._section && !i._hidden) || nav[0];
      setCurrentId(firstReal.id);
    }

    // Chat overlay state + ⌘J keyboard handler.
    // Command palette state + ⌘K keyboard handler.
    const [chatOpen, setChatOpen] = useState(false);
    const [paletteOpen, setPaletteOpen] = useState(false);
    useEffect(() => {
      function onKey(e) {
        if (!(e.metaKey || e.ctrlKey)) return;
        const k = e.key.toLowerCase();
        if (k === 'j') { e.preventDefault(); setChatOpen((v) => !v); }
        if (k === 'k') { e.preventDefault(); setPaletteOpen((v) => !v); }
      }
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, []);

    useEffect(() => {
      const params = new URLSearchParams(window.location.search);
      if (!params.has('recording') || params.has('mobile') || recordingStarted.current) return;
      if (tenantSlug !== 'mchome') {
        if (user.tenants.indexOf('mchome') >= 0) setTenantSlug('mchome');
        return;
      }

      recordingStarted.current = true;
      setChatOpen(false);
      setPaletteOpen(false);

      function setZoom(on) {
        let style = document.getElementById('op-recording-zoom-style');
        if (!on) {
          if (style) style.remove();
          return;
        }
        if (!style) {
          style = document.createElement('style');
          style.id = 'op-recording-zoom-style';
          document.head.appendChild(style);
        }
        style.textContent = 'iframe[title="Prototype surface"]{transform:scale(1.04);transform-origin:center 46%;transition:transform .28s ease;box-shadow:0 18px 54px rgba(15,23,42,.14)}';
      }

      function highlight(needle) {
        const frame = document.querySelector('iframe[title="Prototype surface"]');
        const doc = frame && frame.contentDocument;
        if (!doc || !needle) return;
        let style = doc.getElementById('op-recording-highlight-style');
        if (!style) {
          style = doc.createElement('style');
          style.id = 'op-recording-highlight-style';
          style.textContent = '.op-recording-highlight{outline:3px solid var(--op-accent)!important;box-shadow:0 0 0 7px rgba(47,125,87,.18),0 18px 48px rgba(15,23,42,.20)!important;border-radius:10px!important;transition:box-shadow .18s ease,outline .18s ease}';
          doc.head.appendChild(style);
        }
        doc.querySelectorAll('.op-recording-highlight').forEach((el) => el.classList.remove('op-recording-highlight'));
        const target = Array.from(doc.querySelectorAll('button,a,section,article,div,td,th'))
          .find((el) => (el.innerText || '').toLowerCase().includes(String(needle).toLowerCase()) && el.offsetParent);
        if (!target) return;
        target.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' });
        target.classList.add('op-recording-highlight');
      }

      function go(id, message, needle, zoom) {
        setCurrentId(id);
        setRecordingMessage(message);
        setZoom(!!zoom);
        window.setTimeout(() => highlight(needle), 900);
      }

      const steps = [
        [800, 'daily-briefing', 'MC Home starts the day with one owner brief: what changed, what was handled, and what needs a decision.', 'Agent workload cleared', false],
        [14000, 'daily-briefing', 'Only judgment calls become interruptions. Routine updates are already filed.', 'Approve or edit Maplewood', false],
        [28000, 'field-updates', 'Underneath the brief, crews and subcontractors keep working normally: text messages and photos.', 'Raw field message', false],
        [42000, 'field-updates', 'The agent extracts project, trade, progress, blocker, confidence, and the next action.', 'Agent extraction', true],
        [58000, 'estimate-proposal', 'That field update becomes a reviewable change order with source evidence.', 'Sources', false],
        [74000, 'estimate-proposal', 'Money-impacting work stays human-approved: review, edit, then approve.', 'Approval', false],
        [90000, 'projects', 'Across 50 sites, MC Home gets schedule, progress, subs, and blockers in one place.', 'Trade progress', false],
        [106000, 'schedule-proposal', 'When a site is blocked, the agent proposes a crew move so the day keeps moving.', 'Before', false],
        [118000, 'schedule-proposal', 'The result: less data entry for crews and the office; clearer decisions for the owner.', 'After', false],
      ];
      const timers = steps.map(([delay, id, message, needle, zoom]) => window.setTimeout(() => go(id, message, needle, zoom), delay));
      return () => {
        timers.forEach(window.clearTimeout);
        setZoom(false);
      };
    }, [tenantSlug, user.tenants]);

    return (
      <div className="op-app" style={{
        display: 'flex',
        flexDirection: isMobile ? 'column' : 'row',
        height: '100vh',
        width: isMobile && forceMobile ? 'min(430px, 100vw)' : '100vw',
        margin: isMobile && forceMobile ? '0 auto' : 0,
        background: 'var(--op-bg)',
        color: 'var(--op-text)',
        overflow: 'hidden',
        borderLeft: isMobile && forceMobile && window.innerWidth > 430 ? '0.5px solid var(--op-border)' : 0,
        borderRight: isMobile && forceMobile && window.innerWidth > 430 ? '0.5px solid var(--op-border)' : 0,
      }}>
        {!isMobile && (
          <Sidebar
            tenants={tenants}
            tenantSlug={tenantSlug}
            currentId={currentId}
            onSelect={selectMain}
            onSwitchTenant={switchTenant}
            items={items}
            onOpenPalette={() => setPaletteOpen(true)}
            user={user}
            allowedTenants={user.tenants}
            settingsMode={settingsMode}
            settingsItems={settingsNav}
            settingsId={settingsId}
            onSelectSettings={setSettingsId}
            onExitSettings={exitSettings}
          />
        )}
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
          {isMobile ? (
            <MobileHeader tenant={tenants[tenantSlug]} item={item} items={items} onSelect={selectMain} />
          ) : (
            <Header tenant={tenants[tenantSlug]} item={item} isDark={isDark} onToggleTheme={toggleTheme}
              onOpenPalette={() => setPaletteOpen(true)}
              parentCrumb={settingsMode ? 'Settings' : null}
              crumbTitle={settingsMode ? settingsItem.title : (item ? item.title : 'Untitled')} />
          )}
          <MainPane
            src={activeScreen}
            tenant={tenants[tenantSlug]}
            item={item}
            chatOpen={chatOpen}
            onToggleChat={() => setChatOpen((v) => !v)}
            mobile={isMobile}
            recording={isRecording}
          />
        </div>
        {paletteOpen && (
          <CommandPalette
            tenant={tenants[tenantSlug]}
            tenants={tenants}
            items={items}
            onSelectNav={(id) => { setSettingsMode(false); setCurrentId(id); }}
            onSwitchTenant={switchTenant}
            onOpenChat={() => setChatOpen(true)}
            onClose={() => setPaletteOpen(false)}
          />
        )}
        {recordingMessage && (
          <div style={{
            position: 'fixed',
            left: '50%',
            bottom: 26,
            transform: 'translateX(-50%)',
            zIndex: 1000,
            maxWidth: 'min(860px, calc(100vw - 64px))',
            padding: '15px 20px',
            borderRadius: 16,
            background: 'rgba(16,24,40,.94)',
            color: '#fff',
            fontSize: 18,
            lineHeight: 1.35,
            fontWeight: 600,
            textAlign: 'center',
            boxShadow: '0 18px 54px rgba(15,23,42,.28)',
            pointerEvents: 'none',
          }}>{recordingMessage}</div>
        )}
      </div>
    );
  }

  function mount() {
    ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  }
  if (window.OparetoMocks && window.OparetoMocks.ready) {
    window.OparetoMocks.ready.finally(mount);
  } else {
    mount();
  }
})();
