---
phase: 27-component-library
plan: 02
type: execute
wave: 2
depends_on:
  - 27-01  # Component CSS must exist — JS methods reference CSS class names
files_modified:
  - paperforge/plugin/main.js
autonomous: true
requirements:
  - COMP-01  # Pure CSS/DOM dashboard visualizations
  - COMP-03  # Loading states, CSS transitions, responsive breakpoints

must_haves:
  truths:
    - "PaperForgeStatusView has a _renderLifecycleStepper(lifecycle, currentStage) method producing a div.paperforge-lifecycle-stepper with 6 step divs, each with .step-indicator and .step-label, with appropriate .completed/.current/.pending classes applied"
    - "PaperForgeStatusView has a _renderHealthMatrix(health) method producing a div.paperforge-health-matrix with 4 div.paperforge-health-cell children (PDF/OCR/Note/Asset), each with .ok/.warn/.fail class, status icon (unicode), and title attribute for tooltip"
    - "PaperForgeStatusView has a _renderMaturityGauge(maturityLevel, blockingChecks) method producing a div.paperforge-maturity-gauge with 6 segment divs (filled/unfilled), level number, and optional blocking checks list"
    - "PaperForgeStatusView has a _renderBarChart(lifecycleCounts) method producing a div.paperforge-bar-chart with div.bar-row children, each containing .bar-label, .bar-track > .bar-fill, and .bar-count"
    - "All 4 new render methods (and enhanced _renderStats) accept null/undefined data and render a loading skeleton container (add .paperforge-loading class to parent) instead of crashing"
    - "Enhanced _renderStats() supports optional progress sub-bar on any metric card via a _buildMetricBar() helper"
  artifacts:
    - path: "paperforge/plugin/main.js"
      provides: "New render methods on PaperForgeStatusView: _renderLifecycleStepper, _renderHealthMatrix, _renderMaturityGauge, _renderBarChart, plus enhanced _renderStats and _buildMetricBar helper"
      min_lines: 100  # new JS content expected
  key_links:
    - from: "main.js PaperForgeStatusView._renderLifecycleStepper"
      to: "styles.css .paperforge-lifecycle-stepper, .step, .step-indicator, .step-label, .completed, .current, .pending"
      via: "createEl() cls attribute matching CSS classes"
    - from: "main.js PaperForgeStatusView._renderHealthMatrix"
      to: "styles.css .paperforge-health-matrix, .health-cell, .ok, .warn, .fail"
      via: "createEl() cls attribute and addClass() calls"
    - from: "main.js PaperForgeStatusView._renderMaturityGauge"
      to: "styles.css .paperforge-maturity-gauge, .gauge-segment, .filled, .level-*, .gauge-blockers"
      via: "createEl() cls attribute matching CSS classes"
    - from: "main.js PaperForgeStatusView._renderBarChart"
      to: "styles.css .paperforge-bar-chart, .bar-row, .bar-label, .bar-track, .bar-fill, .bar-count"
      via: "createEl() cls attribute matching CSS classes"
---

<objective>
Implement 5 render methods on `PaperForgeStatusView` class in `paperforge/plugin/main.js` that produce DOM elements matching the CSS class names from Plan 27-01. All methods use `createEl()` DOM API -- no JSX, no innerHTML (D-01). All methods gracefully handle null/undefined data by rendering a loading skeleton (D-24). No new JS files -- all methods live on the existing PaperForgeStatusView class (D-28).

Purpose: Provide the render methods that Phase 28 (Dashboard Shell & Context Detection) and Phase 29/30 will call to populate dashboard views with concrete data.
Output: `paperforge/plugin/main.js` with 4 new methods and 1 enhanced method + 1 helper.
</objective>

<execution_context>
@C:/Users/Lin/.opencode/get-shit-done/workflows/execute-plan.md
@C:/Users/Lin/.opencode/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/phases/27-component-library/27-CONTEXT.md
@paperforge/plugin/main.js

