---
phase: 03-apple-health-integration
plan: 03
type: execute
wave: 2
depends_on:
  - 03-01
files_modified:
  - client/src/components/meatspace/constants.js
  - client/src/components/meatspace/tabs/HealthTab.jsx
  - client/src/components/meatspace/StepsCard.jsx
  - client/src/components/meatspace/HeartRateCard.jsx
  - client/src/components/meatspace/SleepCard.jsx
  - client/src/components/meatspace/HrvCard.jsx
  - client/src/components/meatspace/AlcoholHrvCorrelation.jsx
  - client/src/components/meatspace/ActivityBloodCorrelation.jsx
  - client/src/pages/MeatSpace.jsx
autonomous: false
requirements:
  - HLT-05
  - HLT-06

must_haves:
  truths:
    - "MeatSpace has a Health tab showing steps, heart rate, sleep, and HRV cards with trend charts"
    - "Each health card shows a hero number (current/average) and line chart trend for the selected range"
    - "Range selector buttons (7d/30d/90d/1y) filter all health cards consistently"
    - "Sleep card shows total hours as hero and stacked horizontal bar for stage proportions"
    - "Alcohol vs HRV dual-axis chart shows correlation with auto-generated text summary"
    - "Activity vs blood work chart shows 30-day rolling average steps alongside blood markers"
    - "Correlations show minimum data threshold message when fewer than 14 days of data exist"
  artifacts:
    - path: "client/src/components/meatspace/tabs/HealthTab.jsx"
      provides: "Health tab container with range selector and card grid"
      contains: "HealthTab"
    - path: "client/src/components/meatspace/StepsCard.jsx"
      provides: "Steps trend card with hero number and LineChart"
      contains: "StepsCard"
    - path: "client/src/components/meatspace/HeartRateCard.jsx"
      provides: "Heart rate card with avg/min/max"
      contains: "HeartRateCard"
    - path: "client/src/components/meatspace/SleepCard.jsx"
      provides: "Sleep card with stacked horizontal bar"
      contains: "SleepCard"
    - path: "client/src/components/meatspace/HrvCard.jsx"
      provides: "HRV trend card"
      contains: "HrvCard"
    - path: "client/src/components/meatspace/AlcoholHrvCorrelation.jsx"
      provides: "Dual-axis correlation chart"
      contains: "AlcoholHrvCorrelation"
    - path: "client/src/components/meatspace/ActivityBloodCorrelation.jsx"
      provides: "Activity vs blood work correlation"
      contains: "ActivityBloodCorrelation"
  key_links:
    - from: "client/src/components/meatspace/tabs/HealthTab.jsx"
      to: "/api/health/metrics/:metricName/daily"
      via: "api.getAppleHealthMetrics()"
      pattern: "getAppleHealthMetrics"
    - from: "client/src/components/meatspace/tabs/HealthTab.jsx"
      to: "/api/health/correlation"
      via: "api.getAppleHealthCorrelation()"
      pattern: "getAppleHealthCorrelation"
    - from: "client/src/pages/MeatSpace.jsx"
      to: "client/src/components/meatspace/tabs/HealthTab.jsx"
      via: "case 'health' in renderTabContent"
      pattern: "HealthTab"
---

<objective>
Build the MeatSpace Health tab with four metric cards (steps, heart rate, sleep, HRV), time range selector, and two correlation charts (alcohol vs HRV, activity vs blood work) with auto-generated text summaries.

Purpose: Makes ingested Apple Health data visible and actionable on the MeatSpace dashboard.
Output: New Health tab in MeatSpace with trend charts and correlation insights.
</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/03-apple-health-integration/03-RESEARCH.md
@.planning/phases/03-apple-health-integration/03-01-SUMMARY.md

@client/src/components/meatspace/constants.js — TABS array, design tokens
@client/src/components/meatspace/AlcoholChart.jsx — Recharts patterns for dark theme
@client/src/components/meatspace/BloodTestCard.jsx — Card component patterns
@client/src/pages/MeatSpace.jsx — Tab routing, renderTabContent switch
@client/src/services/api.js — API functions from Plan 03-01

<interfaces>
<!-- From Plan 03-01 (will exist by execution time) -->

From client/src/services/api.js:
```javascript
export const getAppleHealthMetrics = (metricName, from, to) => ...;
export const getAppleHealthSummary = (metricName, from, to) => ...;
export const getAppleHealthRange = () => ...;
export const getAppleHealthCorrelation = (from, to) => ...;
```

