---
phase: 30-collection-view
plan: 01
type: execute
wave: 1
depends_on: [28-02, 27-02]
files_modified:
  - paperforge/plugin/main.js
  - paperforge/plugin/styles.css
autonomous: true
requirements: [COLL-01, COLL-02, COLL-03]

must_haves:
  truths:
    - "User opens a .base file — dashboard switches to collection mode with domain name in header and paper count shown"
    - "User sees metric cards row: total papers, fulltext-ready count (with progress bar), deep-read count (with progress bar)"
    - "User sees a lifecycle distribution bar chart via _renderBarChart() showing domain's papers across lifecycle stages"
    - "User sees aggregated health overview: PDF healthy/broken counts, OCR done/pending/failed counts, Note present/absent counts, Asset valid/drifted counts"
    - "Empty domain shows empty state message, not a placeholder"
  artifacts:
    - path: "paperforge/plugin/styles.css"
      provides: "Collection view layout CSS (Section 17)"
      contains: ".paperforge-collection-view, .paperforge-collection-metrics, .paperforge-collection-health"
    - path: "paperforge/plugin/main.js"
      provides: "Replaces _renderCollectionMode() placeholder with full collection view"
      contains: "_renderCollectionMode(), _renderCollectionHealth()"
  key_links:
    - from: "_switchMode('collection')"
      to: "_renderCollectionMode()"
      via: "this._renderCollectionMode()"
      pattern: "_renderCollectionMode"
    - from: "_renderCollectionMode()"
      to: "_renderBarChart()"
      via: "this._renderBarChart(view, lifecycleCounts)"
      pattern: "_renderBarChart"
    - from: "_renderCollectionMode()"
      to: "_renderCollectionHealth()"
      via: "this._renderCollectionHealth(view, healthAgg)"
      pattern: "_renderCollectionHealth"
    - from: "_renderCollectionMode()"
      to: "_buildMetricBar()"
      via: "this._buildMetricBar(card, value, max)"
      pattern: "_buildMetricBar"
---

<objective>
Wire up the collection-level aggregated dashboard view using Phase 27 component render methods, triggered by Phase 28 context detection. Replace the `_renderCollectionMode()` placeholder with a full rendering pipeline: metric cards (papers count, fulltext-ready, deep-read), lifecycle distribution bar chart, and aggregated health overview.

Purpose: User can see aggregated domain-level lifecycle and health statistics when viewing any Base file in the PaperForge dashboard.
Output: Modified `paperforge/plugin/main.js` + `paperforge/plugin/styles.css`
</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/ROADMAP.md
@.planning/STATE.md

@.planning/phases/27-component-library/27-02-SUMMARY.md
@.planning/phases/28-dashboard-shell-context-detection/28-02-SUMMARY.md

@paperforge/plugin/main.js
@paperforge/plugin/styles.css

<interfaces>
Entry shape from formal-library.json (canonical index entries built in Phase 23 and enriched in Phase 24):
```
entry = {
  "zotero_key": "ABCDEFG",
  "title": "Paper Title",
  "authors": ["Author1", "Author2"],
  "year": "2024",
  "domain": "骨科",
  "lifecycle": "fulltext_ready",
  "health": {
    "pdf_health": "ok",
    "ocr_health": "done",
    "note_health": "healthy",
    "asset_health": "healthy"
  },
  "ocr_status": "done",
  "note_path": "Literature/domain/key - Title.md",
}
```

Available render methods (from Phase 27):
```javascript
_renderBarChart(container, lifecycleCounts)
  // lifecycleCounts: { imported: N, indexed: N, pdf_ready: N, fulltext_ready: N, deep_read: N, ai_ready: N }
  // Renders horizontal bar chart with color-coded stages

_renderSkeleton(container)    // adds .paperforge-loading class for shimmer
_renderEmptyState(container, message)  // shows muted "No data" / custom message

_buildMetricBar(card, value, max)  // creates progress bar inside metric card
```

State references (from Phase 28):
```javascript
this._currentDomain      // domain string or null (set by _detectAndSwitch for .base files)
this._currentMode        // 'global' | 'paper' | 'collection'
this._contentEl          // .paperforge-content-area container
this._filterByDomain(domain)  // returns array of entries matching domain
```