No prior plan SUMMARY references needed -- this is the second plan of Phase 27.
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add loading skeleton utility + enhanced _renderStats() + _buildMetricBar() helper</name>
  <files>paperforge/plugin/main.js</files>

  <read_first>
  - paperforge/plugin/main.js (entire file, especially the PaperForgeStatusView class at lines 210-638, the existing _renderStats() at lines 378-392, and _buildPanel() at lines 232-280)
  - .planning/phases/27-component-library/27-CONTEXT.md (decisions D-01, D-04, D-05, D-06, D-24, D-25, D-28)
  </read_first>

  <action>
  Add the following to the `PaperForgeStatusView` class (between lines 210 and 638). Insert these methods AFTER the existing `_renderOcr(d)` method (which ends around line 461) and BEFORE the existing `_runAction(a, card)` method (which starts around line 463).

  **Method 1: `_renderSkeleton(container)` — utility that adds loading skeleton to any container (D-24)**

  ```javascript
  /* ── Loading Skeleton Utility (D-24) ── */
  _renderSkeleton(container) {
      container.addClass('paperforge-loading');
  }

  /* ── Empty State Utility (D-25) ── */
  _renderEmptyState(container, message) {
      container.createEl('div', {
          cls: 'paperforge-empty-state',
          text: message || 'No data',
      });
  }
  ```

  **Method 2: `_buildMetricBar(card, value, max)` — adds optional progress bar to a metric card (D-05)**

  ```javascript
  /* ── Metric Progress Bar Helper (D-05) ── */
  _buildMetricBar(card, value, max) {
      if (max <= 0) return;
      const pct = Math.min(100, (value / max) * 100);
      const bar = card.createEl('div', { cls: 'paperforge-metric-progress' });
      bar.createEl('div', {
          cls: 'paperforge-metric-progress-fill',
          attr: { style: `width:${pct.toFixed(1)}%` },
      });
  }
  ```

  **Method 3: Enhanced `_renderStats(d)` — adds loading skeleton support and optional progress bar (D-04, D-05, D-06)**

  Replace the EXISTING `_renderStats(d)` method (current lines 378-392) with this enhanced version:

  ```javascript
  /* ── Metric Cards (Enhanced D-04, D-05, D-06) ── */
  _renderStats(d) {
      this._versionBadge.setText(d.version ? 'v' + d.version : 'v\u2014');

      if (!d || typeof d.total_papers === 'undefined') {
          this._renderSkeleton(this._metricsEl);
          return;
      }

      this._metricsEl.removeClass('paperforge-loading');

      const totalPapers = d.total_papers || 0;
      const totalFormal = d.formal_notes || 0;

      const metrics = [
          { value: totalPapers, label: 'Papers', color: 'var(--color-cyan)', barMax: 0 },
          { value: totalFormal, label: 'Formal Notes', color: 'var(--color-blue)', barMax: totalPapers },
          { value: d.exports || 0, label: 'Exports', color: 'var(--color-purple)', barMax: 0 },
      ];
      for (const m of metrics) {
          const card = this._metricsEl.createEl('div', { cls: 'paperforge-metric-card' });
          card.style.setProperty('--metric-color', m.color);
          card.createEl('div', { cls: 'paperforge-metric-value', text: m.value?.toString() || '\u2014' });
          card.createEl('div', { cls: 'paperforge-metric-label', text: m.label });
          if (m.barMax > 0) {
              this._buildMetricBar(card, m.value, m.barMax);
          }
      }
  }
  ```

  Key changes from existing:
  - Added null check at top: if `d` is null/undefined or `d.total_papers` is undefined, render skeleton instead
  - Added `removeClass('paperforge-loading')` when data is valid
  - Changed Notes label from "Notes" to "Formal Notes" for clarity
  - Added `barMax` field to metric config -- when > 0, `_buildMetricBar` is called
  - Formal notes card gets a progress bar showing `formal_notes / total_papers` as fill percentage
  </action>

  <acceptance_criteria>
  - `main.js` PaperForgeStatusView class has a `_renderSkeleton(container)` method that calls `container.addClass('paperforge-loading')`
  - `main.js` PaperForgeStatusView class has a `_renderEmptyState(container, message)` method that creates a `div.paperforge-empty-state`
  - `main.js` PaperForgeStatusView class has a `_buildMetricBar(card, value, max)` method that creates `div.paperforge-metric-progress` with child `div.paperforge-metric-progress-fill`
  - `main.js` enhanced `_renderStats(d)` returns early with skeleton when `d` is null/undefined
  - `main.js` enhanced `_renderStats(d)` has the label "Formal Notes" instead of "Notes"
  - `main.js` enhanced `_renderStats(d)` calls `_buildMetricBar()` for the notes metric
  - All methods are defined as class methods (using proper `methodName() {` syntax, not arrow functions)
  - grep: `_renderSkeleton\b` exists, `_renderEmptyState\b` exists, `_buildMetricBar\b` exists
  - grep: `'Formal Notes'` appears in main.js
  </acceptance_criteria>

  <verify>
  <automated>python -c "
