- Metric cards row: papers count, fulltext-ready (with progress bar), deep-read (with progress bar)
- Lifecycle distribution bar chart via _renderBarChart()
- Aggregated health overview via _renderCollectionHealth() showing PDF/OCR/Note/Asset counts
- Single-pass aggregation from domain-filtered canonical index entries
- Empty state via _renderEmptyState() when domain has no items
- Add _setupEventSubscriptions() for active-leaf-change (debounced 300ms) and vault modify (filtered to formal-library.json)
- Add _renderModeHeader() with mode badge (global/paper/collection classes) and context name
- Add mode warning for paper not found in index (D-18)
- Refactor onOpen() to call _setupEventSubscriptions() before _detectAndSwitch()
- Refactor onClose() with full cleanup: workspace/vault off(), clearTimeout, null cached data
- Refactor constructor with _currentMode, _currentDomain, _currentPaperKey, _currentPaperEntry state
- Refactor onOpen() to call _detectAndSwitch() for initial data load
- Refactor _buildPanel() with _modeContextEl, _contentEl, _actionsGrid elements
- Add _renderActions() extracted from old _buildPanel action loop
- Add _invalidateIndex() to clear cached index
- Add _detectAndSwitch() for active file type detection (D-01..D-04)
- Add _switchMode() for mode-based content rendering (D-05..D-06)
- Add _renderGlobalMode() with OCR section instance vars for _renderOcr compat
- Add _renderPaperMode() placeholder for Phase 29
- Add _renderCollectionMode() placeholder for Phase 30
- Add _refreshCurrentMode() for index change re-render
- Remove orphaned duplicate code block and duplicate _showMessage
- Clean up double closing brace from orphan removal
- _loadIndex(): reads formal-library.json, returns parsed JSON or null
- _getCachedIndex(): lazy-loads and caches items array in this._cachedItems
- _findEntry(key): look up single paper by zotero_key, returns entry or null
- _filterByDomain(domain): filters index items by domain field
Task 1: Add _renderSkeleton, _renderEmptyState, _buildMetricBar utilities + enhanced _renderStats with null guard and progress bar
Task 2: Add _renderLifecycleStepper (6-stage) and _renderHealthMatrix (2x2 grid) methods
Task 3: Add _renderMaturityGauge (6-segment) and _renderBarChart (horizontal bars) methods
All methods use createEl() DOM API, gracefully handle null/undefined via loading skeleton or empty state, and produce CSS classes matching Plan 27-01.
- Loading skeleton, metric card, lifecycle stepper, health matrix, maturity gauge, bar chart
- 391 lines of new CSS across 6 new sections, all using Obsidian CSS variables
- All 22 verification checks passed
- Section 7: Loading skeleton with @keyframes paperforge-shimmer (1.5s)
- Section 7: Empty state with muted italic styling via .paperforge-empty-state
- Section 2 enhanced: opacity 0.3s transition on metric-card and metric-value
- Added .paperforge-metric-progress and .paperforge-metric-progress-fill for optional progress bar
- All colors use var(--*) Obsidian CSS variables
- Add migrate_to_workspace() function to sync.py that copies flat notes to workspace directories
- Extract ## 🔍 精读 section into deep-reading.md in workspace
- Create ai/ directory in each paper workspace
- Wire migration into run_index_refresh() before build_index()
- Update _build_entry() in asset_index.py to write notes to workspace path when workspace dir exists
- Preserve flat path fallback for backward compatibility when workspace dir does not exist
- Add 'context' to _COMMAND_REGISTRY for dynamic module loading
- Add context subparser with key (nargs='?'), --domain, --collection, --all
- Dispatch in main() to paperforge.commands.context.run(args)
- D-06: always outputs JSON, no --json flag needed
- run() reads canonical index, filters by key/domain/collection/all
- _format_context_entry wraps entries with _provenance and _ai_readiness blocks
- AIC-04: blocking explanation when lifecycle != 'ai_context_ready'
- AIC-02: single key outputs single JSON object with provenance
- AIC-03: --domain and --collection output JSON arrays
- D-01: canonical index entry IS the AI context -- no separate pack format
- run_status() reads canonical index via summarize_index() for lifecycle,
health, and maturity aggregates; falls back to filesystem when index missing
- JSON output includes lifecycle_level_counts, health_aggregate,
maturity_distribution (or None when falling back)
- Text output shows lifecycle and health lines when index is present
- run_doctor() shows Index Health section with PDF/OCR/Note/Asset Health
counts and status per dimension
- Brownfield detection: legacy schema, old Base templates, partial OCR assets
- Fixed status_tag mapping to support 'info' status (existing bug)
- 6 new tests covering index-backed JSON output, fallback, text output,
Index Health with/without index, and mixed health counts
Phase 25-01, Tasks 2+3
- Add build_index() call at end of run_repair() when fix=True or fix_paths=True
- Add 'rebuilt' key to result dict initialization
- Add user-facing messages about repair completion and recovery path (MIG-04)
- Lazy import inside conditional block to avoid circular dependency
- Add tests: build_index called after fix, not called during dry-run,
rebuilt in result, error fallback on build_index failure