API response shapes:
```javascript
// GET /api/health/metrics/:metricName/daily
[{ date: "2024-01-15", value: 8432 }, ...]

// GET /api/health/metrics/:metricName
{ metricName, count, latest, average, from, to, dataPoints: [...] }

// GET /api/health/correlation
{ dailyData: [{ date, hrv, alcoholGrams, steps }], bloodTests: [...] }
```

From constants.js:
```javascript
export const TABS = [
  { id: 'overview', ... },
  { id: 'alcohol', ... },
  { id: 'blood', ... },
  { id: 'genome', ... },
  { id: 'lifestyle', ... }
];
```

Recharts imports (already installed v3.7.0):
```javascript
import { LineChart, Line, BarChart, Bar, ComposedChart, XAxis, YAxis,
         CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
```

Design tokens:
```
bg-port-card (#1a1a1a), border-port-border (#2a2a2a), text-port-accent (#3b82f6)
CartesianGrid stroke="#2a2a2a", tick fill="#9ca3af" fontSize 11
Tooltip: background '#1a1a1a', border '1px solid #2a2a2a', color '#fff'
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create four health metric cards and HealthTab container</name>
  <files>
    client/src/components/meatspace/StepsCard.jsx
    client/src/components/meatspace/HeartRateCard.jsx
    client/src/components/meatspace/SleepCard.jsx
    client/src/components/meatspace/HrvCard.jsx
    client/src/components/meatspace/tabs/HealthTab.jsx
  </files>
  <action>
**All four cards follow the same layout pattern (per locked decision):**
- Card wrapper: `bg-port-card border border-port-border rounded-xl p-6`
- Hero number at top: large text showing current/average value with unit label
- Line chart trend below using recharts `ResponsiveContainer` + `LineChart`
- Charts use dark theme tokens: CartesianGrid `stroke="#2a2a2a"`, tick `fill="#9ca3af" fontSize={11}`, Tooltip with dark background
- Each card receives `data` (array of `{ date, value }`) and `loading` as props

**1. StepsCard.jsx:**
- Hero: Daily average steps for the range, formatted with `toLocaleString()` (e.g., "8,432")
- Unit label: "avg daily steps"
- Chart: LineChart with `dataKey="value"`, stroke `#3b82f6` (port-accent), dot={false}, strokeWidth 2
- Empty state: "No step data available" in gray text

**2. HeartRateCard.jsx:**
- Hero: Average resting heart rate (use average of `value` field), with "bpm" unit
- Chart: LineChart with `dataKey="value"`, stroke `#ef4444` (red/port-error for heart), dot={false}
- Empty state: "No heart rate data available"

**3. SleepCard.jsx:**
- Hero: Average total sleep hours (e.g., "7.2 hrs")
- Below hero: Stacked horizontal BarChart showing stage proportions for the most recent days. Use `BarChart layout="vertical"` with stacked Bars:
  - deep: `#3b82f6` (blue)
  - rem: `#8b5cf6` (purple)
  - core: `#06b6d4` (cyan)
  - awake: `#6b7280` (gray)
- This card accepts `data` shaped as `[{ date, totalSleep, deep, rem, core, awake }]`
- Only show last 7 days in the stacked bar (even if range is larger) to keep it readable
- Empty state: "No sleep data available"

**4. HrvCard.jsx:**
- Hero: Average HRV in ms (e.g., "42 ms")
- Chart: LineChart with `dataKey="value"`, stroke `#22c55e` (green/port-success), dot={false}
- Empty state: "No HRV data available"

**5. HealthTab.jsx:**
- State: `range` (default `'7d'`), `stepsData`, `hrData`, `sleepData`, `hrvData`, `correlationData`, `loading`
- Range selector: Row of buttons `7d`, `30d`, `90d`, `1y` (per locked decision). Active button highlighted with `bg-port-accent/10 text-port-accent`. Calculate `from` date string from range.
- On mount and range change: Fetch all four metrics in parallel using `Promise.all([api.getAppleHealthMetrics('step_count', from, to), api.getAppleHealthMetrics('heart_rate', from, to), ...])`. Also fetch correlation data: `api.getAppleHealthCorrelation(from, to)`.
- Layout: 2-column grid on lg, 1-column on mobile (`grid grid-cols-1 lg:grid-cols-2 gap-6`).
  - Row 1: StepsCard, HeartRateCard
  - Row 2: SleepCard, HrvCard
  - Row 3 (full width): Correlations section (rendered by correlation components from Task 2)