with open('paperforge/plugin/main.js') as f:
    c = f.read()
assert '_renderSkeleton' in c, 'Missing _renderSkeleton method'
assert '_renderEmptyState' in c, 'Missing _renderEmptyState method'
assert '_buildMetricBar' in c, 'Missing _buildMetricBar method'
assert \"'Formal Notes'\" in c, 'Missing updated label Formal Notes'
assert 'paperforge-metric-progress' in c, 'Missing progress bar class reference'
assert 'paperforge-loading' in c, 'Missing loading class reference'
assert 'addClass' in c and 'removeClass' in c, 'Missing class management calls'
print('Task 1 JS verification: ALL CHECKS PASSED')
"</automated>
  </verify>

  <done>
  Enhanced _renderStats() handles null data via skeleton loading state. _renderSkeleton() and _renderEmptyState() are reusable utilities. _buildMetricBar() adds optional progress bar to metric cards. Formal Notes card shows coverage ratio as a progress bar.
  </done>
</task>

<task type="auto">
  <name>Task 2: Add _renderLifecycleStepper() + _renderHealthMatrix() methods</name>
  <files>paperforge/plugin/main.js</files>

  <read_first>
  - paperforge/plugin/main.js (PaperForgeStatusView class methods, especially around the existing _renderStats/_renderOcr area)
  - .planning/phases/27-component-library/27-CONTEXT.md (decisions D-07 through D-16, D-24, D-25, D-26, D-28)
  </read_first>

  <action>
  Add the following TWO methods to the PaperForgeStatusView class. Insert them immediately after the `_buildMetricBar` method added in Task 1, continuing the pattern of utility/render methods before `_runAction`.

  **Method 4: `_renderLifecycleStepper(container, lifecycle, currentStage)` — renders 6-step vertical lifecycle indicator (D-07 through D-11, D-24)**

  ```javascript
  /* ── Lifecycle Stepper (D-07 through D-11) ── */
  _renderLifecycleStepper(container, lifecycle, currentStage) {
      if (!lifecycle || !currentStage) {
          this._renderSkeleton(container);
          return;
      }

      const stages = [
          { key: 'imported', label: 'Imported' },
          { key: 'indexed', label: 'Indexed' },
          { key: 'pdf_ready', label: 'PDF Ready' },
          { key: 'fulltext_ready', label: 'Fulltext Ready' },
          { key: 'deep_read', label: 'Deep Read' },
          { key: 'ai_ready', label: 'AI Ready' },
      ];

      const stepper = container.createEl('div', { cls: 'paperforge-lifecycle-stepper' });
      let foundCurrent = false;

      for (const stage of stages) {
          const step = stepper.createEl('div', { cls: 'step' });
          step.createEl('div', { cls: 'step-indicator' });
          step.createEl('div', { cls: 'step-label', text: stage.label });

          if (stage.key === currentStage) {
              step.addClass('current');
              foundCurrent = true;
          } else if (!foundCurrent) {
              // All stages before current are completed
              step.addClass('completed');
          } else {
              step.addClass('pending');
          }
      }
  }
  ```

  **Method 5: `_renderHealthMatrix(container, health)` — renders 2x2 health grid (D-12 through D-16, D-24)**

  ```javascript
  /* ── Health Matrix (D-12 through D-16) ── */
  _renderHealthMatrix(container, health) {
      if (!health) {
          this._renderSkeleton(container);
          return;
      }

      const dimensions = [
          { key: 'pdf_health', label: 'PDF Health', iconOk: '\u2713', iconWarn: '\u26A0', iconFail: '\u2717' },
          { key: 'ocr_health', label: 'OCR Health', iconOk: '\u2713', iconWarn: '\u26A0', iconFail: '\u2717' },
          { key: 'note_health', label: 'Note Health', iconOk: '\u2713', iconWarn: '\u26A0', iconFail: '\u2717' },
          { key: 'asset_health', label: 'Asset Health', iconOk: '\u2713', iconWarn: '\u26A0', iconFail: '\u2717' },
      ];

      const matrix = container.createEl('div', { cls: 'paperforge-health-matrix' });

      for (const dim of dimensions) {
          const status = health[dim.key] || 'healthy';
          const cell = matrix.createEl('div', { cls: 'paperforge-health-cell' });

          let icon, statusClass, tooltip;
          if (status === 'healthy' || status === 'ok') {
              icon = dim.iconOk;
              statusClass = 'ok';
              tooltip = `${dim.label}: OK`;
          } else if (status === 'warn' || status === 'warning' || status === 'degraded') {
              icon = dim.iconWarn;
              statusClass = 'warn';
              tooltip = `${dim.label}: Needs Attention`;
          } else {
              icon = dim.iconFail;
              statusClass = 'fail';
              tooltip = `${dim.label}: Failed`;
          }

          cell.addClass(statusClass);
          cell.setAttribute('title', tooltip);
          cell.createEl('div', { cls: 'paperforge-health-cell-icon', text: icon });
          cell.createEl('div', { cls: 'paperforge-health-cell-label', text: dim.label });
      }
  }
  ```
  </action>

  <acceptance_criteria>
  - `main.js` contains `_renderLifecycleStepper(container, lifecycle, currentStage)` method
  - Method renders exactly 6 stages matching D-11 labels: Imported, Indexed, PDF Ready, Fulltext Ready, Deep Read, AI Ready
  - Each step has `div.step` with children `div.step-indicator` and `div.step-label`
  - `addClass('current')` for matching stage, `addClass('completed')` for prior stages, `addClass('pending')` for later stages
  - Returns early with `_renderSkeleton(container)` when lifecycle or currentStage is null/undefined
  - `main.js` contains `_renderHealthMatrix(container, health)` method
  - Method creates `div.paperforge-health-matrix` with 4 `div.paperforge-health-cell` children
  - Each cell gets `.ok`/`.warn`/`.fail` class based on health status value
  - Each cell has `title` attribute set (for CSS tooltip per D-16)
  - Each cell has icon div + label div
  - Returns early with `_renderSkeleton(container)` when health is null/undefined
  - grep: `_renderLifecycleStepper` exists, `_renderHealthMatrix` exists
  - grep: `step-indicator` appears, `step-label` appears
  - grep: `paperforge-health-cell` appears
  </acceptance_criteria>

  <verify>
  <automated>python -c "