Lifecycle stages for bar chart (from Phase 27):
- imported, indexed, pdf_ready, fulltext_ready, deep_read, ai_ready
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add Section 17 CSS for collection view layout</name>
  <files>paperforge/plugin/styles.css</files>
  <read_first>
    - paperforge/plugin/styles.css (read the last ~50 lines to see Section 16 ending and ensure clean append)
  </read_first>
  <action>
    Append one new CSS section to `paperforge/plugin/styles.css` AFTER the last Section 16 closing line (after line 1267 which is the last `.paperforge-next-step-trigger.running` rule). Use the exact same section header format as existing sections.

    **Section 17 — Collection View Layout:**

    ```css
    /* ==========================================================================
       SECTION 17 — Collection View Layout
       ========================================================================== */
    .paperforge-collection-view {
        display: flex;
        flex-direction: column;
        gap: 24px;
    }

    .paperforge-collection-metrics {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
        gap: 10px;
    }

    .paperforge-collection-health {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
    }

    .paperforge-collection-health-cell {
        background: var(--background-secondary);
        border: 1px solid var(--background-modifier-border);
        border-radius: var(--radius-m);
        padding: 14px 12px;
        display: flex;
        flex-direction: column;
        gap: 6px;
        position: relative;
    }

    .paperforge-collection-health-cell-label {
        font-size: var(--font-ui-smaller);
        font-weight: var(--font-semibold);
        color: var(--text-normal);
        text-transform: uppercase;
        letter-spacing: 0.3px;
    }

    .paperforge-collection-health-counts {
        font-size: var(--font-ui-small);
        color: var(--text-muted);
        line-height: 1.6;
    }

    .paperforge-collection-health-counts .ok {
        color: var(--color-green);
    }

    .paperforge-collection-health-counts .warn {
        color: var(--color-yellow);
    }

    .paperforge-collection-health-counts .fail {
        color: var(--color-red);
    }
    ```

    Insert the above section at the end of the file, after the last line. Ensure section numbering is correct — this is Section 17, continuing from Phase 29's Section 16.
  </action>
  <acceptance_criteria>
    - `styles.css` contains `/* SECTION 17 -- Collection View Layout */`
    - Class `.paperforge-collection-view` defined in CSS
    - Class `.paperforge-collection-metrics` defined in CSS
    - Class `.paperforge-collection-health` defined in CSS
    - Class `.paperforge-collection-health-cell` defined in CSS
    - Class `.paperforge-collection-health-counts` defined in CSS
    - No syntax errors: `node -e "require('fs').readFileSync('paperforge/plugin/styles.css','utf-8')"` succeeds
  </acceptance_criteria>
  <done>CSS Section 17 added. Collection view has layout container, metrics grid, health overview grid, health cell, and health counts styling.</done>
</task>

