---
phase: 04-cross-domain-insights-engine
plan: 02
type: execute
wave: 2
depends_on: ["04-01"]
files_modified:
  - client/src/pages/Insights.jsx
  - client/src/components/insights/GenomeHealthTab.jsx
  - client/src/components/insights/TasteIdentityTab.jsx
  - client/src/components/insights/CrossDomainTab.jsx
  - client/src/components/insights/InsightCard.jsx
  - client/src/components/insights/ConfidenceBadge.jsx
  - client/src/components/insights/ProvenancePanel.jsx
  - client/src/components/insights/EmptyState.jsx
  - client/src/App.jsx
  - client/src/components/Layout.jsx
  - client/package.json
autonomous: false
requirements:
  - INS-03
  - INS-01
  - INS-02
  - INS-04

must_haves:
  truths:
    - "Navigating to /insights shows the overview landing page with domain summary cards"
    - "Clicking a domain summary card navigates to the corresponding deep-linked tab"
    - "/insights/genome-health displays genome markers grouped by clinical category with matched blood values and confidence badges"
    - "/insights/taste-identity displays LLM-generated theme cards with evidence lists and refresh button"
    - "/insights/cross-domain displays narrative text with refresh button and diff view showing changes"
    - "Each tab URL is deep-linkable — refreshing the page stays on the correct tab"
    - "Empty states show helpful messages with navigation links when data is missing"
    - "Insights nav entry appears in the sidebar in correct alphabetical position"
    - "Confidence badges are color-coded: green (strong), yellow (moderate), red (weak/significant)"
  artifacts:
    - path: "client/src/pages/Insights.jsx"
      provides: "Tabbed insights page with 4 sub-views"
      min_lines: 80
    - path: "client/src/components/insights/GenomeHealthTab.jsx"
      provides: "Genome-health correlation display grouped by category"
      min_lines: 80
    - path: "client/src/components/insights/TasteIdentityTab.jsx"
      provides: "Taste-identity theme cards with evidence lists"
      min_lines: 60
    - path: "client/src/components/insights/CrossDomainTab.jsx"
      provides: "Cross-domain narrative with diff display"
      min_lines: 60
    - path: "client/src/components/insights/InsightCard.jsx"
      provides: "Shared card component for insight display"
      min_lines: 20
    - path: "client/src/components/insights/ConfidenceBadge.jsx"
      provides: "Color-coded confidence badge component"
      min_lines: 15
  key_links:
    - from: "client/src/pages/Insights.jsx"
      to: "/api/insights/*"
      via: "api.js client functions"
      pattern: "getGenomeHealthCorrelations|getInsightThemes|getInsightNarrative"
    - from: "client/src/App.jsx"
      to: "client/src/pages/Insights.jsx"
      via: "Route path='insights/:tab'"
      pattern: "insights.*:tab"
    - from: "client/src/components/Layout.jsx"
      to: "/insights/overview"
      via: "nav entry with Lightbulb icon"
      pattern: "insights.*Lightbulb"
---

<objective>
Build the Insights dashboard UI: a tabbed page at `/insights/:tab` with four views — Overview (landing with domain summary cards), Genome-Health (category-grouped correlation cards), Taste-Identity (LLM theme cards), and Cross-Domain (narrative with diff display). Wire into App.jsx routes and Layout.jsx navigation.

Purpose: Surface cross-domain insights in an accessible, deep-linkable UI. Users see genome-health correlations with confidence levels, taste-identity themes with evidence, and refreshable cross-domain narratives — all following established PortOS design patterns.

Output: Insights page with 4 tab views, 4 shared components, route configuration, nav entry, react-diff-viewer-continued dependency.
</objective>

<execution_context>
@/Users/antic/.claude/get-shit-done/workflows/execute-plan.md
@/Users/antic/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-cross-domain-insights-engine/04-01-SUMMARY.md

<interfaces>
<!-- API contract from Plan 01 -->