- Pass fetched data and loading state to each card.
- Row 3 correlations section: Render a placeholder `<div>` with a `{/* Correlation charts — wired in Task 2 */}` comment. Do NOT import AlcoholHrvCorrelation or ActivityBloodCorrelation — those files don't exist yet and will break the build. Task 2 will add the imports and replace this placeholder.
  </action>
  <verify>
    <automated>cd client && npm run build</automated>
  </verify>
  <done>
    - StepsCard shows hero average + LineChart trend
    - HeartRateCard shows hero avg BPM + LineChart trend
    - SleepCard shows hero avg hours + stacked horizontal BarChart for stages
    - HrvCard shows hero avg ms + LineChart trend
    - HealthTab fetches all metrics in parallel on range change
    - Range selector buttons (7d/30d/90d/1y) control all cards consistently
    - All cards have empty states for when no data exists
    - Dark theme tokens used consistently across all charts
    - Row 3 has a placeholder div for correlations (no correlation imports yet)
    - Client builds cleanly with all new components
  </done>
</task>

<task type="auto">
  <name>Task 2: Create correlation charts and wire Health tab into MeatSpace</name>
  <files>
    client/src/components/meatspace/AlcoholHrvCorrelation.jsx
    client/src/components/meatspace/ActivityBloodCorrelation.jsx
    client/src/components/meatspace/tabs/HealthTab.jsx
    client/src/components/meatspace/constants.js
    client/src/pages/MeatSpace.jsx
  </files>
  <action>
**1. AlcoholHrvCorrelation.jsx:**

Dual-axis overlay chart (per locked decision):
- Receives `data` prop: `{ dailyData: [{ date, hrv, alcoholGrams }] }` and `range`
- Minimum data threshold check: If `dailyData.length < 14`, show message "Need 14+ days of data for correlations -- {dailyData.length} days so far." in a subtle card with `text-gray-400`.
- Chart: `ComposedChart` with two `YAxis`:
  - Left YAxis (`yAxisId="hrv"`): HRV in ms, `orientation="left"`
  - Right YAxis (`yAxisId="alcohol"`): Alcohol in grams, `orientation="right"`
  - `Line` for HRV: `yAxisId="hrv"`, stroke `#22c55e` (green), dot={false}
  - `Bar` for alcohol: `yAxisId="alcohol"`, fill `#f59e0b` (amber/port-warning), opacity 0.7
- Card wrapper with title: "Alcohol vs HRV" with HeartPulse + Beer icons
- **Auto-generated text summary below chart** (per locked decision — computed, NOT LLM):
  ```
  Calculate:
  - drinkingDays = days where alcoholGrams > 0
  - soberDays = days where alcoholGrams === 0
  - avgHrvDrinking = average HRV on drinking days
  - avgHrvSober = average HRV on sober days
  - percentDiff = ((avgHrvSober - avgHrvDrinking) / avgHrvSober * 100).toFixed(0)

  Display: "HRV averages {avgHrvDrinking}ms on drinking days vs {avgHrvSober}ms on sober days ({percentDiff}% difference)"
  If no drinking days: "No alcohol data recorded in this period"
  If no sober days: "No sober days recorded in this period"
  ```
- Dark theme styling matching other charts

**2. ActivityBloodCorrelation.jsx:**

Activity vs blood work chart (per locked decision):
- Receives `data` prop: `{ dailyData: [{ date, steps }], bloodTests: [{ date, results: {...} }] }` and `range`
- Minimum data threshold: Same 14-day check as alcohol correlation.
- Calculate 30-day rolling average steps leading up to each blood test date:
  ```javascript
  For each blood test:
    - Find the 30 days of dailyData before the blood test date
    - Calculate average steps for those 30 days
    - Pair with key blood markers from the test (use markers from the test results if available)
  ```
- Chart: `ComposedChart` showing:
  - Bar for rolling average steps (`yAxisId="steps"`)
  - Line/dots for key blood markers at test dates (`yAxisId="markers"`) — choose 2-3 most common markers (cholesterol, glucose, or whatever is in the blood test data)
  - If no blood tests in range, show message: "No blood tests in this period to correlate with activity"
- **Auto-generated text summary:** "Average {avgSteps} steps/day in the 30 days before your {testDate} blood test"
- Card wrapper with title: "Activity vs Blood Work"
- Wrap in `ResponsiveContainer`

**3. Wire correlation components into HealthTab.jsx:**