with open('paperforge/plugin/main.js') as f:
    c = f.read()
assert '_renderLifecycleStepper' in c, 'Missing lifecycle stepper method'
assert '_renderHealthMatrix' in c, 'Missing health matrix method'
assert \"'Imported'\" in c and \"'AI Ready'\" in c, 'Missing stage labels'
assert \"'step-indicator'\" in c, 'Missing step-indicator class'
assert \"'step-label'\" in c, 'Missing step-label class'
assert \"addClass('current')\" in c, 'Missing current state'
assert \"addClass('completed')\" in c, 'Missing completed state'
assert \"addClass('pending')\" in c, 'Missing pending state'
assert \"'paperforge-health-matrix'\" in c, 'Missing health matrix class'
assert \"'paperforge-health-cell'\" in c, 'Missing health cell class'
assert 'setAttribute' in c and \"'title'\" in c, 'Missing title attribute for tooltip'
assert \"addClass('ok')\" in c and \"addClass('fail')\" in c, 'Missing status classes'
print('Task 2 JS verification: ALL CHECKS PASSED')
"</automated>
  </verify>

  <done>
  _renderLifecycleStepper renders 6-stage vertical stepper with correct completed/current/pending classes. _renderHealthMatrix renders 2x2 grid with color-coded status cells and tooltip titles. Both methods gracefully handle null data via skeleton loading.
  </done>