<task type="auto">
  <name>Task 2: Replace _renderCollectionMode() placeholder with full collection view rendering</name>
  <files>paperforge/plugin/main.js</files>
  <read_first>
    - paperforge/plugin/main.js (full file, to see existing _renderCollectionMode placeholder lines 956-971, _renderBarChart method lines 663-694, _renderStats method lines 458-485 for metric card pattern, _buildMetricBar lines 412-420)
  </read_first>
  <action>
    Replace the existing `_renderCollectionMode()` method (lines 956-971) with the full implementation below. Also add a new helper method `_renderCollectionHealth(container, healthAgg)`.

    **Step 1: Replace `_renderCollectionMode()`**

    Remove the existing placeholder (lines 956-971). Insert the new implementation:

    ```javascript
    /* ── Collection Mode Render (Phase 30) ── */
    _renderCollectionMode() {
        const domain = this._currentDomain || 'Unknown';
        const domainItems = this._filterByDomain(domain);

        const view = this._contentEl.createEl('div', { cls: 'paperforge-collection-view' });

        // --- Empty state ---
        if (domainItems.length === 0) {
            this._renderEmptyState(view, 'No papers found in domain "' + domain + '". Sync some papers first.');
            return;
        }

        // --- Single-pass aggregation ---
        const lifecycleCounts = {};
        const healthAgg = {
            pdf_health: { healthy: 0, unhealthy: 0 },
            ocr_health: { healthy: 0, unhealthy: 0 },
            note_health: { healthy: 0, unhealthy: 0 },
            asset_health: { healthy: 0, unhealthy: 0 },
        };
        let fulltextReady = 0;
        let deepRead = 0;

        for (const item of domainItems) {
            const lifecycle = item.lifecycle || 'pdf_ready';
            lifecycleCounts[lifecycle] = (lifecycleCounts[lifecycle] || 0) + 1;

            if (['fulltext_ready', 'deep_read', 'ai_ready'].includes(lifecycle)) fulltextReady++;
            if (['deep_read', 'ai_ready'].includes(lifecycle)) deepRead++;

            const health = item.health || {};
            for (const dim of ['pdf_health', 'ocr_health', 'note_health', 'asset_health']) {
                const val = health[dim] || 'healthy';
                if (val === 'healthy') healthAgg[dim].healthy++;
                else healthAgg[dim].unhealthy++;
            }
        }

        // --- Metric cards row (D-03) ---
        const metrics = view.createEl('div', { cls: 'paperforge-collection-metrics' });
        const totalPapers = domainItems.length;

        const metricCards = [
            { value: totalPapers, label: 'Papers', color: 'var(--color-cyan)', barMax: 0 },
            { value: fulltextReady, label: 'Fulltext Ready', color: 'var(--color-green)', barMax: totalPapers },
            { value: deepRead, label: 'Deep Read', color: 'var(--color-yellow)', barMax: totalPapers },
        ];
        for (const m of metricCards) {
            const card = metrics.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);
            }
        }

        // --- Lifecycle distribution bar chart (D-04) ---
        this._renderBarChart(view, lifecycleCounts);

        // --- Health overview (D-05) ---
        this._renderCollectionHealth(view, healthAgg);
    }
    ```

    **Step 2: Add `_renderCollectionHealth(container, healthAgg)` helper**

    Insert this NEW method AFTER `_renderCollectionMode()` (after its closing brace):

    ```javascript
    /* ── Collection Health Overview (Phase 30, D-05) ── */
    _renderCollectionHealth(container, healthAgg) {
        if (!healthAgg) return;

        const dimensions = [
            { key: 'pdf_health', label: 'PDF Health', okLabel: 'Healthy', failLabel: 'Broken' },
            { key: 'ocr_health', label: 'OCR Health', okLabel: 'Done', failLabel: 'Pending/Failed' },
            { key: 'note_health', label: 'Note Health', okLabel: 'Present', failLabel: 'Missing' },
            { key: 'asset_health', label: 'Asset Health', okLabel: 'Valid', failLabel: 'Drifted' },
        ];

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

        for (const dim of dimensions) {
            const data = healthAgg[dim.key] || { healthy: 0, unhealthy: 0 };
            const cell = grid.createEl('div', { cls: 'paperforge-collection-health-cell' });
            cell.createEl('div', { cls: 'paperforge-collection-health-cell-label', text: dim.label });

            const counts = cell.createEl('div', { cls: 'paperforge-collection-health-counts' });
            const okSpan = counts.createEl('span', { cls: 'ok', text: String(data.healthy) + ' ' + dim.okLabel });
            counts.createEl('span', { text: ' \u00B7 ' });
            const failSpan = counts.createEl('span', { cls: 'fail', text: String(data.unhealthy) + ' ' + dim.failLabel });
        }
    }
    ```

    **IMPORTANT IMPLEMENTATION RULES:**
    - Insert `_renderCollectionHealth` AFTER `_renderCollectionMode`, BEFORE the `_refreshCurrentMode` method.
    - The `_renderBarChart(container, lifecycleCounts)` method already exists from Phase 27 (lines 663-694) — do NOT modify it.
    - The `_buildMetricBar(card, value, max)` method already exists from Phase 27 (lines 412-420) — do NOT modify it.
    - Metric cards use the same `.paperforge-metric-card` class from Phase 27 CSS with `--metric-color` for the left accent bar.
    - Do NOT modify `_switchMode`, `_detectAndSwitch`, `_refreshCurrentMode`, or any existing method signatures.
    - Health aggregation is computed inline from `domainItems` — no need to read from `_cachedStats`.
    - The `lifecycleCounts` object uses lifecycle stage keys: imported, indexed, pdf_ready, fulltext_ready, deep_read, ai_ready
    - Fulltext-ready threshold: lifecycle in ['fulltext_ready', 'deep_read', 'ai_ready']
    - Deep-read threshold: lifecycle in ['deep_read', 'ai_ready']
  </action>
  <acceptance_criteria>
    - `main.js` contains `_renderCollectionMode()` replaced with new implementation
    - `main.js` contains `_renderCollectionHealth(container, healthAgg)` method
    - `main.js` parses without syntax errors: `node -c paperforge/plugin/main.js` succeeds
    - No reference to the old placeholder text "Phase 30 will add" remains in _renderCollectionMode
    - Empty domain renders via `_renderEmptyState()`, not placeholder text
    - Health overview renders 4 cells (pdf, ocr, note, asset) with healthy/unhealthy counts
    - Metric cards use `.paperforge-collection-metrics` container with 3 cards: Papers, Fulltext Ready, Deep Read
    - Fulltext-ready count computed from lifecycle stages fulltext_ready, deep_read, ai_ready
    - Deep-read count computed from lifecycle stages deep_read, ai_ready
    - Bar chart called with `(view, lifecycleCounts)` where lifecycleCounts aggregates domain items
  </acceptance_criteria>
  <done>_renderCollectionMode() renders: metric cards row (papers count, fulltext-ready with progress bar, deep-read with progress bar), lifecycle distribution bar chart via _renderBarChart(), and aggregated health overview grid with healthy/unhealthy counts for PDF/OCR/Note/Asset dimensions. Empty state handled gracefully. Placeholder text fully replaced.</done>
</task>

</tasks>

<verification>
1. Open a .base file (any domain) — PaperForge dashboard switches to collection mode with domain name in header and 3 metric cards.
2. Metric cards show: total papers, fulltext-ready count with progress bar, deep-read count with progress bar.
3. Lifecycle distribution bar chart renders with color-coded horizontal bars for all lifecycle stages present in the domain.
4. Health overview grid shows 4 cells (PDF Health, OCR Health, Note Health, Asset Health) with healthy/unhealthy counts.
5. Empty domain shows "No papers found" empty state message.
6. File parses: `node -c paperforge/plugin/main.js` exits 0.
</verification>

<success_criteria>
- Phase 28 `_switchMode('collection')` → `_renderCollectionMode()` renders a complete collection dashboard
- Metric cards, bar chart, and health overview all display with correct data from canonical index
- Existing Phase 27 and Phase 28 methods unchanged — pure wiring phase
- Empty state handled when domain has no items
</success_criteria>

<output>
After completion, create `.planning/phases/30-collection-view/30-01-SUMMARY.md`
</output>