Now that both correlation component files exist, update `HealthTab.jsx`:
- Add imports at top: `import AlcoholHrvCorrelation from '../AlcoholHrvCorrelation'` and `import ActivityBloodCorrelation from '../ActivityBloodCorrelation'`
- Replace the placeholder `<div>` (with the `{/* Correlation charts — wired in Task 2 */}` comment) with the actual correlation components:
  ```jsx
  {correlationData && (
    <>
      <AlcoholHrvCorrelation data={correlationData} range={range} />
      <ActivityBloodCorrelation data={correlationData} range={range} />
    </>
  )}
  ```

**4. Add Health tab to constants.js:**

In the TABS array, add (alphabetically between 'genome' and 'lifestyle'):
```javascript
{ id: 'health', label: 'Health', icon: HeartPulse }
```

Note: HeartPulse is already imported in constants.js (used for Blood & Body). But 'health' uses it too. Since Blood & Body already uses HeartPulse, use a different icon for Health. Check available lucide-react icons: use `Activity` (already imported) for Health since it represents activity/vitals. Actually `Activity` is used for Overview. Use `Heart` from lucide-react instead — import it.

Wait, looking at constants.js: `Activity` is used for Overview, `HeartPulse` for Blood & Body. For the Health tab, import and use `Stethoscope` from lucide-react — it's distinct and represents health monitoring.

Update the import line to include `Stethoscope`.

**5. Wire Health tab into MeatSpace.jsx:**

Add import:
```javascript
import HealthTab from '../components/meatspace/tabs/HealthTab';
```

Add case to `renderTabContent` switch (alphabetically between 'genome' and 'lifestyle'):
```javascript
case 'health':
  return <HealthTab />;
```
  </action>
  <verify>
    <automated>cd client && npm run build && cd ../server && npm test</automated>
  </verify>
  <done>
    - AlcoholHrvCorrelation shows dual-axis chart with auto-generated text summary
    - ActivityBloodCorrelation shows activity vs blood markers with text summary
    - Both correlations show minimum data threshold message when < 14 days
    - Health tab appears in MeatSpace tab bar between Genome and Lifestyle
    - Clicking Health tab renders HealthTab component via URL routing (/meatspace/health)
    - All correlation text summaries are computed from data, not LLM-generated
    - Client builds cleanly with all new components
  </done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Verify Health tab and correlation charts</name>
  <files>none (verification only)</files>
  <action>
Human verification checkpoint. Verify:
1. Health tab appears in MeatSpace at /meatspace/health
2. Four metric cards render correctly with empty states or data
3. Range selector buttons work
4. Correlation charts show threshold messages or data
5. URL is deep-linkable
  </action>
  <verify>
    <automated>curl -s http://localhost:5554/api/health/range | node -e "process.stdin.on('data',d=>{console.log('PASS: range endpoint responding')})"</automated>
  </verify>
  <done>Health tab verified visually with cards, range selector, and correlations</done>
  <what-built>MeatSpace Health tab with four metric cards, range selector, and two correlation charts</what-built>
  <how-to-verify>
    1. Navigate to http://localhost:5555/meatspace/health
    2. Verify the Health tab appears in the MeatSpace tab bar (between Genome and Lifestyle)
    3. Verify range selector buttons (7d/30d/90d/1y) are visible and clickable
    4. If health data was ingested via curl in Plan 03-02 checkpoint, verify cards show data
    5. If no data: verify empty states display correctly ("No step data available" etc.)
    6. Verify card styling matches existing MeatSpace cards (dark theme, rounded corners)
    7. Verify correlation section shows threshold message if < 14 days of data
    8. Verify the URL is deep-linkable: refreshing /meatspace/health returns to the Health tab
  </how-to-verify>
  <resume-signal>Type "approved" or describe issues</resume-signal>
</task>

</tasks>

<verification>
1. Health tab appears in MeatSpace at /meatspace/health
2. Four metric cards render with hero numbers and trend charts
3. Range selector controls all cards consistently
4. Correlation charts show dual-axis data or threshold messages
5. All existing tests pass: `cd server && npm test`
6. Client builds cleanly: `cd client && npm run build`
</verification>

<success_criteria>
- MeatSpace Health tab shows steps, heart rate, sleep, and HRV cards
- Each card has hero number + line chart (sleep has stacked bar for stages)
- Range selector (7d/30d/90d/1y) filters all cards
- Alcohol vs HRV correlation shows dual-axis chart with computed text summary
- Activity vs blood work correlation shows 30-day rolling average with markers
- Correlations show "Need 14+ days" message when insufficient data
- Health tab is deep-linkable at /meatspace/health
</success_criteria>

<output>
After completion, create `.planning/phases/03-apple-health-integration/03-03-SUMMARY.md`
</output>