</task>

<task type="auto">
  <name>Task 3: Add _renderMaturityGauge() + _renderBarChart() methods</name>
  <files>paperforge/plugin/main.js</files>

  <read_first>
  - paperforge/plugin/main.js (PaperForgeStatusView class methods area)
  - .planning/phases/27-component-library/27-CONTEXT.md (decisions D-17 through D-25, D-28)
  </read_first>

  <action>
  Add the following TWO methods to the PaperForgeStatusView class. Insert them immediately after the `_renderHealthMatrix` method added in Task 2.

  **Method 6: `_renderMaturityGauge(container, maturityLevel, blockingChecks)` — renders 6-segment gauge (D-17 through D-20, D-24, D-25)**

  ```javascript
  /* ── Maturity Gauge (D-17 through D-20) ── */
  _renderMaturityGauge(container, maturityLevel, blockingChecks) {
      if (maturityLevel == null || maturityLevel === undefined) {
          this._renderSkeleton(container);
          return;
      }

      const gauge = container.createEl('div', { cls: 'paperforge-maturity-gauge' });
      const track = gauge.createEl('div', { cls: 'gauge-track' });
      const currentLevel = Math.max(1, Math.min(6, Math.round(maturityLevel)));

      for (let i = 1; i <= 6; i++) {
          const seg = track.createEl('div', { cls: 'gauge-segment' });
          if (i <= currentLevel) {
              seg.addClass('filled');
              seg.addClass(`level-${i}`);
          }
      }

      gauge.createEl('div', { cls: 'gauge-level', text: `Level ${currentLevel} / 6` });

      if (currentLevel < 6 && blockingChecks && blockingChecks.length > 0) {
          const list = gauge.createEl('ul', { cls: 'gauge-blockers' });
          for (const check of blockingChecks) {
              list.createEl('li', { text: check });
          }
      }
  }
  ```

  **Method 7: `_renderBarChart(container, lifecycleCounts)` — renders horizontal bars from lifecycle counts (D-21 through D-23, D-24, D-25)**

  ```javascript
  /* ── Bar Chart (D-21 through D-23) ── */
  _renderBarChart(container, lifecycleCounts) {
      if (!lifecycleCounts || Object.keys(lifecycleCounts).length === 0) {
          this._renderEmptyState(container, 'No lifecycle data');
          return;
      }

      const stages = [
          { key: 'imported', label: 'Imported', cls: 'stage-imported' },
          { key: 'indexed', label: 'Indexed', cls: 'stage-indexed' },
          { key: 'pdf_ready', label: 'PDF Ready', cls: 'stage-pdf-ready' },
          { key: 'fulltext_ready', label: 'Fulltext Ready', cls: 'stage-fulltext-ready' },
          { key: 'deep_read', label: 'Deep Read', cls: 'stage-deep-read' },
          { key: 'ai_ready', label: 'AI Ready', cls: 'stage-ai-ready' },
      ];

      const chart = container.createEl('div', { cls: 'paperforge-bar-chart' });
      const maxCount = Math.max(1, ...stages.map(s => lifecycleCounts[s.key] || 0));

      for (const stage of stages) {
          const count = lifecycleCounts[stage.key] || 0;
          const pct = (count / maxCount) * 100;

          const row = chart.createEl('div', { cls: 'bar-row' });
          row.createEl('div', { cls: 'bar-label', text: stage.label });
          const track = row.createEl('div', { cls: 'bar-track' });
          const fill = track.createEl('div', {
              cls: `bar-fill ${stage.cls}`,
              attr: { style: `width:${pct.toFixed(1)}%` },
          });
          row.createEl('div', { cls: 'bar-count', text: count.toString() });
      }
  }
  ```
  </action>

  <acceptance_criteria>
  - `main.js` contains `_renderMaturityGauge(container, maturityLevel, blockingChecks)` method
  - Method creates `div.paperforge-maturity-gauge` with child `div.gauge-track`
  - Creates exactly 6 `div.gauge-segment` children, filled segments get `.filled` and `.level-N` classes
  - Creates `div.gauge-level` with text "Level N / 6"
  - Creates `ul.gauge-blockers` with `li` children only when currentLevel < 6 and blockingChecks is non-empty
  - Returns early with `_renderSkeleton(container)` when maturityLevel is null/undefined
  - `main.js` contains `_renderBarChart(container, lifecycleCounts)` method
  - Method creates `div.paperforge-bar-chart` with `div.bar-row` children
  - Each bar-row has child: `div.bar-label`, `div.bar-track` > `div.bar-fill`, `div.bar-count`
  - Bar fill widths computed as percentages of max count
  - Returns early with `_renderEmptyState(container, ...)` when lifecycleCounts is null/empty
  - grep: `_renderMaturityGauge` exists, `_renderBarChart` exists
  - grep: `gauge-segment` appears, `bar-fill` appears
  - grep: `gauge-blockers` appears, `bar-count` appears
  </acceptance_criteria>

  <verify>
  <automated>python -c "