From client/src/services/api.js (added in Plan 01):
```javascript
export const getGenomeHealthCorrelations = () => request('/insights/genome-health');
// Returns: { available: true, categories: [...], totalMarkers, matchedMarkers, sources }
//   OR: { available: false, reason: 'no_genome' }
// Category shape: { category, label, icon, color, markers: [...], notFoundCount }
// Marker shape: { rsid, gene, name, status, description, implications, confidence: { level, color, label }, matchedBloodValues: [{ analyte, value, date, unit }], references }

export const getInsightThemes = () => request('/insights/themes');
// Returns: { themes: [...], generatedAt, model }
//   OR: { available: false, reason: 'not_generated' | 'no_taste_data' }
// Theme shape: { title, narrative, evidence: [{ preference, domain, connection }], strength }

export const refreshInsightThemes = (providerId, model) => request('/insights/themes/refresh', { method: 'POST', body: JSON.stringify({ providerId, model }) });
// Returns: { themes: [...], generatedAt }

export const getInsightNarrative = () => request('/insights/narrative');
// Returns: { text, previousText, generatedAt, previousGeneratedAt, model }
//   OR: { available: false, reason: 'not_generated' }

export const refreshInsightNarrative = (providerId, model) => request('/insights/narrative/refresh', { method: 'POST', body: JSON.stringify({ providerId, model }) });
// Returns: { text, previousText, generatedAt, previousGeneratedAt, model }
```

From existing App.jsx route pattern (MeatSpace example):
```jsx
<Route path="meatspace" element={<Navigate to="/meatspace/overview" replace />} />
<Route path="meatspace/:tab" element={<MeatSpace />} />
```

From existing Layout.jsx nav structure:
```javascript
// Current order after separator: AI Config, Apps, Brain, Chief of Staff, Dev Tools,
// Digital Twin, Instances, MeatSpace, Security, Shell, Social Agents, Uploads
// Insights goes after Instances, before MeatSpace (alphabetical)
```