with open('paperforge/plugin/main.js') as f:
    c = f.read()
assert '_renderMaturityGauge' in c, 'Missing maturity gauge method'
assert '_renderBarChart' in c, 'Missing bar chart method'
assert \"'gauge-track'\" in c, 'Missing gauge-track class'
assert \"'gauge-segment'\" in c, 'Missing gauge-segment class'
assert \"'gauge-blockers'\" in c, 'Missing gauge-blockers class'
assert \"'paperforge-bar-chart'\" in c, 'Missing bar chart class'
assert \"'bar-fill'\" in c, 'Missing bar-fill class'
assert \"'bar-count'\" in c, 'Missing bar-count class'
assert \"_renderEmptyState\" in c, 'Missing empty state call in bar chart'
assert \"_renderSkeleton\" in c, 'Missing skeleton call in maturity gauge'
print('Task 3 JS verification: ALL CHECKS PASSED')
"</automated>
  </verify>

  <done>
  _renderMaturityGauge creates 6-segment gauge with level-appropriate filled segments and blocking checks list. _renderBarChart creates horizontal bars proportional to max count with lifecycle stage color classes. Both methods handle null/empty data gracefully.
  </done>
</task>

</tasks>

<verification>
1. All 7 methods exist on PaperForgeStatusView: `_renderSkeleton`, `_renderEmptyState`, `_buildMetricBar`, `_renderStats` (enhanced), `_renderLifecycleStepper`, `_renderHealthMatrix`, `_renderMaturityGauge`, `_renderBarChart`
2. All methods use `createEl()` DOM API — no `innerHTML` assignments (grep: `innerHTML` only in pre-existing header refresh button and setup wizard)
3. All CSS class names in createEl() cls attributes match the CSS classes defined in Plan 27-01
4. No new JS files created — all methods on existing PaperForgeStatusView class
5. All rendering methods guard against null/undefined input
6. Skeleton loading and empty state have consistent patterns across all components
7. All unicode characters used for health icons (✓, ⚠, ✗) and checkmark (✓) are valid JavaScript string literals
</verification>

<success_criteria>
- [ ] All 7 methods added to PaperForgeStatusView class in main.js
- [ ] Enhanced _renderStats() handles null data and adds progress bar for Formal Notes
- [ ] _renderLifecycleStepper renders 6 stages with correct CSS classes (completed/current/pending)
- [ ] _renderHealthMatrix renders 2x2 grid with status icons and tooltip titles
- [ ] _renderMaturityGauge renders 6-segment gauge with level and blockers
- [ ] _renderBarChart renders horizontal proportional bars with lifecycle stage colors
- [ ] All methods gracefully handle null/undefined data
- [ ] CSS class names match Plan 27-01 exactly
- [ ] No innerHTML assignments in new code
- [ ] No new JS files
</success_criteria>

<output>
After completion, create `.planning/phases/27-component-library/27-02-SUMMARY.md`
</output>