Tailwind design tokens:
```
port-bg: #0f0f0f       port-card: #1a1a1a
port-border: #2a2a2a   port-accent: #3b82f6
port-success: #22c55e  port-warning: #f59e0b
port-error: #ef4444
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Install react-diff-viewer-continued, create shared insight components, and build all tab views</name>
  <files>
    client/package.json
    client/src/components/insights/ConfidenceBadge.jsx
    client/src/components/insights/ProvenancePanel.jsx
    client/src/components/insights/EmptyState.jsx
    client/src/components/insights/InsightCard.jsx
    client/src/components/insights/GenomeHealthTab.jsx
    client/src/components/insights/TasteIdentityTab.jsx
    client/src/components/insights/CrossDomainTab.jsx
  </files>
  <action>
**0. Install dependency:**
```bash
cd client && npm install react-diff-viewer-continued
```
If `react-diff-viewer-continued` is not available on npm, fall back to hand-rolling a simple word-level diff using the `diff` npm package (`npm install diff`) — for 2-3 paragraph texts, a custom `<NarrativeDiff>` component using `diffWords()` from the `diff` package is sufficient.

**1. `ConfidenceBadge.jsx`** — small presentational component:
```jsx
const BADGE_STYLES = {
  strong:      'bg-port-success/20 text-port-success border-port-success/30',
  moderate:    'bg-port-warning/20 text-port-warning border-port-warning/30',
  weak:        'bg-port-error/20 text-port-error border-port-error/30',
  significant: 'bg-port-error/30 text-port-error border-port-error/50',
  unknown:     'bg-gray-800 text-gray-400 border-gray-700'
};
```
- Props: `{ level, label, sources }` — renders styled `<span>` with `title={sources?.join(', ')}`
- Consistent across all insight types (genome-health and taste-identity)

**2. `ProvenancePanel.jsx`** — collapsible provenance section:
- Props: `{ references, expanded, onToggle }` — or use internal useState for expand/collapse
- Default collapsed. Click to expand.
- Renders list of references with study name, publication year, database source (ClinVar, PharmGKB, etc.)
- If no references, renders nothing (return null)
- Use ChevronDown/ChevronRight from lucide-react for expand indicator

**3. `EmptyState.jsx`** — gentle empty state component:
- Props: `{ message, linkTo, linkLabel }` (all optional except message)
- Renders centered text in muted color with optional navigation Link
- Examples: "No genome data uploaded yet" → link to `/meatspace/genome`; "No taste profile completed yet" → link to `/digital-twin/taste`
- Use Lucide `Info` icon

**4. `InsightCard.jsx`** — shared card shell:
- Props: `{ title, subtitle, badge, children, sources, className }`
- Card layout: `bg-port-card border border-port-border rounded-lg p-4`
- Header row: title + badge (ConfidenceBadge). Subtitle below title.
- `sources` rendered as inline tags: small pill-shaped spans showing "23andMe", "Apple Health", etc.
- `children` for card body content

**5. `GenomeHealthTab.jsx`** (INS-01, INS-03):
- Fetch `getGenomeHealthCorrelations()` on mount (useEffect)
- Loading state: skeleton cards (pulse animation placeholders)
- If `!data.available` → render `<EmptyState message="No genome data uploaded yet" linkTo="/meatspace/genome" linkLabel="Upload Genome" />`
- If available: render categories as collapsible sections
- Each category section has:
  - Header: icon (from Lucide — map string icon names to Lucide components), category label, marker count, expand/collapse toggle
  - When expanded: grid of InsightCards for each marker
  - Each marker card shows: marker name as title, gene as subtitle, ConfidenceBadge with status-mapped level, description text, implications text (for the marker's status), matched blood values if any (analyte: value with date), collapsible ProvenancePanel for references
  - If `matchedBloodValues` is empty, show "No blood test on record" muted text with link to `/meatspace/blood`
  - Category header shows `notFoundCount` as "(N not in dataset)" note if > 0
- Summary stats at top: total markers analyzed, matched with blood data count

**6. `TasteIdentityTab.jsx`** (INS-02, INS-03):
- Fetch `getInsightThemes()` on mount
- If `!data?.available && data?.reason === 'not_generated'` → show EmptyState with "Generate" action description and explain what it does
- If `data?.reason === 'no_taste_data'` → show EmptyState linking to `/digital-twin/taste`
- If themes exist: show `generatedAt` timestamp, "Refresh" button (calls `refreshInsightThemes()`)
- During refresh: button shows loading spinner, disable button
- Staleness indicator: compare `generatedAt` against current time, show "Generated X ago" relative time
- Theme cards layout: vertical stack or 2-column grid on lg screens
- Each theme card (InsightCard): title = theme title, badge = ConfidenceBadge with strength mapped to level (strong→strong, moderate→moderate, tentative→weak), body = narrative paragraph
- Below narrative: collapsible evidence list (similar to ProvenancePanel pattern). Each evidence item: `{preference}` from `{domain}` — `{connection}`
- Tone: analytical, third-person as per CONTEXT.md decisions

**7. `CrossDomainTab.jsx`** (INS-04, INS-03):
- Fetch `getInsightNarrative()` on mount
- If not generated → show EmptyState: "No cross-domain narrative generated yet. Click Refresh to analyze patterns across all your data."
- If generated: show narrative text in a styled container (bg-port-card, prose-like typography)
- Show `generatedAt` timestamp and model used
- "Refresh" button: calls `refreshInsightNarrative()`, shows loading state
- After refresh: if `previousText` exists and differs from `text`, show diff toggle
  - "Show changes" button toggles diff view on/off
  - Diff view uses `react-diff-viewer-continued` (or fallback diff component):
    - `oldValue={previousText}`, `newValue={text}`, `splitView={false}`, `useDarkTheme`, `hideLineNumbers`
  - If `previousText === text` (trimmed), show "No significant changes detected" instead of diff
- Show `previousGeneratedAt` as "Previous analysis: {date}" when diff is shown
  </action>
  <verify>
    <automated>cd /Users/antic/github.com/atomantic/PortOS/client && ls src/components/insights/ConfidenceBadge.jsx src/components/insights/ProvenancePanel.jsx src/components/insights/EmptyState.jsx src/components/insights/InsightCard.jsx src/components/insights/GenomeHealthTab.jsx src/components/insights/TasteIdentityTab.jsx src/components/insights/CrossDomainTab.jsx && echo "All 7 component files exist"</automated>
  </verify>
  <done>7 insight component files created. ConfidenceBadge renders color-coded badges. GenomeHealthTab groups markers by clinical category with blood value matching. TasteIdentityTab shows theme cards with evidence lists and refresh. CrossDomainTab shows narrative with diff view. EmptyState handles missing data with navigation links.</done>
</task>

<task type="auto">
  <name>Task 2: Create Insights page, wire App.jsx routes and Layout.jsx navigation</name>
  <files>
    client/src/pages/Insights.jsx
    client/src/App.jsx
    client/src/components/Layout.jsx
  </files>
  <action>
**1. Create `client/src/pages/Insights.jsx`:**
- Import `useParams`, `useNavigate` from react-router-dom
- Import tab components: GenomeHealthTab, TasteIdentityTab, CrossDomainTab
- Import API functions: `getGenomeHealthCorrelations`, `getInsightThemes`, `getInsightNarrative`
- Import Lucide icons: `Dna`, `Palette`, `Link2`, `Lightbulb`, `ArrowRight`

Tab configuration:
```javascript
const TABS = [
  { id: 'overview', label: 'Overview', icon: Lightbulb },
  { id: 'genome-health', label: 'Genome-Health', icon: Dna },
  { id: 'taste-identity', label: 'Taste & Identity', icon: Palette },
  { id: 'cross-domain', label: 'Cross-Domain Patterns', icon: Link2 }
];
```

Page structure:
- Read `tab` from `useParams()`. Default to 'overview' if not in TABS.
- Tab bar at top: horizontal list of tab buttons, active tab highlighted with `border-b-2 border-port-accent text-white`, inactive tabs `text-gray-400 hover:text-white`. Each tab is a button that calls `navigate(\`/insights/${tab.id}\`)`.
- Below tab bar: render the appropriate component based on active tab ID.

**Overview tab** (landing view, rendered inline in Insights.jsx):
- Fetch summary data from all three endpoints on mount (parallel: `Promise.allSettled`)
- 3 domain summary cards in a responsive grid (1 col mobile, 3 cols lg):
  - **Genome-Health**: key stat = `{matchedMarkers} markers analyzed`, top insight = highest-confidence finding or "Upload genome to get started", confidence indicator = ConfidenceBadge of top marker
  - **Taste & Identity**: key stat = `{themes.length} themes identified` or "Not yet generated", top insight = first theme title or prompt to complete taste profile
  - **Cross-Domain Patterns**: key stat = "Last analyzed {relativeTime}" or "Not yet generated", top insight = first sentence of narrative text
- Each summary card: bg-port-card, border, rounded-lg, p-6, clickable → navigates to the detail tab
- Source tags on each card: inline pills showing data sources
- Show loading skeletons while fetching

**2. Update `client/src/App.jsx`:**
- Import `Insights` from `./pages/Insights`
- Add routes (follow existing MeatSpace pattern):
```jsx
<Route path="insights" element={<Navigate to="/insights/overview" replace />} />
<Route path="insights/:tab" element={<Insights />} />
```
Place alphabetically among existing routes (after `instances`, before `jira`).

**3. Update `client/src/components/Layout.jsx`:**
- Import `Lightbulb` icon from lucide-react (add to existing import destructure)
- Add Insights nav entry after the `Instances` entry and before the `MeatSpace` entry (alphabetical order):
```javascript
{ to: '/insights/overview', label: 'Insights', icon: Lightbulb, single: true },
```
This keeps it as a single nav item (not expandable section) that links to the overview landing. Users navigate sub-tabs from within the page.
  </action>
  <verify>
    <automated>cd /Users/antic/github.com/atomantic/PortOS/client && npx vite build 2>&1 | tail -5</automated>
  </verify>
  <done>Insights.jsx page renders 4 tabs at /insights/:tab with deep-linked routes. Overview shows domain summary cards. App.jsx has route entries. Layout.jsx has Insights nav entry between Instances and MeatSpace. Client builds without errors.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Verify Insights dashboard UI</name>
  <files>client/src/pages/Insights.jsx</files>
  <action>
Human verification of the complete Insights dashboard. No automated changes — verify the UI built in Tasks 1-2:

1. Navigate to http://localhost:5555/insights — should redirect to /insights/overview
2. Overview tab: 3 domain summary cards should render (may show empty states if no data uploaded)
3. Click "Genome-Health" tab or summary card — URL changes to /insights/genome-health
4. If genome data exists: markers grouped by clinical category with confidence badges, collapsible provenance panels, matched blood values
5. If no genome data: empty state with link to /meatspace/genome
6. Click "Taste & Identity" tab — URL changes to /insights/taste-identity
7. If taste profile completed: click Refresh to generate themes, verify theme cards appear with evidence lists
8. Click "Cross-Domain Patterns" tab — URL changes to /insights/cross-domain
9. Click Refresh to generate narrative, verify text appears with timestamp
10. Click Refresh again — verify diff view shows changes between old and new narrative
11. Refresh browser on any tab URL — should stay on correct tab (deep-linking works)
12. Check sidebar: "Insights" nav item appears between "Instances" and "MeatSpace"
13. Verify confidence badges: green (strong evidence), yellow (moderate), red (elevated/significant risk)
  </action>
  <verify>
    <automated>curl -s http://localhost:5555/insights/overview | grep -q "html" && echo "Page serves" || echo "Page not found"</automated>
  </verify>
  <done>All 4 insight tabs render correctly. Deep-linked URLs work. Empty states guide to data import. Confidence badges use correct color coding. Nav entry is alphabetically positioned. Diff view works on narrative refresh.</done>
</task>

</tasks>

<verification>
1. `npm run build` in client succeeds — no import errors, no missing dependencies
2. All 4 tab routes work: /insights/overview, /insights/genome-health, /insights/taste-identity, /insights/cross-domain
3. Each route is deep-linkable (direct URL access works)
4. Empty states render correctly when data is missing
5. Genome-health markers are grouped by clinical category with MARKER_CATEGORIES metadata
6. Confidence badges are consistent across genome-health and taste-identity tabs
7. Theme analysis generates and caches via LLM
8. Narrative refresh preserves previous text and shows diff view
9. Nav entry "Insights" appears in correct alphabetical position in sidebar
</verification>

<success_criteria>
- /insights/:tab renders the correct sub-view for all 4 tab values
- Overview shows 3 clickable domain summary cards
- Genome-Health groups 117 markers by clinical category with collapsible sections
- Each marker card shows confidence badge, description, implications, matched blood values
- Taste-Identity shows LLM theme cards with title, narrative, evidence list, strength badge
- Cross-Domain shows refreshable narrative with before/after diff view
- Empty states guide users to relevant data import pages
- Sidebar nav entry is alphabetically correct
- All URLs are deep-linkable (no modal-only views)
- No window.alert or window.confirm used (CLAUDE.md mandate)
</success_criteria>

<output>
After completion, create `.planning/phases/04-cross-domain-insights-engine/04-02-SUMMARY.md`
</output>
