mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
docs: complete v2.0 project research — 6-layer testing infrastructure synthesis
- STACK.md: pytest 8+, Vitest 2+, obsidian-test-mocks, plasma CI matrix - FEATURES.md: table stakes, differentiators, anti-features, MVP recommendation - ARCHITECTURE.md: modified testing diamond, mock systems, fixture hierarchy, 6 ADRs - PITFALLS.md: 13 pitfalls with prevention strategies, recovery plans - SUMMARY.md: cross-cutting synthesis with 5-phase roadmap implications
This commit is contained in:
parent
5b30c02392
commit
6b68794c76
5 changed files with 2707 additions and 1761 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,461 +1,97 @@
|
|||
# Feature Research: v1.8 AI Discussion Recording & Deep-Reading Dashboard
|
||||
# Feature Landscape: 6-Layer Testing Infrastructure
|
||||
|
||||
**Domain:** AI discussion recording and deep-reading dashboard for Obsidian literature asset management
|
||||
**Researched:** 2026-05-06
|
||||
**Confidence:** HIGH
|
||||
**Domain:** Multi-layer quality gate testing
|
||||
**Researched:** 2026-05-08
|
||||
|
||||
## Feature Landscape
|
||||
## Table Stakes
|
||||
|
||||
### Table Stakes (Users Expect These)
|
||||
Features the testing infrastructure must provide. Missing any = incomplete.
|
||||
|
||||
Features users assume exist. Missing these = product feels incomplete.
|
||||
| Feature | Why Expected | Complexity | Notes |
|
||||
|---------|--------------|------------|-------|
|
||||
| Version consistency gate (L0) | Preventing deployment with mismatched versions across Python package, Obsidian plugin, and config is critical for Ops | Low | Single Python script; checks `__init__.py`, `manifest.json`, `versions.json` |
|
||||
| Unit tests (L1) | Without isolated unit tests, regressions are caught too late (E2E only) | Low | Existing 470+ tests move into `tests/unit/` |
|
||||
| CLI output contract tests (L2) | Plugin and agents parse CLI JSON output; schema changes break downstream | Medium | Subprocess invoker + snapshot assertions |
|
||||
| Plugin unit tests (L3) | Plugin JS runs inside Obsidian; mock-based tests catch logic errors before manual QA | Medium | Vitest + obsidian-test-mocks |
|
||||
| E2E pipeline tests (L4) | Integration points between sync/OCR/status must be tested together | High | Temp vault lifecycle; 3 mock systems |
|
||||
| Golden datasets | Deterministic test data shared across all layers | Medium | Centralized `fixtures/` with zotero/, pdf/, ocr/, snapshots/ |
|
||||
| CI integration | Tests must run automatically on PR and merge | Medium | GitHub Actions workflows with matrix |
|
||||
| Mock system for OCR | Tests must not hit real PaddleOCR API | Medium | `responses` HTTP mock + fixture JSON responses |
|
||||
| Mock system for Zotero | Tests must not require real Zotero installation | Low | Static JSON fixtures in `fixtures/zotero/` |
|
||||
| Mock vault filesystem | Each test needs an isolated, disposable vault | Medium | `tmp_path` + factory function for configurable completeness |
|
||||
|
||||
| Category | Feature | Why Expected | Complexity | Notes |
|
||||
|----------|---------|--------------|------------|-------|
|
||||
| AI Discussion | **Discussion history in workspace** | Researchers expect to find past AI conversations about a paper next to the paper itself, not scattered in browser tabs or chat logs | MEDIUM | Already have `ai/` directory; just need to populate it |
|
||||
| AI Discussion | **Human-readable Q&A format** | Users need to browse and reference past discussions without parsing JSON or opening special tools | LOW | Markdown Q&A format (问题:/解答:) is naturally scannable |
|
||||
| AI Discussion | **Timestamped chronology** | Every AI chat platform shows timestamps; users expect to know "when did I discuss this?" | LOW | Simple date/time stamp per Q&A pair |
|
||||
| Deep-Reading Dashboard | **Know what deep-reading exists** | When viewing a paper's `deep-reading.md`, users expect to see at a glance whether Pass 1/2/3 are complete, without scrolling through the entire note | MEDIUM | Status bar at the top of the dashboard; parse Pass headers |
|
||||
| Deep-Reading Dashboard | **Quick navigation to deep-reading content** | The deep-reading.md is a long file; users need a way to jump directly to it from the paper dashboard | LOW | "Jump to Deep Reading" contextual button |
|
||||
| Navigation | **Jump-to-deep-reading from per-paper card** | If the user is looking at a paper's health/lifecycle dashboard, the natural next question is "can I read the deep analysis?" | LOW | Single button on the per-paper dashboard card |
|
||||
| Bug Fixes | **Version number shown** | Users expect to confirm which version of PaperForge they're running, especially when reporting issues | LOW | Restore the `_versionBadge` update path |
|
||||
| Bug Fixes | **No meaningless UI rows** | If a UI element shows irrelevant data (like the "ai" row), it erodes trust in the rest of the dashboard | LOW | Remove or replace with meaningful data |
|
||||
## Differentiators
|
||||
|
||||
### Differentiators (Competitive Advantage)
|
||||
Features that add significant value beyond basic testing.
|
||||
|
||||
Features that set PaperForge apart from generic Zotero sync or AI chat tools.
|
||||
| Feature | Value Proposition | Complexity | Notes |
|
||||
|---------|-------------------|------------|-------|
|
||||
| Snapshot testing for CLI output | Catches unintended output changes immediately; makes output format changes deliberate commits | Low | Single assertion per contract test |
|
||||
| Plasma CI matrix | Optimizes CI cost while maintaining coverage confidence | Medium | Full matrix for fast tests, narrow matrix for slow tests |
|
||||
| User journey tests (L5) | Validates complete workflows against documented UX contracts; catches cross-component gaps that E2E misses | High | Most expensive tests; run only on merge to main |
|
||||
| Chaos/destructive tests (L6) | Ensures system handles corrupted inputs, network failures, disk issues gracefully | High | Scheduled weekly; parallelized by scenario |
|
||||
| Hierarchical conftest fixtures | Root conftest provides shared fixtures; each layer's conftest extends/overrides | Medium | Reduces duplication while keeping layer-specific behavior |
|
||||
| Unified vault_builder factory | Single entry point for creating vaults at 3 completeness levels | Medium | `VaultBuilder(fixtures_root)` with `"minimal"`, `"standard"`, `"full"` modes |
|
||||
| Chaos scenario matrix documentation | `CHaOS_MATRIX.md` documents all destructive scenarios, triggers, expected behavior | Low | Living document; updated as new edge cases discovered |
|
||||
|
||||
| Category | Feature | Value Proposition | Complexity | Notes |
|
||||
|----------|---------|-------------------|------------|-------|
|
||||
| AI Discussion | **Dual-format output: discussion.md + discussion.json** | Human-readable markdown for browsing AND structured JSON for dashboard consumption. No other literature tool bridges this gap with local-first files. | MEDIUM | JSON feeds the dashboard Q&A history card; markdown is the reference copy |
|
||||
| AI Discussion | **Chronological session grouping with metadata** | Groups Q&A pairs by session (each `/pf-paper` or `/pf-deep` invocation), with start time, model, and agent type recorded. Enables session-level browsing. | MEDIUM | Tier-of-sessions > questions-within-session structure |
|
||||
| Deep-Reading Dashboard | **Context-aware mode: deep-reading.md auto-detection** | When user opens `deep-reading.md`, the dashboard switches to a dedicated mode showing Pass status, AI Q&A history, and fulltext summary — NOT the per-paper lifecycle dashboard | MEDIUM | Extends existing mode-switching architecture (already has global/paper/collection) |
|
||||
| Deep-Reading Dashboard | **Pass status overview with completion indicators** | Shows at-a-glance which of the three Keshav passes are complete, with content snippets. Reduces need to scroll through long deep-reading.md files. | MEDIUM | Parse `### Pass 1: 概览`, `### Pass 2: 精读还原`, `### Pass 3: 深度理解` |
|
||||
| Deep-Reading Dashboard | **Recent AI Q&A from discussion.json** | The dashboard surfaces the most recent AI discussions about the paper directly in the deep-reading mode view. Bridges the gap between dashboard and chat history. | MEDIUM | Read `discussion.json` from `ai/` directory; show last N Q&A pairs |
|
||||
| AI Discussion | **Voluntary recording model (never auto-triggered)** | Differentiates from "AI logs everything" approaches. Users opt in by running `/pf-paper` or `/pf-deep` — the recording is explicit, not surveillance. | LOW | Recording happens as a side-effect of user-initiated agent commands |
|
||||
## Anti-Features
|
||||
|
||||
### Anti-Features
|
||||
Features to explicitly NOT build.
|
||||
|
||||
Features that seem good but create problems.
|
||||
|
||||
| Feature | Why Requested | Why Problematic | Alternative |
|
||||
|---------|---------------|-----------------|-------------|
|
||||
| **Auto-recording all agent conversations** | "I never want to lose a conversation" | Creates noise files for abandoned/toy prompts; violates the worker/agent boundary; may capture sensitive or experimental prompts user wants ephemeral | Record only when user runs `/pf-paper` or `/pf-deep` for a specific paper — voluntary and targeted |
|
||||
| **Full chat transcription in markdown** | "I want to replay the entire conversation" | Long, unstructured, hard to extract value; duplicates what the agent already stores internally | Store structured Q&A pairs with 问题:/解答: format; only meaningful exchanges, not every tool call and status message |
|
||||
| **Deep-reading dashboard as a replacement for deep-reading.md** | "I just want the dashboard, not the long note" | The deep-reading.md is the authoritative source; a dashboard summary that diverges creates inconsistency and distrust | Dashboard is an index/summary view INTO the deep-reading.md; it links to the full content, never replaces it |
|
||||
| **Real-time dashboard updates during deep-reading** | "I want to see progress live" | Deep reading is agent-executed outside the plugin; real-time sync would require polling or WebSocket complexity for marginal value | Dashboard refreshes on active-leaf-change (already built); shows final state after deep reading completes |
|
||||
| **Discussion search across all papers** | "Find any discussion about 'osteoporosis' across my library" | Full-text search is already handled by Obsidian; rebuilding search in the plugin duplicates functionality poorly | Rely on Obsidian's built-in vault search for cross-paper discussion search |
|
||||
| Anti-Feature | Why Avoid | What to Do Instead |
|
||||
|--------------|-----------|-------------------|
|
||||
| Real Obsidian E2E in CI | Requires running Obsidian process; adds 3+ min per test; flaky results | Use `obsidian-test-mocks` for unit tests; manual QA for real Obsidian |
|
||||
| Load/performance testing | PaperForge is local single-user tool; no performance requirements | Not needed; defer unless user reports slow performance |
|
||||
| Cross-browser testing | Plugin runs inside Obsidian's Electron instance (Chromium) | Not applicable; plugin does not need cross-browser compat |
|
||||
| Full parallel test matrix (3×3×6) | Would cost 54 CI jobs per PR for minimal added confidence | Plasma matrix: L1 on 3×3, L2-L5 on 1-2 configs |
|
||||
| Test database / snapshots on every test | Snapshot tests add maintenance burden; use only for CLI contract output | Limit snapshot tests to JSON output format stability |
|
||||
| Automated visual regression | Screenshot-based plugin UI tests are fragile and high-maintenance | Defer; component-level tests catch logic errors at lower cost |
|
||||
|
||||
## Feature Dependencies
|
||||
|
||||
```
|
||||
AI Discussion Recorder (discussion.md + discussion.json)
|
||||
├──requires──> Existing ai/ directory (v1.6 workspace migration)
|
||||
├──requires──> /pf-paper and /pf-deep agent commands exist
|
||||
└──feeds──> Deep-Reading Dashboard (AI Q&A History card)
|
||||
|
||||
Deep-Reading Dashboard Mode
|
||||
├──requires──> Existing mode-switching architecture (global/paper/collection)
|
||||
├──requires──> deep-reading.md exists in paper workspace
|
||||
├──requires──> discussion.json exists for AI Q&A card
|
||||
└──enhances──> Per-paper Dashboard (provides entry point via Jump button)
|
||||
|
||||
Jump-to-Deep-Reading Button
|
||||
├──requires──> Per-paper dashboard exists (v1.7)
|
||||
├──requires──> deep_reading_path in canonical index
|
||||
└──triggers──> Deep-Reading Dashboard mode (navigates user there)
|
||||
|
||||
Bug Fix: Version Number
|
||||
├──requiress──> _versionBadge element exists (v1.7)
|
||||
└──requires──> version field populated in _cachedStats
|
||||
|
||||
Bug Fix: Meaningless "ai" Row
|
||||
└──removes──> Stale UI element from pre-workspace era
|
||||
fixtures/ (golden datasets)
|
||||
|
|
||||
+--> L0 (version check) -- independent, needs only scripts/
|
||||
|
|
||||
+--> L1 (unit tests) -- needs fixtures/ + mock systems
|
||||
| |
|
||||
| +--> L2 (CLI contracts) -- needs L1 + fixtures/snapshots/
|
||||
|
|
||||
+--> L3 (plugin tests) -- independent of Python layers; needs fixtures/ for mock vault config
|
||||
|
|
||||
L2 + L3
|
||||
|
|
||||
+--> L4 (E2E) -- needs golden datasets + mock systems + vault_builder
|
||||
| |
|
||||
| +--> L5 (journey) -- needs E2E working + UX_CONTRACT.md
|
||||
|
|
||||
L6 (chaos) -- independent but shares mock systems
|
||||
```
|
||||
|
||||
### Dependency Notes
|
||||
## MVP Recommendation
|
||||
|
||||
- **AI Discussion Recorder feeds Deep-Reading Dashboard:** The dashboard's AI Q&A History card reads `discussion.json`. Without the recorder, this card shows an empty state. Both features should be built in the same phase.
|
||||
- **Deep-Reading Dashboard extends paper mode detection:** The existing `_detectAndSwitch()` already handles `.md` files with `zotero_key` frontmatter. The new mode adds a check: if the active file is named `deep-reading.md` and resides in a paper workspace directory, switch to `deep-reading` mode instead of falling back to `global`.
|
||||
- **Jump-to-Deep-Reading bridges per-paper and deep-reading dashboards:** This is a one-click navigation affordance on the per-paper card. It depends on the `deep_reading_path` field in the canonical index (already populated by `asset_index.py` v1.6).
|
||||
- **Bug fixes are independent:** They don't block any new feature and can be shipped independently, but bundling in v1.8 improves perceived quality of the new features.
|
||||
**Must have for v2.0 launch:**
|
||||
|
||||
## MVP Definition
|
||||
1. **L0 + L1** (version sync + unit tests) — highest value per effort; existing tests just need relocation
|
||||
2. **`fixtures/` directory** (golden datasets) — prerequisite for all higher layers
|
||||
3. **Mock OCR backend** — enables deterministic testing without real API calls
|
||||
4. **L2 CLI contract tests** — protects the plugin<->Python boundary
|
||||
5. **`ci-pr-checks.yml`** — fast pre-flight gate (L0 + L1) on every PR
|
||||
|
||||
### Must Have (v1.8 Launch)
|
||||
**Must have for v2.0 completion:**
|
||||
|
||||
These define the milestone. Without them, "v1.8 AI Discussion & Deep-Reading Dashboard" is not delivered.
|
||||
6. **L3 plugin tests** — Vitest + obsidian-test-mocks
|
||||
7. **L4 temp vault E2E** — full pipeline confidence
|
||||
8. **`ci.yml` full gate** — complete CI with all layers
|
||||
|
||||
- [x] **AI Discussion Recorder (discussion.md + discussion.json):** Python recorder module that writes structured Q&A to `ai/discussion.md` (human-readable) and `ai/discussion.json` (dashboard-consumable) when `/pf-paper` or `/pf-deep` is run.
|
||||
- [x] **Deep-Reading Dashboard Mode:** Plugin detects `deep-reading.md` as active file, switches to `deep-reading` mode showing Pass status summary + AI Q&A history.
|
||||
- [x] **Jump-to-Deep-Reading Button:** On per-paper dashboard card, a button that opens the paper's `deep-reading.md` in Obsidian and triggers the deep-reading dashboard mode.
|
||||
- [x] **Bug Fix: Version Number:** Restore version badge display in the plugin header (reads from canonical index or plugin manifest).
|
||||
- [x] **Bug Fix: Remove meaningless "ai" row:** Identify and remove the stale "ai" UI row from the dashboard.
|
||||
**Defer to v2.1 if needed:**
|
||||
|
||||
### Should Have (v1.8.x Follow-Up)
|
||||
|
||||
Deferrable without breaking the milestone.
|
||||
|
||||
- [ ] **Discussion session merging:** If a user runs `/pf-paper` twice for the same paper, append to existing `discussion.md` rather than overwrite.
|
||||
- [ ] **Session metadata in discussion.json:** Record agent type (pf-paper vs pf-deep), model, and duration for each session.
|
||||
- [ ] **Pass completion percentage in deep-reading dashboard:** Calculate rough completion (filled headings / total headings) for each Pass.
|
||||
|
||||
### Future Consideration (v2+)
|
||||
|
||||
- [ ] **Discussion search across library:** A dashboard view or Base showing all AI discussions across all papers.
|
||||
- [ ] **Discussion export to context pack:** Include relevant discussion.json in the AI context pack for follow-up questions.
|
||||
- [ ] **Deep-reading maturity integration:** Factor deep-reading completion into the existing maturity gauge/score.
|
||||
|
||||
## Feature Prioritization Matrix
|
||||
|
||||
| Feature | User Value | Implementation Cost | Priority |
|
||||
|---------|------------|---------------------|----------|
|
||||
| AI Discussion Recorder (discussion.md) | HIGH — researchers lose conversations in chat logs | MEDIUM — new Python module, template, integration with agent commands | P1 |
|
||||
| AI Discussion Recorder (discussion.json) | MEDIUM — enables dashboard, but markdown is the primary value | MEDIUM — JSON serialization layer on top of discussion.md data | P1 |
|
||||
| Deep-Reading Dashboard: Pass status | HIGH — immediate answer to "how complete is my reading?" | MEDIUM — new plugin render function, markdown parsing | P1 |
|
||||
| Deep-Reading Dashboard: AI Q&A history | MEDIUM — surfaces recorded discussions in dashboard | LOW — reads existing discussion.json, renders cards | P1 |
|
||||
| Deep-Reading Dashboard: Mode detection | HIGH — entry point for the whole feature | LOW — extends existing _detectAndSwitch() | P1 |
|
||||
| Jump-to-Deep-Reading button | HIGH — bridges paper dashboard to deep reading | LOW — single contextual button + Obsidian openLinkText() | P1 |
|
||||
| Bug Fix: Version number | MEDIUM — users notice when absent | LOW — single-line fix in _renderStats / _cachedStats | P1 |
|
||||
| Bug Fix: Remove "ai" row | LOW — cosmetic | LOW — remove dead code | P1 |
|
||||
|
||||
## Feature Details: AI Discussion Recording
|
||||
|
||||
### Discussion Format
|
||||
|
||||
**discussion.md** (human-readable, one file per paper, chronologically appended):
|
||||
|
||||
```markdown
|
||||
---
|
||||
paper_key: ABCDEFG
|
||||
paper_title: "Mechanisms of Osteoarthritis..."
|
||||
created: 2026-05-06T10:30:00Z
|
||||
updated: 2026-05-06T14:45:00Z
|
||||
session_count: 2
|
||||
---
|
||||
|
||||
# AI Discussions
|
||||
|
||||
## Session 2026-05-06 10:30 — Quick Summary
|
||||
|
||||
**Agent:** /pf-paper
|
||||
**Started:** 2026-05-06 10:30:00
|
||||
|
||||
### 问题: What is the main finding of this paper?
|
||||
|
||||
**解答:** The paper demonstrates that Piezo1 mechanosensitive ion channels mediate...
|
||||
(Observed in Figure 3: IHC staining shows Piezo1 expression in chondrocytes...)
|
||||
|
||||
**来源:** Pass 1 overview, Figure 3
|
||||
|
||||
---
|
||||
|
||||
### 问题: What signaling pathways are involved?
|
||||
|
||||
**解答:** The study identifies Ca²⁺/NFAT and YAP/TAZ as downstream pathways...
|
||||
|
||||
---
|
||||
|
||||
## Session 2026-05-06 14:00 — Deep Reading Discussion
|
||||
|
||||
**Agent:** /pf-deep
|
||||
**Started:** 2026-05-06 14:00:00
|
||||
|
||||
### 问题: Is the sample size adequate for the conclusions drawn?
|
||||
|
||||
**解答:** The study uses n=6 per group which is standard for rodent OA models...
|
||||
However, the power analysis was not reported — a limitation noted in the Discussion.
|
||||
|
||||
**来源:** Pass 3 analysis, Methods section
|
||||
```
|
||||
|
||||
**Key design decisions:**
|
||||
- **问题:/解答: format:** Natural for Chinese-speaking biomedical researchers. Minimally structured — easy to write, easy to scan.
|
||||
- **Session grouping:** Each `/pf-paper` or `/pf-deep` invocation creates a new session header. Prevents one giant undifferentiated blob.
|
||||
- **来源 field:** Traces each answer back to specific Pass or section — maintains PaperForge's provenance principle.
|
||||
- **Append-only:** New sessions append to the end. Never rewrites old sessions. Preserves history naturally.
|
||||
|
||||
**discussion.json** (structured, dashboard-consumable):
|
||||
|
||||
```json
|
||||
{
|
||||
"paper_key": "ABCDEFG",
|
||||
"schema_version": "1",
|
||||
"updated": "2026-05-06T14:45:00Z",
|
||||
"sessions": [
|
||||
{
|
||||
"session_id": "2026-05-06T10:30:00",
|
||||
"agent": "pf-paper",
|
||||
"started": "2026-05-06T10:30:00Z",
|
||||
"qa_pairs": [
|
||||
{
|
||||
"question": "What is the main finding of this paper?",
|
||||
"answer": "The paper demonstrates that Piezo1 mechanosensitive ion channels mediate...",
|
||||
"source": "Pass 1 overview, Figure 3",
|
||||
"timestamp": "2026-05-06T10:31:15Z"
|
||||
},
|
||||
{
|
||||
"question": "What signaling pathways are involved?",
|
||||
"answer": "The study identifies Ca²⁺/NFAT and YAP/TAZ as downstream pathways...",
|
||||
"source": null,
|
||||
"timestamp": "2026-05-06T10:33:42Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"session_id": "2026-05-06T14:00:00",
|
||||
"agent": "pf-deep",
|
||||
"started": "2026-05-06T14:00:00Z",
|
||||
"qa_pairs": [
|
||||
{
|
||||
"question": "Is the sample size adequate for the conclusions drawn?",
|
||||
"answer": "The study uses n=6 per group which is standard...",
|
||||
"source": "Pass 3 analysis, Methods section",
|
||||
"timestamp": "2026-05-06T14:05:30Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Key design decisions:**
|
||||
- **Minimal schema:** Only what the dashboard needs. Avoids duplicating the full markdown content.
|
||||
- **session_id = ISO timestamp:** Timestamps are unique enough to serve as IDs without a UUID dependency.
|
||||
- **qa_pairs array:** Flat, filterable, order-preserving.
|
||||
- **source field optional:** Not all Q&A has a clear source section; null means "general discussion."
|
||||
|
||||
### Recorder Integration Points
|
||||
|
||||
The recorder lives as a new Python module: `paperforge/worker/discussion_recorder.py`
|
||||
|
||||
**Entry points (two):**
|
||||
|
||||
1. **`/pf-paper` completion:** When the agent finishes a `/pf-paper` session, the orchestrator invokes `discussion_recorder.record_session(key, agent="pf-paper", qa_pairs=[...])`
|
||||
2. **`/pf-deep` completion:** Same pattern, with `agent="pf-deep"`
|
||||
|
||||
**Key constraint from architecture (PROJECT.md):**
|
||||
> "No auto-triggering of AI agents allowed — recording must be voluntary/user-initiated."
|
||||
|
||||
The recorder is invoked at the END of a user-initiated agent session, writing output to files. It does NOT trigger agents, watch for new prompts, or run in background.
|
||||
|
||||
**File operations:**
|
||||
- Read existing `discussion.md` / `discussion.json` if they exist
|
||||
- Append new session data
|
||||
- Write updated files back to `ai/` directory
|
||||
- All operations are idempotent: re-running the same session with the same session_id overwrites that session's entry, not duplicates it
|
||||
|
||||
## Feature Details: Deep-Reading Dashboard Mode
|
||||
|
||||
### Mode Detection
|
||||
|
||||
The plugin's `_detectAndSwitch()` adds a new check BEFORE the existing `.md` → `zotero_key` check:
|
||||
|
||||
```javascript
|
||||
// Pseudocode for deep-reading mode detection:
|
||||
if (ext === 'md') {
|
||||
// Check if this is a deep-reading.md in a paper workspace
|
||||
const parentDir = activeFile.parent?.name || '';
|
||||
const workspaceMatch = parentDir.match(/^([A-Z0-9]+) - /);
|
||||
const isDeepReading = activeFile.basename === 'deep-reading';
|
||||
|
||||
if (isDeepReading && workspaceMatch) {
|
||||
// deep-reading mode
|
||||
this._currentPaperKey = workspaceMatch[1]; // extract zotero_key
|
||||
this._switchMode('deep-reading');
|
||||
return;
|
||||
}
|
||||
|
||||
// Existing zotero_key check for per-paper mode
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Detection hierarchy (in order):**
|
||||
1. `.base` → collection mode (existing)
|
||||
2. `deep-reading.md` in `{KEY} - {Title}/` workspace → deep-reading mode (NEW)
|
||||
3. `.md` with `zotero_key` frontmatter → per-paper mode (existing)
|
||||
4. Everything else → global mode (existing)
|
||||
|
||||
### Dashboard Layout (Deep-Reading Mode)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ [DEEP READING] Paper Title (truncated) │ ← Mode badge + context
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 📊 Reading Progress │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ Pass 1: 概览 ✓ Complete │ │ ← Status bar
|
||||
│ │ Pass 2: 精读还原 ✓ Complete │ │
|
||||
│ │ Pass 3: 深度理解 ○ Pending │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 📝 Pass 1 Summary │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ One-liner from 一句话总览 field │ │ ← Snippet from deep-reading.md
|
||||
│ │ Category: Original Research │ │
|
||||
│ │ Context: Osteoarthritis models... │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 💬 Recent AI Discussions (2 sessions) │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ Q: What is the main finding? │ │ ← From discussion.json
|
||||
│ │ A: The paper demonstrates... [↗] │ │ (last 3 Q&A pairs)
|
||||
│ ├─────────────────────────────────────┤ │
|
||||
│ │ Q: Is the sample size adequate? │ │
|
||||
│ │ A: The study uses n=6... [↗] │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 🔗 Quick Links │
|
||||
│ [Open Fulltext] [Open Main Note] ... │ ← Contextual buttons
|
||||
│ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Section Details
|
||||
|
||||
#### 1. Reading Progress (Status Bar)
|
||||
|
||||
**What:** Three-line indicator showing Pass completion status.
|
||||
|
||||
**How:**
|
||||
- Parse `deep-reading.md` content for section markers:
|
||||
- Pass 1 complete if `### Pass 1: 概览` section has non-scaffold content (not empty, not just template text)
|
||||
- Pass 2 complete if `### Pass 2: 精读还原` + `#### Figure-by-Figure 解析` have filled content
|
||||
- Pass 3 complete if `### Pass 3: 深度理解` has filled content
|
||||
- Simple heuristic: if section has more than X non-empty lines after the heading, it's "complete"
|
||||
- Visual: ✓ (green) for complete, ○ (gray) for pending, ⚠ (yellow) for partial
|
||||
|
||||
**Why not just check deep_reading_status:** The canonical index's `deep_reading_status` is binary (done/pending). The dashboard needs granularity: a paper might have Pass 1 complete but Pass 2 in progress. Parsing the file directly avoids needing Python-side updates for this granular state.
|
||||
|
||||
#### 2. Pass 1 Summary
|
||||
|
||||
**What:** Quick overview extracted from the deep-reading.md.
|
||||
|
||||
**How:**
|
||||
- Extract the `**一句话总览**` content (the first non-empty line after that marker)
|
||||
- Extract the 5 Cs fields from the Pass 1 section
|
||||
- Show as a card with max 5 lines, "Read more →" link to full deep-reading.md
|
||||
|
||||
#### 3. AI Q&A History
|
||||
|
||||
**What:** Recent Q&A pairs from discussion.json.
|
||||
|
||||
**How:**
|
||||
- Read `ai/discussion.json` (same workspace directory)
|
||||
- Show last 3 Q&A pairs across all sessions (most recent first)
|
||||
- Each card shows: question (truncated to 80 chars) + answer preview (40 chars)
|
||||
- Click expands to full answer, or opens discussion.md for full context
|
||||
- Empty state: "No AI discussions yet. Run /pf-paper or /pf-deep to start."
|
||||
|
||||
#### 4. Quick Links
|
||||
|
||||
**What:** Navigation buttons to related files.
|
||||
|
||||
**How:**
|
||||
- "Open Fulltext" → opens `fulltext.md` in workspace (reuse existing _openFulltext)
|
||||
- "Open Main Note" → opens the main paper note in workspace
|
||||
- "Open Discussion" → opens `ai/discussion.md` (if exists)
|
||||
- These replace the per-paper mode's contextual buttons since the context is different
|
||||
|
||||
### What the Deep-Reading Dashboard is NOT
|
||||
|
||||
- **NOT a replacement for deep-reading.md:** The full note remains the authoritative source. The dashboard is a navigation/view layer.
|
||||
- **NOT a re-render of the per-paper dashboard:** The lifecycle stepper, health matrix, and maturity gauge belong to the per-paper card. Deep-reading mode is focused on the reading content itself.
|
||||
- **NOT a real-time monitor:** No polling for agent progress. Dashboards refresh on active-leaf-change (existing pattern).
|
||||
|
||||
## Feature Details: Jump-to-Deep-Reading Button
|
||||
|
||||
### Placement
|
||||
|
||||
On the per-paper dashboard card, in the contextual actions row (where "Copy Context" and "Open Fulltext" currently live).
|
||||
|
||||
### Behavior
|
||||
|
||||
1. **Visible condition:** Button appears when `entry.deep_reading_path` is non-empty in the canonical index (meaning deep-reading.md exists in the workspace).
|
||||
2. **Click action:** Opens `deep-reading.md` in Obsidian using `this.app.workspace.openLinkText()`. The dashboard auto-detects the new active file and switches to deep-reading mode.
|
||||
3. **Visual:** Similar to existing contextual buttons (`paperforge-contextual-btn` class), with an icon + text: "🔬 Deep Reading" (or a Unicode character that renders well).
|
||||
|
||||
### Implementation
|
||||
|
||||
```javascript
|
||||
// In _renderPaperMode(), after the existing contextual buttons:
|
||||
if (entry.deep_reading_path) {
|
||||
const drBtn = actionsRow.createEl('button', { cls: 'paperforge-contextual-btn' });
|
||||
drBtn.createEl('span', { cls: 'paperforge-contextual-btn-icon', text: '\uD83D\uDD2C' }); // 🔬
|
||||
drBtn.createEl('span', { text: 'Deep Reading' });
|
||||
drBtn.addEventListener('click', () => {
|
||||
this.app.workspace.openLinkText(entry.deep_reading_path, '', false);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Details: Bug Fixes
|
||||
|
||||
### Bug: Version Number Not Displaying
|
||||
|
||||
**Root cause:** The `_versionBadge` element is created in `_buildPanel()` but only updated in `_renderStats()`. The `_cachedStats.version` field is set to `'\u2014'` (em dash) on first load and never overwritten because the version field isn't populated from the canonical index.
|
||||
|
||||
**Fix:** Two changes:
|
||||
1. In `_fetchStats()`, after building `this._cachedStats`, add: `this._cachedStats.version = index.schema_version || index.version || '\u2014';`
|
||||
2. Alternatively, read the version from `paperforge/__init__.py` via Python subprocess, or from `paperforge/plugin/manifest.json` plugin version. The canonical index should carry a version field.
|
||||
|
||||
**Simplest fix:** The plugin manifest already has `"version": "1.4.15"`. Read it at plugin init and supply to the badge:
|
||||
|
||||
```javascript
|
||||
// In onOpen() or _buildPanel():
|
||||
const manifest = this.app.plugins.plugins['paperforge']?.manifest;
|
||||
if (manifest?.version) {
|
||||
this._versionBadge.setText('v' + manifest.version);
|
||||
}
|
||||
```
|
||||
|
||||
### Bug: Meaningless "ai" Row
|
||||
|
||||
**Root cause:** From the v1.6 workspace migration era, the per-paper dashboard or global dashboard may have a row/label displaying "ai" — likely a leftover from when `ai/` directory creation was tracked as a health dimension.
|
||||
|
||||
**Fix:** Identify the source. Likely in the `_renderHealthMatrix()` call or `_renderNextStepCard()` where a stale field like `health.ai_health` is being rendered. Remove any reference to an "ai" health dimension from:
|
||||
1. `paperforge/worker/asset_index.py` — `compute_health()` function
|
||||
2. `paperforge/plugin/main.js` — `_renderHealthMatrix()` dimensions array
|
||||
3. `paperforge/plugin/main.js` — `_fetchStats()` health aggregation
|
||||
|
||||
## Competitor / Ecosystem Analysis
|
||||
|
||||
| Tool | Discussion Recording | Dashboard Integration | Notes |
|
||||
|------|---------------------|-----------------------|-------|
|
||||
| **Obsidian Smart Chat** | Saves thread URLs + status inline in notes; markdown codeblocks | Dataview dashboards from `chat-active`/`chat-done` fields | Closest pattern: inline recording + dashboard querying. PaperForge differs by having dedicated per-paper workspace + structured Q&A format |
|
||||
| **Obsidian Copilot** | Saves conversations as `.md` with YAML frontmatter; project-aware isolation | Chat history popover with fuzzy search, time grouping | Strong inspiration for session grouping and naming conventions |
|
||||
| **Gemini Scribe** | Per-note history file: `[Note] - Gemini History.md`; auto-appending | History files linked to source notes; graph integration | One-file-per-note model — simple, but doesn't support multi-session grouping well |
|
||||
| **Claude Sessions** | Reads Claude Code JSONL logs; renders interactive timeline | Summary dashboard with hero cards, token charts, tool charts; Base dashboards | Over-engineered for PaperForge's needs (full timeline rendering), but the "summary dashboard + Bases" pattern is relevant |
|
||||
| **Smart2Brain** | Save chats; continue later | RAG-powered Q&A with vault note references | Focuses on knowledge retrieval, not discussion recording |
|
||||
| **Omi Conversations** | Auto-sync from Omi device; daily-indexed conversations | Hub dashboard with tabs (tasks, conversations, memories, stats, map) | The folder-per-day + index structure is clean. PaperForge's session-per-invocation is simpler and sufficient |
|
||||
|
||||
**Key insight from ecosystem:** The Obsidian plugin ecosystem overwhelmingly favors **file-based recording** (markdown in the vault) over database-backed storage. PaperForge's `discussion.md` + `discussion.json` dual-format approach fits this pattern while adding dashboard-queryable structure that pure markdown lacks.
|
||||
|
||||
## Architecture Alignment
|
||||
|
||||
All new features must respect existing architecture principles from `.planning/research/ARCHITECTURE.md`:
|
||||
|
||||
1. **Thin-shell plugin:** The plugin reads JSON (canonical index + discussion.json), never recomputes lifecycle or health. The deep-reading mode parsing (Pass status) is a read-only display concern — not business logic duplication.
|
||||
2. **Python-owned truth:** Discussion recording logic lives in `paperforge/worker/discussion_recorder.py`. The plugin only reads the output files.
|
||||
3. **No new canonical index fields needed:** The deep-reading dashboard reads `deep_reading.md` content directly and `discussion.json` from the workspace. No changes to `formal-library.json` required (though a `discussion_count` field could be added later for the per-paper card).
|
||||
4. **Mode switching extends existing pattern:** Deep-reading mode is a fourth mode (`deep-reading`) alongside global, paper, and collection. It uses the same `_switchMode()`, `_renderModeHeader()`, and `_refreshCurrentMode()` infrastructure.
|
||||
9. **L5 journey tests** — high effort; most value after core pipeline is proven
|
||||
10. **L6 chaos tests** — valuable but can be added incrementally post-launch
|
||||
|
||||
## Sources
|
||||
|
||||
- **PaperForge internal code inspection:** `paperforge/plugin/main.js` — mode switching architecture (global/paper/collection), per-paper dashboard rendering, contextual buttons, event subscriptions — HIGH confidence
|
||||
- **PaperForge internal code inspection:** `paperforge/worker/sync.py` — workspace migration creating `ai/` directory (lines 1680-1749) — HIGH confidence
|
||||
- **PaperForge internal code inspection:** `paperforge/worker/asset_index.py` — canonical index `_build_entry()` creating workspace paths including `ai_path`, `deep_reading_path` — HIGH confidence
|
||||
- **PaperForge internal code inspection:** `paperforge/skills/literature-qa/scripts/ld_deep.py` — deep-reading scaffold structure, Pass 1/2/3 markers, figure block format — HIGH confidence
|
||||
- **Obsidian Smart Chat documentation:** Chat thread linking within notes, Dataview dashboards, chat-active/chat-done tracking — https://smartconnections.app/smart-chat/ — HIGH confidence (official plugin docs)
|
||||
- **Obsidian Copilot (DeepWiki):** Chat persistence and history system — markdown files with YAML frontmatter, session grouping, recent usage tracking — https://deepwiki.com/logancyang/obsidian-copilot/8-chat-persistence-and-history — HIGH confidence (documented architecture)
|
||||
- **Gemini Scribe chat history guide:** Per-note history file pattern, auto-appending, markdown formatting — https://github.com/allenhutchison/obsidian-gemini — MEDIUM confidence (community plugin docs)
|
||||
- **Claude Sessions plugin:** Session timeline rendering, summary dashboard with hero cards, Obsidian Bases dashboards — https://github.com/gapmiss/claude-sessions — HIGH confidence (well-documented community plugin)
|
||||
- **PaulGP llms.txt proposal:** Discussion of AI-readable paper annotations, limitations-first orientation — https://paulgp.com/2026/03/10/llms-txt-for-academic-papers.html — MEDIUM confidence (academic blog post, design philosophy reference)
|
||||
- **Effortless Academic discussion writing guide:** Q&A-style paper discussion structure — MEDIUM confidence (practitioner guide)
|
||||
|
||||
---
|
||||
|
||||
*Feature research for: v1.8 AI Discussion Recording & Deep-Reading Dashboard*
|
||||
*Researched: 2026-05-06*
|
||||
- Existing codebase analysis: all 30 existing test files audited for behavioral correctness
|
||||
- pytest best practices (verified via Exa search): pytest-with-eric.com, orchestrator.dev
|
||||
- GitHub Actions matrix patterns (verified via official docs): docs.github.com/en/actions/guides/building-and-testing-python
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,218 +1,81 @@
|
|||
# Stack Research — v1.8 AI Discussion Recording & Deep-Reading Dashboard
|
||||
# Technology Stack: v2.0 Testing Infrastructure
|
||||
|
||||
**Domain:** AI discussion recording + deep-reading dashboard mode for PaperForge Obsidian plugin
|
||||
**Researched:** 2026-05-06
|
||||
**Confidence:** HIGH
|
||||
|
||||
## Executive Summary
|
||||
|
||||
v1.8 adds two new capabilities to the existing PaperForge stack: (1) AI discussion recording that captures `/pf-paper` and agent chat interactions into structured `ai/` records, and (2) a 4th dashboard mode activated when the user views `deep-reading.md`. Both features extend the existing thin-shell plugin architecture — the plugin reads JSON from the filesystem and renders Pure CSS/DOM components; the Python side provides templates and optional recording helpers. **No new npm dependencies, no new Python packages.** All additions are new modules/files within the existing architecture.
|
||||
**Project:** PaperForge v2.0
|
||||
**Researched:** 2026-05-08
|
||||
|
||||
## Recommended Stack
|
||||
|
||||
### Core Technologies (unchanged from v1.6–v1.7)
|
||||
|
||||
### Core Test Framework
|
||||
| Technology | Version | Purpose | Why |
|
||||
|------------|---------|---------|-----|
|
||||
| Obsidian CommonJS plugin | existing (`main.js`) | Dashboard/settings/commands UI | Pure Obsidian API — no bundler, no npm deps, no build step |
|
||||
| Python 3.10+ | existing | Single owner of business logic, templates, schema | Already owns config, lifecycle, health, maturity, index; extends naturally to discussion templates |
|
||||
| Filesystem JSON | UTF-8, no schema library needed | `discussion.json` per paper | Plugin reads via `fs.readFileSync`, localStorage-free, vault-native, traceable |
|
||||
| Filesystem Markdown | Obsidian-flavored | `discussion.md` per paper | Human-readable, Obsidian-editable, wikilink-compatible |
|
||||
| pytest | >=8.0 | Python test framework | Already in use; industry standard for Python |
|
||||
| pytest-snapshot | >=0.9.0 | Snapshot/approval testing | CLI JSON output stability contracts; simpler than full approval-testing libs |
|
||||
| pytest-timeout | >=2.2.0 | Test timeout guards | Prevent runaway E2E/chaos tests from blocking CI |
|
||||
| pytest-mock | >=3.12.0 | Enhanced mocking | Built-in monkeypatch replacement; integrates with `unittest.mock` |
|
||||
| responses | >=0.25.0 | HTTP mock library | Intercept `requests` library calls at HTTP layer for mock OCR backend |
|
||||
| coverage | >=7.4.0 | Coverage measurement | Standard Python coverage tool; generates CI reports |
|
||||
|
||||
### File Format Additions (NEW for v1.8)
|
||||
### Plugin Test Framework
|
||||
| Technology | Version | Purpose | Why |
|
||||
|------------|---------|---------|-----|
|
||||
| Vitest | >=2.0 | JS test runner | Native ESM support (plugin uses `require` but can be wrapped); ~2x faster than Jest |
|
||||
| obsidian-test-mocks | >=0.12 | Obsidian API mocks | Comprehensive mock implementations of `obsidian.d.ts`; 100% code coverage maintained |
|
||||
| jsdom | >=24.0 | DOM environment | Required by Obsidian plugin tests (document, window access) |
|
||||
| Node.js | >=20.0 | JS runtime | Required by Vitest and obsidian-test-mocks |
|
||||
|
||||
#### 1. `discussion.json` — Structured AI Q&A Record
|
||||
### CI Infrastructure
|
||||
| Technology | Version | Purpose | Why |
|
||||
|------------|---------|---------|-----|
|
||||
| actions/setup-python | v5 | Python version management | Standard GitHub Action; supports cache, pre-release |
|
||||
| actions/setup-node | v4 | Node version management | Required for plugin tests |
|
||||
| actions/checkout | v4 | Source checkout | Latest stable checkout action |
|
||||
| re-actors/alls-green | v1 | CI gate aggregation | Simplifies "all jobs passed" check with branch protection |
|
||||
|
||||
**Location:** `<paper_workspace>/ai/discussion.json`
|
||||
### Supporting Scripts
|
||||
| Script | Language | Purpose |
|
||||
|--------|----------|---------|
|
||||
| `scripts/check_version_sync.py` | Python | Level 0: verify version consistency across 6+ files |
|
||||
| `scripts/run_all_tests.sh` | Bash | Sequential runner for local testing |
|
||||
| `scripts/run_chaos_tests.sh` | Bash | Chaos test runner with scenario selection |
|
||||
| `scripts/generate_fixtures.py` | Python | Regenerate golden datasets from canonical sources |
|
||||
|
||||
**Schema (v1):**
|
||||
## Alternatives Considered
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "1",
|
||||
"paper_key": "ABCDEFG",
|
||||
"generated_at": "2026-05-06T12:00:00+08:00",
|
||||
"source": "/pf-paper", // or "/pf-deep", "agent-chat"
|
||||
"history": [
|
||||
{
|
||||
"index": 1,
|
||||
"timestamp": "2026-05-06T12:00:00+08:00",
|
||||
"question": "这篇论文的主要发现是什么?",
|
||||
"answer": "该研究发现...",
|
||||
"tags": ["background", "results"],
|
||||
"agent_model": "deepseek-v4-pro"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"total_qa": 5,
|
||||
"last_updated": "2026-05-06T12:30:00+08:00",
|
||||
"top_tags": ["methods", "results"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why this shape:**
|
||||
- `schema_version` for forward compatibility
|
||||
- `history[]` is a flat append-only list (no nested threads — keeps dashboard rendering simple)
|
||||
- `index` field enables stable references even if history is reordered
|
||||
- `tags` enable future dashboard filtering without schema change
|
||||
- `summary` provides pre-computed metadata the plugin can read with a single parse (avoids looping through all history entries for total count)
|
||||
|
||||
**Integration:** Plugin reads this file directly via `fs.readFileSync` when rendering the deep-reading dashboard. No Python intermediary needed for read path.
|
||||
|
||||
#### 2. `discussion.md` — Human-Readable Q&A Log
|
||||
|
||||
**Location:** `<paper_workspace>/ai/discussion.md`
|
||||
|
||||
**Template format:**
|
||||
|
||||
```markdown
|
||||
# AI 讨论记录 — 文献标题
|
||||
|
||||
> **来源:** /pf-paper | **生成时间:** 2026-05-06 12:00 | **模型:** deepseek-v4-pro
|
||||
|
||||
---
|
||||
|
||||
## 讨论 1
|
||||
|
||||
**问题:** 这篇论文的主要发现是什么?
|
||||
|
||||
**解答:** 该研究发现...
|
||||
|
||||
*标签: `background`, `results`*
|
||||
|
||||
---
|
||||
|
||||
## 讨论 2
|
||||
|
||||
**问题:** 研究者用了什么统计方法?
|
||||
|
||||
**解答:** ...
|
||||
|
||||
*标签: `methods`*
|
||||
```
|
||||
|
||||
**Why this format:**
|
||||
- Each discussion is a `## 讨论 N` section — natural Obsidian heading hierarchy
|
||||
- `**问题:**` and `**解答:**` bold markers are scannable in both Reading and Source mode
|
||||
- Horizontal rules (`---`) visually separate discussions
|
||||
- Tags as inline code spans — searchable in Obsidian's global search
|
||||
- Frontmatter-compatible metadata line at top
|
||||
|
||||
### Plugin JS Additions (NEW for v1.8 — no new npm deps)
|
||||
|
||||
All additions stay within the single `paperforge/plugin/main.js` file and `paperforge/plugin/styles.css`.
|
||||
|
||||
| Module/Function | Type | Purpose |
|
||||
|---------|------|---------|
|
||||
| `_detectAndSwitch()` extension | Mode detection logic | Add `deep-reading` as 4th mode: detect when active file path ends with `deep-reading.md` and resolve paper context from workspace path |
|
||||
| `_renderDeepReadingMode()` | New render function | Renders deep-reading dashboard: status bar, Pass 1 summary, AI discussion history |
|
||||
| `_renderDiscussionHistory()` | New render function | Reads `discussion.json` from paper `ai/` directory and renders Q&A cards |
|
||||
| `_readDiscussionJson(key)` | New utility | Resolves `ai_path` → reads `discussion.json` → returns parsed object or null |
|
||||
| `_renderPass1Summary()` | New render function | Renders Pass 1 overview extracted from deep-reading.md content |
|
||||
| `_renderPaperMode()` extension | Existing function | Add "Jump to Deep Reading" contextual button when `deep_reading_path` exists |
|
||||
| `_versionBadge` fix | Bug fix | Restore version display by reading from `paperforge/__init__.py` `__version__` or `manifest.json` |
|
||||
| "ai" row removal | Bug fix | Remove the meaningless "ai" UI row (track down which render path creates it) |
|
||||
|
||||
**Mode detection logic (how `_detectAndSwitch()` grows):**
|
||||
|
||||
```javascript
|
||||
// Existing: checks for .base, .md with zotero_key
|
||||
// NEW: check if active file basename is "deep-reading.md"
|
||||
if (ext === 'md' && activeFile.basename === 'deep-reading') {
|
||||
// Resolve paper key from parent directory name
|
||||
const parentDir = activeFile.parent.path; // e.g., "Literature/骨科/ABC12345 - Title"
|
||||
const match = parentDir.match(/([A-Z0-9]{8})/);
|
||||
if (match) {
|
||||
this._currentPaperKey = match[1];
|
||||
this._currentPaperEntry = this._findEntry(match[1]);
|
||||
this._switchMode('deep-reading');
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CSS Additions (NEW for v1.8)
|
||||
|
||||
All additions go into the existing `paperforge/plugin/styles.css` file after Section 17.
|
||||
|
||||
| CSS Class | Purpose |
|
||||
|-----------|---------|
|
||||
| `.paperforge-deep-reading-view` | Root container for deep-reading mode layout (flex column, gap: 20px) |
|
||||
| `.paperforge-dr-status-bar` | Status indicator bar: shows Pass 1/2/3 completion, OCR status badge, last-updated timestamp |
|
||||
| `.paperforge-dr-pass-summary` | Pass 1 summary card: bordered card with muted background for the overview text |
|
||||
| `.paperforge-dr-discussion-card` | Individual Q&A card: question in bold, answer below, tag chips, timestamp |
|
||||
| `.paperforge-dr-discussion-list` | Scrollable container for stacked discussion cards |
|
||||
| `.paperforge-dr-tag-chip` | Small pill for tags (font-size: 10px, border-radius: 8px) |
|
||||
| `.paperforge-dr-empty` | Empty state: "No AI discussions recorded yet" with muted styling |
|
||||
| `.paperforge-dr-section-title` | Section header within deep-reading view (uppercase, letter-spaced) |
|
||||
|
||||
### Python Additions (NEW for v1.8 — no new dependencies)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `paperforge/worker/discussion.py` | NEW module: `record_discussion(key, vault, question, answer, source, model)` — writes both `discussion.md` and `discussion.json` to paper `ai/` directory |
|
||||
| `paperforge/worker/discussion.py` | `load_discussion_json(key, vault)` → dict — reads existing discussion.json, returns parsed dict |
|
||||
| `paperforge/worker/discussion.py` | `append_discussion(key, vault, qa_pair)` — appends to existing discussion history (read-modify-write atomic via tempfile + os.replace) |
|
||||
|
||||
**Why a separate module:**
|
||||
- Discussion recording crosses the Worker/Agent boundary (Agent generates content, Worker writes files)
|
||||
- Keeps discussion I/O isolated from OCR, sync, and index logic
|
||||
- Enables future `/pf-discuss` command or discussion-search features without refactoring
|
||||
|
||||
**No new Python packages required.** All I/O uses `json`, `pathlib`, and `datetime` (stdlib). Atomic write uses the same `tempfile` + `os.replace` pattern already proven in `asset_index.py`.
|
||||
|
||||
### Integration Points (with existing canonical index)
|
||||
|
||||
| Integration | Direction | How |
|
||||
|-------------|-----------|-----|
|
||||
| `ai_path` in canonical index | Read by plugin | Already exists: `entry.ai_path` = `"Literature/<domain>/<key> - <Title>/ai/"` |
|
||||
| Resolve `discussion.json` path | Plugin computed | `path.join(vaultPath, entry.ai_path, 'discussion.json')` |
|
||||
| Deep-reading mode detection | Plugin computed | Check `activeFile.basename === 'deep-reading'` + resolve key from parent directory |
|
||||
| Jump-to-deep-reading button visibility | Plugin check | `entry.deep_reading_path` non-empty → show button |
|
||||
| Discussion recording write path | Python writes | `record_discussion()` resolves paths via `paperforge_paths()` → writes to workspace ai/ |
|
||||
|
||||
## What NOT to Add
|
||||
|
||||
| Category | Avoid | Why | Use Instead |
|
||||
|----------|-------|-----|-------------|
|
||||
| JS dependencies | Any npm package (React, Vue, chart libs, date-fns) | Plugin must stay pure Obsidian CommonJS; adding a build chain breaks the single-file deployment model and Obsidian Community Plugin requirements | Pure DOM API (`createEl`, `addClass`, `setText`) |
|
||||
| Database for discussions | SQLite, IndexedDB, localStorage | Discussions belong to the paper workspace — they must be vault-native, backup-friendly, and git-trackable | Filesystem JSON + Markdown in `ai/` |
|
||||
| Python dependencies for discussion | `pydantic`, `jsonschema` | Overkill for a flat Q&A list; adds import cost and version coupling for a simple structure | Plain `dict` with documented keys, validated by `assert` in tests |
|
||||
| Second plugin file | Splitting `main.js` into modules | Adds complexity without benefit at ~2100 lines; Obsidian plugin import resolution is fragile | Inline functions in `main.js` (as all existing render functions are) |
|
||||
| Schema library in plugin | AJV, Zod, or custom JSON validator | Plugin should read, not validate; schema enforcement lives in Python tests | Trust but handle: `try/catch` around `JSON.parse`, fallback to empty state |
|
||||
| New CLI command (v1.8) | `paperforge discuss` or `paperforge record` | Discussion recording happens inside Agent sessions; adding a CLI command would require users to manually run it after each Q&A | Python module callable from Agent scripts (`/pf-paper` → calls `record_discussion()` internally) |
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
| Component | Current Version | Notes |
|
||||
|-----------|----------------|-------|
|
||||
| PaperForge Python | 1.4.15 (→ 1.8.0) | No breaking changes to existing CLI or index schema |
|
||||
| Canonical index schema | v2 (`formal-library.json`) | `ai_path` field already present; no schema version bump needed |
|
||||
| Obsidian API | 1.5+ | Existing API surface used (ItemView, metadataCache, vault.adapter) |
|
||||
| Node.js (for Obsidian) | 18+ (Electron embedded) | `fs.readFileSync`, `path.join` — all stdlib since Node 0.x |
|
||||
| Category | Recommended | Alternative | Why Not |
|
||||
|----------|-------------|-------------|---------|
|
||||
| HTTP Mock | responses | pytest-httpx | responses is simpler and sufficient for `requests`-based OCR calls; httpx adds unnecessary dependency |
|
||||
| Plugin Test Mock | obsidian-test-mocks | Manual `__mocks__/obsidian.ts` | Manual obsidian mocking requires constant maintenance as API evolves; obsidian-test-mocks is community-maintained with 100% coverage |
|
||||
| JS Test Runner | Vitest | Jest | Jest requires transforms for ESM; Vitest is natively ESM-compatible and faster for watch mode |
|
||||
| Snapshot Testing | pytest-snapshot | syrupy | pytest-snapshot is simpler; syrupy's extra features (serializer plugins) not needed for JSON-only snapshots |
|
||||
| CI Orchestration | Native GitHub Actions | tox | tox adds complexity for simple package install; actions/setup-python + pip install is more transparent and debuggable |
|
||||
| Obsidian E2E Testing | Deferred to v2.1 | obsidian-e2e / obsidian-integration-testing | Both require a running Obsidian process, add 3+ min per test, and are flaky in CI. Not worth the cost for current milestone |
|
||||
|
||||
## Installation
|
||||
|
||||
No new installation steps. v1.8 is additive:
|
||||
```toml
|
||||
# pyproject.toml additions
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-snapshot>=0.9.0",
|
||||
"pytest-timeout>=2.2.0",
|
||||
"responses>=0.25.0",
|
||||
"pytest-mock>=3.12.0",
|
||||
"coverage>=7.4.0",
|
||||
"ruff>=0.4.0",
|
||||
]
|
||||
```
|
||||
|
||||
```bash
|
||||
# No new pip packages needed
|
||||
# No new npm packages needed
|
||||
|
||||
# Plugin update: replace main.js + styles.css + manifest.json + versions.json
|
||||
# Python update: add paperforge/worker/discussion.py
|
||||
# Plugin test dependencies (separate package.json in tests/plugin/)
|
||||
npm install --save-dev vitest obsidian-test-mocks jsdom
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Existing codebase analysis: `paperforge/plugin/main.js` (2067 lines), `paperforge/plugin/styles.css` (1325 lines), `paperforge/worker/asset_index.py` (577 lines), `paperforge/worker/asset_state.py` (243 lines), `paperforge/worker/sync.py` (1829 lines) — HIGH confidence
|
||||
- `.planning/PROJECT.md` — v1.8 milestone definition, ai/ directory already created per paper — HIGH confidence
|
||||
- AGENTS.md — Lite architecture, Worker/Agent split, plugin thin-shell constraint — HIGH confidence
|
||||
- `.planning/research/STACK.md` (v1.6) — existing stack decisions, Pydantic version, filelock version, "what NOT to add" — HIGH confidence
|
||||
- Actual test fixtures: `tests/test_asset_state.py` — ai_path field verified in lifecycle/health computation — HIGH confidence
|
||||
|
||||
---
|
||||
|
||||
*Stack research for: PaperForge v1.8 AI Discussion Recording & Deep-Reading Dashboard*
|
||||
*Researched: 2026-05-06*
|
||||
- pytest documentation: https://pytest.org/ (HIGH confidence — official docs)
|
||||
- pytest-snapshot: https://pypi.org/project/pytest-snapshot/ (MEDIUM confidence — verified via search)
|
||||
- obsidian-test-mocks: https://www.npmjs.com/package/obsidian-test-mocks (MEDIUM confidence — verified via Exa search)
|
||||
- Vitest: https://vitest.dev/ (HIGH confidence — official docs)
|
||||
- GitHub Actions setup-python: https://github.com/actions/setup-python (HIGH confidence — official docs)
|
||||
- responses library: https://pypi.org/project/responses/ (HIGH confidence — official docs)
|
||||
|
|
|
|||
|
|
@ -1,353 +1,292 @@
|
|||
# Project Research Summary
|
||||
|
||||
**Project:** PaperForge v1.8 — AI Discussion Recording & Deep-Reading Dashboard
|
||||
**Domain:** Brownfield Obsidian plugin — extending mode-based dashboard with AI discussion capture
|
||||
**Researched:** 2026-05-06
|
||||
**Project:** PaperForge v2.0 — Testing Infrastructure (6-Layer Quality Gates)
|
||||
**Domain:** Brownfield hybrid Python + Obsidian plugin project — multi-layer quality gate testing
|
||||
**Researched:** 2026-05-08
|
||||
**Confidence:** HIGH
|
||||
|
||||
## Executive Summary
|
||||
|
||||
PaperForge v1.8 adds two tightly-coupled capabilities to the existing PaperForge Obsidian plugin: **(1) AI discussion recording** that captures `/pf-paper` and `/pf-deep` conversations into structured `ai/discussion.json` and human-readable `ai/discussion.md` files within each paper's workspace directory; and **(2) a 4th dashboard mode** (`deep-reading`) activated automatically when the user opens a `deep-reading.md` file, showing Pass 1/2/3 completion status, Pass 1 summary, and recent AI Q&A history.
|
||||
PaperForge is a **brownfield hybrid Python + Obsidian plugin** project that manages literature assets (Zotero exports, PDFs, OCR fulltext, figures, notes, and AI outputs) as a local-first research library. The current milestone (v2.0) adds a **6-layer quality gate testing infrastructure** — version consistency, Python unit tests, CLI contract tests, plugin-backend integration, temp vault E2E workflows, user journey contracts, and destructive scenarios — with CI matrix, golden datasets, and snapshot testing. The product has 473+ existing tests that need restructuring into a test pyramid, and the testing infrastructure itself is the deliverable.
|
||||
|
||||
The recommended approach is **thin-shell extension** — zero new dependencies, zero new CLI commands. The Obsidian plugin (CommonJS, single `main.js` file) gains one new mode renderer and one mode detection branch. A new Python module (`paperforge/worker/discussion.py`) handles writing discussion files at the end of Agent sessions. The canonical index (`formal-library.json`) serves as the bridge — JS reads pre-computed lifecycle/health/maturity from it, while `discussion.json` (vault-internal) should use `vault.adapter.read()` for cache consistency, **not** `fs.readFileSync()` as the older index pattern does.
|
||||
The recommended approach is a **modified testing diamond** with strict layer dependency ordering (L0 -> L1 -> L2/L3 in parallel -> L4 -> L5), a **plasma CI matrix** (full 3x3 only for unit tests, narrow configs for higher layers), **hierarchical pytest fixtures** (5 levels from empty_vault to full_test_vault), and **shape-specific snapshot assertions** (not whole-file JSON). The build order is: fixtures first, then Python layers (L0-L2), then JS layers (L3), then expensive integration layers (L4-L6).
|
||||
|
||||
The three key risks are: (1) mode detection ordering — `deep-reading.md` carries the same `zotero_key` frontmatter as the formal note and will incorrectly trigger per-paper mode unless the `deep-reading.md` filename check comes *first*; (2) encoding corruption on Windows with CJK locales — Chinese Q&A content written by Python must use explicit `encoding='utf-8'` and Node.js must read with matching encoding to avoid mojibake; and (3) `discussion.json` must include `schema_version` from day one to prevent silent data loss on format changes. All three are preventable with the right discipline in Phase 1 and Phase 2.
|
||||
**Key risks and mitigations:**
|
||||
1. **Mock drift** — mocks that silently diverge from real APIs. Mitigation: capture real PaddleOCR responses before mocking; use `autospec=True` on all patches; limit patch nesting to 3 levels.
|
||||
2. **CI combinatorial explosion** — trying to run everything on every platform. Mitigation: plasma CI matrix with path-filtered jobs; L1 on 3x3; L2-L5 on 1-2 configs; slow tests on nightly only.
|
||||
3. **Snapshot brittleness** — whole-file snapshot assertions that break on every refactor. Mitigation: assert specific shapes with normalized dynamic fields; use `dirty-equals` for timestamps/UUIDs.
|
||||
4. **Windows path hell** — junctions, symlinks, temp dir differences. Mitigation: at least one Windows CI node; platform-specific tests with `@pytest.mark.skipif`; path normalization utilities.
|
||||
5. **Fixture bloat** — 100MB+ fixture directories in git. Mitigation: generate fixtures from code; track with manifest; keep large fixtures outside git repo.
|
||||
|
||||
## Key Findings
|
||||
|
||||
### Recommended Stack
|
||||
|
||||
v1.8 is purely additive to the existing v1.6–v1.7 stack. **No new npm packages, no new Python packages.** The plugin remains a single CommonJS file (`main.js`, currently ~2067 lines) with CSS in `styles.css` (~1325 lines). Python gains one new module (`discussion.py`) using only stdlib (`json`, `pathlib`, `datetime`, `tempfile`, `os`).
|
||||
The testing stack splits cleanly into Python test infrastructure and JavaScript plugin test infrastructure, plus CI orchestration.
|
||||
|
||||
**Core technologies (unchanged):**
|
||||
- **Obsidian CommonJS plugin** (`main.js`, no bundler): Dashboard/settings/commands UI — pure Obsidian API, no build step
|
||||
- **Python 3.10+** (existing CLI): Single owner of business logic, templates, schema; extends naturally to discussion recording
|
||||
- **Filesystem JSON** (`formal-library.json`, `discussion.json`): Contract between Python and JS; vault-native, git-trackable, backup-friendly
|
||||
- **Filesystem Markdown** (`discussion.md`): Human-readable companion, Obsidian-editable, wikilink-compatible
|
||||
**Core test framework (Python):**
|
||||
|
||||
**New file formats for v1.8:**
|
||||
- `ai/discussion.json` — structured AI Q&A record with `schema_version`, `paper_key`, `sessions[]` array containing `session_id`, `agent`, `started`, and `qa_pairs[]`
|
||||
- `ai/discussion.md` — human-readable Q&A log with session-grouped `##` headings, `**问题:**`/`**解答:**` markers, timestamp metadata
|
||||
| Technology | Version | Purpose | Rationale |
|
||||
|------------|---------|---------|-----------|
|
||||
| pytest | >=8.0 | Python test framework | Already in use; industry standard |
|
||||
| pytest-snapshot | >=0.9.0 | Snapshot/approval testing | CLI JSON output stability contracts |
|
||||
| pytest-timeout | >=2.2.0 | Test timeout guards | Prevent runaway E2E/chaos tests |
|
||||
| pytest-mock | >=3.12.0 | Enhanced mocking | Better than raw monkeypatch |
|
||||
| responses | >=0.25.0 | HTTP mock library | Intercept `requests` at HTTP layer for mock OCR |
|
||||
| coverage | >=7.4.0 | Coverage measurement | Standard Python tool; CI reports |
|
||||
| ruff | >=0.4.0 | Linting | Already in use via pre-commit hooks |
|
||||
|
||||
**Key "what NOT to add" decisions (all researchers agree):**
|
||||
- No npm packages (React, Vue, chart libs) — breaks single-file deployment
|
||||
- No database (SQLite, IndexedDB) — discussions belong to the paper workspace, must be vault-native
|
||||
- No Python schema libraries (Pydantic, jsonschema) — overkill for flat Q&A list
|
||||
- No second plugin file — import resolution is fragile in Obsidian
|
||||
- No new CLI command (`paperforge discuss`) — recording happens inside Agent sessions
|
||||
**Plugin test framework (JavaScript):**
|
||||
|
||||
| Technology | Version | Purpose | Rationale |
|
||||
|------------|---------|---------|-----------|
|
||||
| Vitest | >=2.0 | JS test runner | Native ESM; 2x faster than Jest; better `obsidian-test-mocks` integration |
|
||||
| obsidian-test-mocks | >=0.12 | Obsidian API mocks | Community-maintained; 100% code coverage |
|
||||
| jsdom | >=24.0 | DOM environment | Required by plugin tests (document/window access) |
|
||||
| Node.js | >=20.0 | JS runtime | Required by Vitest and obsidian-test-mocks |
|
||||
|
||||
**CI Infrastructure:**
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|------------|---------|---------|
|
||||
| actions/setup-python | v5 | Python version management with cache |
|
||||
| actions/setup-node | v4 | Node.js version management |
|
||||
| actions/checkout | v4 | Source checkout |
|
||||
| re-actors/alls-green | v1 | CI gate aggregation (all-jobs-passed check) |
|
||||
|
||||
**Alternatives considered and rejected:**
|
||||
- **Jest** rejected over Vitest (ESM compatibility, speed)
|
||||
- **tox** rejected for CI (superfluous complexity; raw pip install is more transparent)
|
||||
- **syrupy** rejected over pytest-snapshot (extra features not needed for JSON-only snapshots)
|
||||
- **Real Obsidian E2E** rejected as too expensive (3+ min per test, flaky) — deferred to v2.1
|
||||
|
||||
### Expected Features
|
||||
|
||||
**Must have (table stakes — v1.8 launch):**
|
||||
- AI Discussion Recorder: Writes `discussion.md` + `discussion.json` when `/pf-paper` or `/pf-deep` completes — researchers expect past AI conversations next to the paper, not scattered in browser tabs
|
||||
- Deep-Reading Dashboard Mode: Plugin detects `deep-reading.md` as active file, switches to dedicated mode showing Pass 1/2/3 status + AI Q&A history — users want at-a-glance reading progress without scrolling
|
||||
- Jump-to-Deep-Reading Button: Contextual button on per-paper dashboard card that opens deep-reading.md — bridges paper lifecycle view to reading content
|
||||
- Version Number Display Fix: Restore the `_versionBadge` — users need to confirm version for issue reporting
|
||||
- "ai" UI Row Removal: Remove the meaningless row — erodes trust in dashboard accuracy
|
||||
**Must have (table stakes):**
|
||||
- **L0: Version consistency gate** — prevents deployment with mismatched versions across 6+ files. Low complexity. Single Python script.
|
||||
- **L1: Python unit tests** — existing 470+ tests relocated to `tests/unit/`. Low complexity. Fast isolation.
|
||||
- **L2: CLI contract tests** — subprocess invoker + snapshot assertions on `--json` output. Medium complexity. Protects plugin<->Python boundary.
|
||||
- **L3: Plugin unit tests** — Vitest + obsidian-test-mocks. Medium complexity. Catches JS logic errors before manual QA.
|
||||
- **L4: Temp vault E2E tests** — full pipeline (sync -> OCR -> status -> repair) in disposable vault. High complexity. 3 mock systems.
|
||||
- **Golden datasets** — centralized `fixtures/` with zotero/, pdf/, ocr/, snapshots/. Medium complexity. Prerequisite for all higher layers.
|
||||
- **Mock OCR backend** — intercepts PaddleOCR at HTTP layer with `responses`. Medium complexity. Enables deterministic testing.
|
||||
- **Mock Zotero data source** — static JSON fixtures in 3 BBT path formats. Low complexity.
|
||||
- **Mock vault filesystem** — `tmp_path` + factory function with configurable completeness. Medium complexity.
|
||||
- **CI integration** — GitHub Actions with matrix strategies. Medium complexity.
|
||||
|
||||
**Should have (v1.8.x follow-up, deferrable):**
|
||||
- Discussion session merging (append, not overwrite, on repeated `/pf-paper` for same paper)
|
||||
- Session metadata in discussion.json (model, duration, agent type)
|
||||
- Pass completion percentage calculation
|
||||
**Should have (differentiators):**
|
||||
- **Snapshot testing for CLI output** — catches unintended changes immediately. Single assertion per contract test.
|
||||
- **Plasma CI matrix** — optimizes CI cost while maintaining coverage. Full matrix for fast tests, narrow for slow tests.
|
||||
- **User journey tests (L5)** — validates complete workflows against documented UX contracts. Catches cross-component gaps.
|
||||
- **Chaos/destructive tests (L6)** — corrupted inputs, network failures, disk issues. Ensures graceful degradation.
|
||||
- **Hierarchical conftest fixtures** — root conftest provides shared fixtures; each layer extends/overrides.
|
||||
- **VaultBuilder factory** — single entry point for 3 completeness levels: "minimal", "standard", "full".
|
||||
- **CHAOS_MATRIX.md** — documentation of all destructive scenarios, triggers, expected behavior.
|
||||
|
||||
**Future consideration (v2+):**
|
||||
- Cross-library discussion search (rely on Obsidian built-in search)
|
||||
- Discussion export to AI context packs
|
||||
- Deep-reading maturity integration into maturity gauge
|
||||
|
||||
**Anti-features explicitly rejected:**
|
||||
- Auto-recording all agent conversations (creates noise, violates worker/agent boundary)
|
||||
- Full chat transcription in markdown (unstructured, duplicates agent's internal logs)
|
||||
- Deep-reading dashboard as replacement for deep-reading.md (dashboard is index/summary view only)
|
||||
- Real-time dashboard updates during deep-reading (agent executes outside plugin; refresh on active-leaf-change is sufficient)
|
||||
- Discussion search across all papers (Obsidian's built-in search already handles this)
|
||||
**Anti-features (explicitly NOT building):**
|
||||
- Real Obsidian E2E in CI (3+ min per test, flaky)
|
||||
- Load/performance testing (local single-user tool)
|
||||
- Cross-browser testing (plugin runs in Chromium-only Electron)
|
||||
- Full parallel matrix (54 CI jobs per PR for minimal confidence)
|
||||
- Automated visual regression (fragile; component-level tests suffice)
|
||||
|
||||
### Architecture Approach
|
||||
|
||||
The architecture extends PaperForge's existing **thin-shell plugin pattern**: Python owns all business logic and writes structured data to the filesystem; the Obsidian JS plugin reads that data and renders Pure CSS/DOM components. No business logic is duplicated in JS.
|
||||
The testing architecture follows a **modified testing diamond** (unit tests at base, integration/contract in the middle, E2E at top) with an extra **L0 version-check layer** as the fast pre-flight gate. All layers share a centralized `fixtures/` golden dataset with 4 subdirectories (zotero/, pdf/, snapshots/, ocr/). Mock systems are layered: HTTP-level (`responses` for OCR), static fixtures (for Zotero), and `tmp_path` (for vault filesystem).
|
||||
|
||||
The key architectural addition is a **4th mode dispatch** in the existing `_detectAndSwitch()` → `_switchMode()` → `case 'mode': renderX()` pattern. The mode detection hierarchy (in strict order) becomes:
|
||||
**Major components:**
|
||||
1. **L0: `scripts/check_version_sync.py`** — validates all 6+ version declarations (Python **init**, manifest.json, versions.json, CHANGELOG) before any tests run. Sub-100ms. Single CI job on ubuntu.
|
||||
2. **L1: `tests/unit/`** — relocated existing 470+ tests. No external dependencies. Mock everything outside module under test. <30s full suite. Coverage >85% for core, >75% for workers.
|
||||
3. **L2: `tests/cli/`** — subprocess invoker asserts exit codes, stdout JSON schemas, stderr patterns. Uses `pytest-snapshot` with shape-specific assertions (not whole-file).
|
||||
4. **L3: `tests/plugin/`** — Vitest + obsidian-test-mocks + jsdom. Tests plugin lifecycle, settings, dashboard, i18n. Extracts `paperforge-backend.js` for Node.js-testable backend module.
|
||||
5. **L4: `tests/e2e/`** — creates full temp vault; calls Python worker functions directly (not subprocess). Session-scoped golden vault + per-test fast clone for performance.
|
||||
6. **L5: `tests/journey/`** — UX contract-driven complete workflows. Single concrete scenario per test. Step abstraction layer. Nightly-only.
|
||||
7. **L6: `tests/chaos/scenarios/`** — 8 destructive scenarios documented in CHAOS_MATRIX.md. Docker-only isolation. Safety contracts on every test.
|
||||
8. **`fixtures/`** (golden dataset) — centralized at repo root. zotero/ (8 JSON fixtures), pdf/ (4 minimal PDFs), snapshots/ (4 subdirectories), ocr/ (5 fixture files). Generated from code where possible.
|
||||
9. **CI workflows** — 3 workflow files: `ci-pr-checks.yml` (L0+L1 per push, <2min), `ci.yml` (L0-L5 on merge to main), `ci-chaos.yml` (L6 weekly + manual).
|
||||
|
||||
```
|
||||
1. no active file → 'global' (existing)
|
||||
2. .base file → 'collection' (existing)
|
||||
3. deep-reading.md → 'deep-reading' (NEW — MUST precede zotero_key check)
|
||||
4. .md with zotero_key → 'paper' (existing)
|
||||
5. fallback → 'global' (existing)
|
||||
```
|
||||
|
||||
**Major components affected:**
|
||||
1. **`_detectAndSwitch()` + `_switchMode()`** — Mode detection extended with deep-reading.md filename check; must be checked *before* zotero_key frontmatter to prevent hijacking by per-paper mode
|
||||
2. **`_renderDeepReadingMode()` (NEW)** — Dedicated render method consuming data from formal-library.json (lifecycle/health/maturity), deep-reading.md (Pass 1 summary), and ai/discussion.json (AI Q&A history)
|
||||
3. **`_renderPaperMode()` extension** — Adds "Jump to Deep Reading" contextual button when `deep_reading_path` exists
|
||||
4. **`discussion.py` (NEW Python module)** — `record_discussion()` writes both discussion.md and discussion.json; `load_discussion_json()` reads existing record; `append_discussion()` appends new Q&A pairs atomically
|
||||
5. **`asset_index.py` build_envelope()** — Adds `paperforge_version` field for the version badge fix
|
||||
|
||||
**Key data flow:**
|
||||
```
|
||||
User runs /pf-deep → ld_deep.py generates scaffold → Agent completes session
|
||||
→ discussion_recorder.py writes ai/discussion.{md,json}
|
||||
User opens deep-reading.md → _detectAndSwitch() detects it → _renderDeepReadingMode()
|
||||
→ reads formal-library.json (status badges) + deep-reading.md (Pass 1 summary) + ai/discussion.json (Q&A history)
|
||||
```
|
||||
**Key patterns:**
|
||||
- **Plasma CI matrix:** L1 on full 3 OS x 3 Python (9 jobs); L2 on 2 Python x 1 OS (2 jobs); L3-L5 on single config (1 job each)
|
||||
- **Fixture hierarchy (5 levels):** `empty_vault` (config only) -> `config_vault` (+ dirs) -> `vault_with_export` (+ BBT JSON) -> `vault_with_ocr` (+ OCR data) -> `full_test_vault` (+ Zotero storage, formal notes)
|
||||
- **Mock OCR with `responses`:** intercepts at HTTP layer, not module level. Fixtures from real API captures. Prevents mock drift.
|
||||
- **Snapshot update protocol:** `pytest --snapshot-update` -> review diff -> commit deliberately. Never "regenerate all snapshots."
|
||||
- **Truth hierarchy:** L4 is authoritative for "does it work?"; L2 validates CLI translation; L1 validates edge cases. L1 mocks must be validated against L4 output.
|
||||
|
||||
### Critical Pitfalls
|
||||
|
||||
All researchers independently flagged these — they represent consensus on highest-risk items:
|
||||
**Top 7 pitfalls from research (ordered by severity):**
|
||||
|
||||
1. **Mode detection ordering regression** — `deep-reading.md` carries the same `zotero_key` frontmatter as the formal note. If the filename check isn't placed FIRST in the `.md` handler (before the `zotero_key` check), the dashboard enters per-paper mode instead of deep-reading mode. **Fix:** Insert `if (activeFile.basename === 'deep-reading')` as the first branch inside the `.md` handler.
|
||||
1. **Mocking External Services Too Early and Too Rigidly** — The existing test suite already demonstrates 12+ levels of nested `with patch(...):` blocks. Mocks created from memory (not real API captures) silently drift from real PaddleOCR behavior. Tests pass but production breaks. *Prevention: capture real API responses first; use `autospec=True` on all patches; limit nesting to 3 levels via fixture extraction; add VCR.py layer for OCR polling.*
|
||||
|
||||
2. **Encoding corruption on Windows CJK systems** — Python may write GBK-encoded files while Node.js reads as UTF-8, producing mojibake for Chinese Q&A content. This is a well-documented Obsidian + Windows + Chinese locale problem. **Fix:** Python must use `open(path, 'w', encoding='utf-8')` explicitly; JS reads must match. Use `PYTHONIOENCODING=utf-8` and `PYTHONUTF8=1` environment variables for child processes.
|
||||
2. **CI Matrix Combinatorial Explosion Killing Feedback Loops** — A naive full matrix (3 OS x 3 Python x 2 Node = 18 jobs at 15 min each = 4.5 hours wall-clock). Developers wait all day or skip CI. *Prevention: plasma matrix with path-filtered jobs; L1 on 3x3; L2-L5 on narrower configs; `pytest -m "not slow"` for push; hard CI budget of 20 concurrent runners.*
|
||||
|
||||
3. **`discussion.json` schema version missing** — Shipping without `schema_version` repeats the `formal-library.json` v1→v2 migration pain from v1.6/v1.7. Every format change becomes a breaking change. **Fix:** Include `"schema_version": "1"` in the envelope from day one. Always use object envelope, never top-level array.
|
||||
3. **Snapshot Tests Breaking on Every Refactor** — Whole-file JSON snapshots cause 500-line diffs for 5-line code changes. "Regenerated all snapshots" becomes the default review response — regressions slip through. *Prevention: assert specific shapes, not whole files; normalize dynamic fields (timestamps, UUIDs) before snapshotting; use `inline-snapshot` or `dirty-equals` for version-agnostic comparisons.*
|
||||
|
||||
4. **`btoa()` crash on Chinese filenames** — `window.btoa()` only supports Latin1 characters. If any path construction for `ai/` directory uses `btoa()` with Chinese paper titles, it throws `InvalidCharacterError`. **Fix:** Ban `btoa()` and `atob()` from the plugin. Use `Buffer.from(str, 'utf-8').toString('base64')` instead.
|
||||
4. **Temp Vault Tests Being Slow, Non-Deterministic, or Platform-Specific** — E2E tests with subprocess calls take 30-60 seconds each. Cross-platform path issues (Windows junctions, macOS `/var` -> `/private/var` symlinks). `shutil.rmtree` fails on Windows with `PermissionError`. *Prevention: session-scoped golden vault + per-test fast clone; separate "fast" (Python API) from "full" (subprocess) E2E; safe teardown with retry on Windows; normalize paths in all assertions.*
|
||||
|
||||
5. **`active-leaf-change` double-firing mode oscillation** — Obsidian fires this event twice during tab switches (old leaf blur + new leaf focus). The 300ms debounce can catch either, causing a visible flash of global mode between per-paper and deep-reading modes. **Fix:** Extract `_resolveModeForFile()` as a pure function; guard with identity check (same mode AND same file path = no-op); increase debounce to 500ms during transitions.
|
||||
5. **User Journey Tests Being Too Vague to Automate** — Prose UX contracts like "User opens Obsidian, configures Zotero, runs OCR" are ambiguous and un-automatable. Tests either hardcode assumptions or check nothing meaningful. *Prevention: write journey tests as pseudo-code BEFORE implementation; create a "step" abstraction layer (not raw Playwright); define EXACTLY ONE concrete scenario per test; create journey fixture packs for pre-configured states.*
|
||||
|
||||
## Researcher Conflicts Resolved
|
||||
6. **Destructive Tests That Damage Developer Machines or CI Shared State** — Chaos tests that delete files or modify config can accidentally run on real vaults if `--vault` is omitted or fixture paths resolve incorrectly. *Prevention: ALL destructive tests MUST use `tmp_path` with isolation assertion (`assert "tmp" in str(vault)`); run in Docker only; add safety contract comment to every destructive test function; keep in separate module ignored by default test runner.*
|
||||
|
||||
The four researchers produced high-quality, mostly aligned outputs. However, two substantive conflicts emerged:
|
||||
7. **Golden Dataset and Fixture Bloat** — Fixtures grow from 10KB to 100MB+ in git over 6 months. Nobody knows which are still used. Binary fixtures (PDFs, OCR JSON) permanently bloat git history. *Prevention: generate fixtures from code (not hand-written); keep large fixtures outside git via `download_fixtures.py` script; track with MANIFEST.json and validate coverage in CI; version fixture schemas explicitly.*
|
||||
|
||||
### Conflict 1: `discussion.json` schema shape
|
||||
|
||||
| Researcher | Schema Proposal |
|
||||
|------------|----------------|
|
||||
| **STACK.md** | Flat `history[]` array with `index`, `timestamp`, `question`, `answer`, `tags`, `agent_model`. Summary at top level with `total_qa`, `last_updated`, `top_tags`. |
|
||||
| **FEATURES.md** | Nested `sessions[]` with `session_id`, `agent`, `started`, and `qa_pairs[]` array. Each QA pair has `question`, `answer`, `source`, `timestamp`. |
|
||||
| **ARCHITECTURE.md** | Similar to FEATURES: `sessions[]` with `session_id`, `timestamp`, `model`, `command`, `summary`, `message_count`, `messages[]` with `role`/`content`. Separate `format_version` (not `schema_version`). |
|
||||
| **PITFALLS.md** | Warns against bare array format, recommends `schema_version` envelope. |
|
||||
|
||||
**Resolution: Adopt the FEATURES.md sessions-based schema with these adjustments:**
|
||||
- Use `schema_version: "1"` (from PITFALLS) — not `format_version`
|
||||
- Keep session grouping (`sessions[]` → `qa_pairs[]`) — this is the right semantic model; a flat history loses the session boundary that `/pf-paper` vs `/pf-deep` invocations represent
|
||||
- Use `timestamp` per QA pair (from FEATURES/STACK) — individual message timing matters for chronology
|
||||
- Include `source` field as optional (from FEATURES) — traces answers back to specific Pass/section
|
||||
- Add `model` at session level (from STACK/ARCHITECTURE) — the model is per-session, not per-QA
|
||||
- Drop `message_count` (ARCHITECTURE proposes it but it's derivable from `qa_pairs.length`)
|
||||
|
||||
**Rationale:** Sessions are the natural grouping unit — each `/pf-paper` or `/pf-deep` invocation creates a new session. A flat history array loses this structure. The FEATURES.md schema best captures this while staying minimal.
|
||||
|
||||
### Conflict 2: How the plugin reads `discussion.json`
|
||||
|
||||
| Researcher | Recommendation |
|
||||
|------------|----------------|
|
||||
| **STACK.md** | `fs.readFileSync()` — same pattern as `formal-library.json` reading |
|
||||
| **ARCHITECTURE.md** | `fs.readFileSync()` — "same pattern for discussion.json... from ai/ directory" |
|
||||
| **PITFALLS.md** | **DO NOT use `fs.readFileSync()`** — use `vault.adapter.read()` because discussion.json is vault-internal (paper workspace), not system-directory. `fs.readFileSync` bypasses vault cache, misses modify events, risks encoding issues. |
|
||||
| **FEATURES.md** | Doesn't specify read method, only says "reads discussion.json" |
|
||||
|
||||
**Resolution: PITFALLS.md is correct. Use `app.vault.adapter.read()` for discussion.json.**
|
||||
|
||||
**Rationale:** `fs.readFileSync()` is the correct pattern for `formal-library.json` because it lives in `<system_dir>/PaperForge/indexes/` — outside the Obsidian vault, invisible to vault cache. But `discussion.json` lives in `Literature/<domain>/<key> - <Title>/ai/` — inside the vault, visible in Obsidian's file explorer. Using `fs.readFileSync()` on vault-internal files:
|
||||
- Bypasses Obsidian's metadata cache
|
||||
- Prevents `modify` events from triggering dashboard refresh when Python writes new discussions
|
||||
- Risks encoding mismatch on Windows CJK systems (vault adapter normalizes encoding)
|
||||
|
||||
The STACK.md and ARCHITECTURE.md researchers made a natural but incorrect assumption that the existing pattern extends unmodified. The PITFALLS researcher caught the distinction: system-directory files use `fs`, vault-internal files use `vault.adapter`.
|
||||
**See `.planning/research/PITFALLS.md` for all 13 pitfalls, including:** fixture inheritance conflicts (P13), Windows path hell (P12), test layering conflicts (P11), plugin tests testing subprocess mechanics instead of behavior (P10), CLI --json tests checking shape but not semantics (P9), and version sync checking the wrong things (P8).
|
||||
|
||||
## Implications for Roadmap
|
||||
|
||||
Based on dependency analysis across all four research files, the research converges on a **7-phase build order**. Phases 31a and 31b are quick-win bug fixes; Phase 32 establishes the mode detection infrastructure; Phase 33 builds the dashboard renderer; Phase 34 adds navigation; Phases 35-36 build the data pipeline end-to-end.
|
||||
Based on combined research, the testing infrastructure should be built in **5 phases** with strict dependency ordering. Each phase delivers value independently and is testable before the next begins.
|
||||
|
||||
### Phase 31a: Fix Version Number Display
|
||||
### Phase 1: Foundation — Fixture Hierarchy + L0 + L1 Relocation
|
||||
|
||||
**Rationale:** Lowest risk, no dependencies. Single change in Python (`build_envelope()` adds `paperforge_version`) + single change in JS (`_fetchStats()` reads it). Quick win that improves perceived quality before new features ship.
|
||||
**Rationale:** All higher layers depend on healthy fixtures and a working test runner. The existing 473+ tests must be relocated into the new hierarchy before any new tests are written. L0 (version check) and L1 (unit tests) can be extracted early because they require minimal new infrastructure.
|
||||
|
||||
**Delivers:** Version badge shows actual PaperForge version (e.g., `v1.8.0`) instead of `v—` placeholder.
|
||||
**Delivers:**
|
||||
- Refactored `tests/conftest.py` with 5-level fixture hierarchy (`empty_vault` -> `config_vault` -> `vault_with_export` -> `vault_with_ocr` -> `full_test_vault`)
|
||||
- All existing tests relocated from flat `tests/` to `tests/unit/` (pure directory moves, no behavior changes)
|
||||
- `scripts/check_version_sync.py` (L0: validates 6+ version declarations, semver structure, installed `--version` against source)
|
||||
- `ci-pr-checks.yml` (L0 on ubuntu + L1 on full 3x3 matrix, <2 min)
|
||||
- `pyproject.toml` updated with markers, testpaths, new dependencies
|
||||
- `tests/sandbox/` retained in place for backward compat
|
||||
|
||||
**Addresses:** Bug Fix: Version Number (FEATURES.md)
|
||||
**Avoids:** Pitfall 8 — version badge race condition (reads from plugin manifest as floor, Python index as ceiling)
|
||||
**Addresses:** FEATURES.md table stakes (L0, L1)
|
||||
**Avoids:** PITFALLS.md P13 (fixture inheritance) — implement hierarchy before any new tests; P8 (version sync wrong checks) — validate installed version not file strings
|
||||
**Stack:** pytest >=8.0, pytest-timeout >=2.2.0, pytest-mock >=3.12.0, coverage >=7.4.0, ruff >=0.4.0
|
||||
**Research flag:** Well-documented patterns — pytest fixtures and test organization are standard. No deep research needed.
|
||||
|
||||
**Stack elements used:** Python `__version__` from `paperforge/__init__.py`; JS `_cachedStats.version` read path.
|
||||
### Phase 2: Golden Datasets + CLI Contract Tests (L2)
|
||||
|
||||
### Phase 31b: Fix "ai" Row Bug
|
||||
**Rationale:** The `fixtures/` golden dataset is the shared foundation for L2-L6. L2 (CLI contracts) is the most valuable integration layer — it protects the Python<->plugin boundary with minimal setup (subprocess invoker + snapshot assertions). Building fixtures and L2 together ensures fixture design matches real usage.
|
||||
|
||||
**Rationale:** Independent bug fix, but must be applied AFTER Phase 31a because understanding which UI element is "ai" requires reading current dashboard render paths. Grep all render paths before removal.
|
||||
**Delivers:**
|
||||
- `fixtures/` directory structure: zotero/ (8 fixtures), pdf/ (4 minimal PDFs), snapshots/ (4 subdirectories), ocr/ (5 files)
|
||||
- `fixtures/MANIFEST.json` — tracks `used_by`, `generated`, `desc` for each fixture
|
||||
- `tests/cli/` with subprocess invoker fixture and contract tests for all 7 CLI commands
|
||||
- `pytest-snapshot` integrated with shape-specific assertions (not whole-file)
|
||||
- Mock OCR backend with `responses` library, using fixture responses captured from real API
|
||||
- `ci.yml` extended with L2 on 2 Python versions x 1 OS
|
||||
- All L2 tests excluded from `ci-pr-checks.yml` (they depend on L1 being green)
|
||||
|
||||
**Delivers:** Dashboard no longer shows a meaningless "ai" row.
|
||||
**Addresses:** FEATURES.md table stakes (golden datasets, mock OCR, L2), differentiators (snapshot testing)
|
||||
**Avoids:** PITFALLS.md P1 (mock drift) — capture real API first; P3 (snapshot brittleness) — shape assertions; P7 (fixture bloat) — MANIFEST + CI validation; P9 (CLI shallow tests) — schema validation + cross-command consistency
|
||||
**Research flag:** **Needs research** — Snapshot testing strategy decisions: inline vs external snapshots, normalization helpers for dynamic fields. Low risk, but needs concrete examples before implementation.
|
||||
|
||||
**Addresses:** Bug Fix: Remove meaningless "ai" row (FEATURES.md)
|
||||
**Avoids:** Pitfall 9 — "ai" row removal without checking all render paths
|
||||
### Phase 3: Plugin Tests + Temp Vault E2E (L3 + L4)
|
||||
|
||||
**Stack elements used:** `grep -rn 'ai' paperforge/plugin/main.js paperforge/plugin/styles.css` to identify source; surgical removal scoped to the specific render path.
|
||||
**Rationale:** L3 and L4 can be built in parallel (they target different runtimes: JS vs Python). L3 (plugin) is independent of Python layers; L4 (E2E) depends on L2 being stable (contracts define what E2E tests should verify). Building them together optimizes the critical path.
|
||||
|
||||
### Phase 32: Add Deep-Reading Mode Detection
|
||||
**Delivers:**
|
||||
- `tests/plugin/` with Vitest + obsidian-test-mocks + jsdom
|
||||
- Extracted `paperforge-backend.js` module for Node.js-testable backend interface
|
||||
- Plugin lifecycle, settings, dashboard, i18n, subprocess dispatch tests
|
||||
- `tests/e2e/conftest.py` with session-scoped golden vault + per-test fast clone
|
||||
- Temp vault E2E tests: full sync-OCR-status pipeline, repair workflow, headless setup, migration, doctor, multi-domain
|
||||
- Safe teardown with Windows file-lock retry
|
||||
- Mock OCR used in L2/L3/L4 with layer-specific fixture configurations
|
||||
- CI extended with L3 (1 Node) + L4 (1 Python, 1 OS)
|
||||
|
||||
**Rationale:** This is the architectural foundation for everything in v1.8. Must be built before any deep-reading rendering can happen. Dependencies: nothing (pure JS change in `_detectAndSwitch()`).
|
||||
**Addresses:** FEATURES.md must-haves (L3, L4), differentiators (VaultBuilder factory)
|
||||
**Avoids:** PITFALLS.md P4 (temp vault slow) — session-scoped + per-test clone; P10 (plugin subprocess mechanics) — extract backend module; P12 (Windows path hell) — platform-specific marks + normalization
|
||||
**Research flag:** **Needs research** — `obsidian-test-mocks` API surface: does it support the specific `App`, `Workspace`, `Plugin` APIs used by PaperForge? Verify by loading the npm package and comparing against actual usage in `paperforge/plugin/main.js`.
|
||||
|
||||
**Delivers:** When user opens `Literature/<domain>/<key> - <Title>/deep-reading.md`, the plugin detects it and dispatches to `deep-reading` mode (even though the stub renderer is a placeholder until Phase 33).
|
||||
### Phase 4: User Journey + Chaos Tests (L5 + L6)
|
||||
|
||||
**Implements:** Architecture component — `_detectAndSwitch()` extension, `_switchMode()` `case 'deep-reading'` branch.
|
||||
**Rationale:** These are the most expensive and least stable tests. They should be built LAST, after the foundation (L0-L4) is solid. L5 depends on L4 for vault infrastructure; L6 is independent but shares mock systems with L2.
|
||||
|
||||
**Avoids:** Pitfall 1 (mode detection ordering — check `basename === 'deep-reading'` BEFORE `zotero_key` frontmatter); Pitfall 5 (active-leaf-change double-fire — extract `_resolveModeForFile()` as pure function with identity guard); Pitfall 3 (filename-based heuristic — verify parent directory matches `{8-char key} - {slug}` pattern, not just basename).
|
||||
**Delivers:**
|
||||
- `tests/journey/UX_CONTRACT.md` — concrete step sequences for each user journey
|
||||
- 4 journey tests: new user onboarding, daily workflow, upgrade migration, error recovery
|
||||
- Journey fixture packs: "half-setup", "ready-for-deep-reading" pre-configured environments
|
||||
- `tests/chaos/scenarios/CHAOS_MATRIX.md` — 8 destructive scenarios documented
|
||||
- 8 chaos test files with Docker-only isolation and safety contracts
|
||||
- `ci-chaos.yml` — weekly schedule + manual trigger, scenarios parallelized by matrix
|
||||
- Journey tests NOT in PR gate (nightly only); chaos tests NOT in regular CI
|
||||
|
||||
**Key implementation constraint:**
|
||||
```javascript
|
||||
// Inside .md handler, BEFORE zotero_key check:
|
||||
if (activeFile.basename === 'deep-reading') {
|
||||
const parentDir = activeFile.parent?.name || '';
|
||||
const match = parentDir.match(/^([A-Z0-9]{8})\s+-\s+(.+)$/);
|
||||
if (match) {
|
||||
this._currentPaperKey = match[1];
|
||||
this._currentPaperEntry = this._findEntry(match[1]);
|
||||
this._switchMode('deep-reading');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// THEN: existing zotero_key check for per-paper mode
|
||||
```
|
||||
**Addresses:** FEATURES.md differentiators (L5, L6, chaos matrix doc)
|
||||
**Avoids:** PITFALLS.md P5 (vague journeys) — concrete scenarios + step abstraction; P6 (destructive safety) — isolation assertions + Docker
|
||||
**Research flag:** **Needs research** — Chaos scenario design: what are the real-world failure modes for PaddleOCR API, Zotero junctions, concurrent sync calls? Needs domain knowledge from the existing codebase's error-handling paths.
|
||||
|
||||
### Phase 33: Build `_renderDeepReadingMode()` Component
|
||||
### Phase 5: CI Matrix Optimization + Consistency Audit
|
||||
|
||||
**Rationale:** Depends on Phase 32 (mode detection must route to this renderer). This is the largest single JS change. Must implement the sub-components (status bar, paper info header, Pass 1 summary, AI Q&A history placeholder, navigation) with empty-state handling for all data sources.
|
||||
**Rationale:** The final phase optimizes and hardens the CI pipeline based on real data from earlier phases. Path filters, performance baselines, and cross-layer consistency audits should be informed by actual CI run times and failure patterns.
|
||||
|
||||
**Delivers:** Deep-reading dashboard that shows:
|
||||
- Status bar with lifecycle/OCR/deep-reading/maturity badges
|
||||
- Paper info header (title, authors, year, domain)
|
||||
- Pass 1 summary extracted from deep-reading.md
|
||||
- AI Q&A history section (placeholder until Phase 36; shows empty state)
|
||||
- Navigation link back to the per-paper dashboard
|
||||
**Delivers:**
|
||||
- Path-filtered CI triggers (e.g., changes to `ocr.py` trigger L1+L2+L4; changes to `main.js` trigger L3)
|
||||
- `pytest -m "slow"` markers for tests >30s (moved to nightly or merge-only)
|
||||
- Consistency audit test: validates L1 mock expectations against L4 real output
|
||||
- `re-actors/alls-green` gate aggregation for branch protection
|
||||
- `scripts/validate_fixtures.py` — CI check that every fixture is referenced by at least one test
|
||||
- CI budget enforcement: max 20 concurrent runners; single-config L2-L5
|
||||
|
||||
**Implements:** Architecture component — `_renderDeepReadingMode()` with all sub-renderers.
|
||||
|
||||
**Avoids:** Pitfall 6 (empty discussion.json states — implement `_loadDiscussionData()` helper returning discriminated union for all 4 empty states: `no_ai_dir`, `not_found`, `empty`, `no_discussions`, `ok`); Pitfall 13 (CSS namespace collision — use `paperforge-deepreading-*` prefix, scope under `.paperforge-mode-deepreading` wrapper class); Pitfall 3 (anti-pattern: deep-reading dashboard replacing deep-reading.md — dashboard is index/summary only, never authoritative).
|
||||
|
||||
**CSS additions** (all scoped under `.paperforge-mode-deepreading`):
|
||||
- `.paperforge-deep-reading-view` — root container (flex column, gap: 20px)
|
||||
- `.paperforge-dr-status-bar` — Pass 1/2/3 completion indicator row
|
||||
- `.paperforge-dr-pass-summary` — Pass 1 summary card
|
||||
- `.paperforge-dr-discussion-card` — individual Q&A card
|
||||
- `.paperforge-dr-discussion-list` — scrollable card container
|
||||
- `.paperforge-dr-tag-chip` — tag pills (font-size: 10px, border-radius: 8px)
|
||||
- `.paperforge-dr-empty` — empty state styling
|
||||
- `.paperforge-dr-section-title` — section headers
|
||||
|
||||
### Phase 34: Add "Jump to Deep Reading" Button
|
||||
|
||||
**Rationale:** Depends on Phase 32 (deep-reading path resolution) and Phase 33 (deep-reading dashboard must exist for navigation to matter). Simple modification to `_renderPaperMode()`.
|
||||
|
||||
**Delivers:** On per-paper dashboard card, a "Jump to Deep Reading" button appears when `entry.deep_reading_path` is non-empty and `entry.deep_reading_status === 'done'`. Click navigates to `deep-reading.md`, which triggers Phase 32 mode detection.
|
||||
|
||||
**Implements:** Architecture component — integration point on per-paper dashboard contextual actions row.
|
||||
|
||||
**Avoids:** Pitfall 7 (Jump button assumes file exists — verify `getAbstractFileByPath()` before `openLinkText()`, show clear `Notice` on missing file); Anti-feature (dashboard replacing deep-reading.md — button opens the actual file, dashboard is a view layer).
|
||||
|
||||
**Condition to show button:** `entry.deep_reading_path && entry.deep_reading_status === 'done'` — don't show for papers that haven't had deep reading performed.
|
||||
|
||||
### Phase 35: AI Discussion Recorder (Python)
|
||||
|
||||
**Rationale:** Depends on nothing (standalone Python module). Can be built in parallel with Phases 32-34. Writes the data that Phase 36 will read. Must implement atomic writes to prevent corruption during concurrent access.
|
||||
|
||||
**Delivers:** `paperforge/worker/discussion.py` with:
|
||||
- `record_discussion(key, vault, agent, qa_pairs)` — writes both `discussion.md` and `discussion.json`
|
||||
- `load_discussion_json(key, vault)` — reads existing record
|
||||
- `append_discussion(key, vault, qa_pair)` — appends to existing (read-modify-write atomic via tempfile + os.replace)
|
||||
|
||||
**Implements:** Architecture component — Python discussion recording module, integration with `/pf-paper` and `/pf-deep` Agent commands.
|
||||
|
||||
**Uses:** Existing stdlib only (`json`, `pathlib`, `datetime`, `tempfile`, `os`); same atomic write pattern as `asset_index.py`.
|
||||
|
||||
**Avoids:** Pitfall 3 (schema version missing — ship with `schema_version: "1"` in envelope); Pitfall 2 (encoding corruption — use `encoding='utf-8'` explicitly, set `PYTHONIOENCODING=utf-8` env var); Pitfall 4 (btoa on Chinese — path resolution uses zotero_key only, never writes slugified title paths); Pitfall 10 (.md vs .json inconsistency — define .json as canonical, .md as derived view); Pitfall 11 (tabs/newlines breaking Obsidian callouts — use `newline='\n'` consistently, avoid tabs); Pitfall 15 (heading collisions — use unique heading prefix `## AI Discussions` for discussion.md).
|
||||
|
||||
**File operations must be atomic:** Use the proven `tempfile.NamedTemporaryFile` + `os.replace()` pattern to prevent partial writes during concurrent access. Do NOT write directly to the target file.
|
||||
|
||||
### Phase 36: Wire AI Q&A History into Deep-Reading Dashboard
|
||||
|
||||
**Rationale:** Depends on Phase 33 (renderer exists) and Phase 35 (data exists). This is the integration step that connects the data pipeline end-to-end.
|
||||
|
||||
**Delivers:** When deep-reading dashboard shows, the AI Q&A History section renders actual discussion data from `ai/discussion.json` (last 3 Q&A pairs across all sessions, most recent first). Empty state shown when no discussions exist.
|
||||
|
||||
**Implements:** Architecture component — `_renderDiscussionHistory()` in deep-reading mode renderer.
|
||||
|
||||
**Avoids:** Pitfall 2 (fs.readFileSync bypasses vault API — use `app.vault.adapter.read()` for vault-internal discussion.json, NOT `fs.readFileSync`); Pitfall 12 (debounce timer leak — add 2-second cooldown after refresh, use mtime comparison to avoid redundant re-renders).
|
||||
|
||||
**Key integration detail:** The modify event filter must include `discussion.json` paths to trigger dashboard refresh when Python appends new Q&A:
|
||||
```javascript
|
||||
const modifyHandler = this.app.vault.on('modify', (file) => {
|
||||
if (file?.path?.endsWith('formal-library.json') || file?.path?.endsWith('discussion.json')) {
|
||||
this._invalidateIndex();
|
||||
this._refreshCurrentMode();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Phase 37: Integration Testing & Polish
|
||||
|
||||
**Rationale:** After all components are built, verify end-to-end flow and fix edge cases. Must test: empty states, Chinese filenames/content, split-pane mode switching, rapid Q&A recording.
|
||||
|
||||
**Delivers:** Verified end-to-end flow: `/pf-deep` → discussion files written → deep-reading dashboard shows Q&A history → Jump button navigates correctly → mode doesn't oscillate in split panes.
|
||||
|
||||
**Avoids:** All pitfalls together — uses the "Looks Done But Isn't" checklist from PITFALLS.md (13 verification items).
|
||||
**Addresses:** FEATURES.md differentiators (plasma CI matrix), anti-features avoidance (full matrix)
|
||||
**Avoids:** PITFALLS.md P2 (CI too slow) — plasma matrix; P11 (test layering conflicts) — truth hierarchy + mock validation
|
||||
**Research flag:** **Standard patterns** — GitHub Actions path filters and matrix strategies are well-documented. No deep research needed.
|
||||
|
||||
### Phase Ordering Rationale
|
||||
|
||||
- **Fixes first (31a, 31b):** Low risk, no dependencies, quick wins that improve perceived quality before new features ship. Version fix also establishes the Python→JS version bridge that Phase 33 needs.
|
||||
- **Detection before rendering (32 → 33):** Mode detection is the routing infrastructure; the renderer can't work without it. Building detection first allows incremental testing.
|
||||
- **Renderer before data integration (33 → 36):** Build the UI with proper empty states first, then wire real data in. This ensures graceful degradation when no discussions exist.
|
||||
- **Python recorder parallel to JS dashboard (35 parallel to 32-34):** No runtime dependency — the recorder writes files, the dashboard reads them. Can be developed in parallel.
|
||||
- **Integration last (36 → 37):** Wire the full data pipeline only after both ends exist. Test end-to-end only after wiring.
|
||||
1. **Fixtures before tests** (Phase 1 before Phase 2): Golden datasets and fixture hierarchy are prerequisites for ALL test layers. Building them first prevents the "kitchen sink" fixture anti-pattern and fixture bloat.
|
||||
2. **Python before JS** (Phase 2 before Phase 3): L2 CLI contracts define the interface between Python and plugin JS. Building L2 first means L3 can validate against known contracts. However, L3 and L4 can be parallelized since they target different runtimes.
|
||||
3. **Cheap before expensive** (L0-L2 before L4-L6): The most valuable-per-effort tests (version sync, unit, CLI contracts) cost <2 min CI time. They catch 80% of regressions. E2E, journey, and chaos tests add 10-30 min each and should be built only when the fast layers are solid.
|
||||
4. **Optimize last** (Phase 5 after all others): CI matrix optimization metrics (path filter accuracy, test durations, flake rates) can only be measured once all phases are running in CI. Optimizing earlier would be premature.
|
||||
|
||||
### Research Flags
|
||||
|
||||
**Phases likely needing deeper research during planning:**
|
||||
- **Phase 32 (Mode Detection):** The `active-leaf-change` double-fire behavior varies by Obsidian version and platform. May need platform-specific debounce tuning during implementation. Consider `/gsd-research-phase` if Obsidian API behavior is uncertain.
|
||||
- **Phase 35 (AI Discussion Recorder):** The exact integration point with `/pf-paper` and `/pf-deep` scripts needs implementation-level detail. How does the Agent session signal completion? What's the handoff protocol? May need `/gsd-research-phase` if the Agent integration surface is unclear.
|
||||
Phases likely needing deeper research during planning:
|
||||
- **Phase 2 (Golden datasets + L2):** Snapshot testing strategy — inline vs external, normalization helpers. Low risk, but need concrete decisions before implementation.
|
||||
- **Phase 3 (L3 plugin tests):** `obsidian-test-mocks` API surface coverage — does it fully support PaperForge's Obsidian API usage patterns? Verify by loading the npm package.
|
||||
- **Phase 4 (L5 + L6):** Chaos scenario design — what are the real-world failure modes for PaddleOCR, Zotero junctions, concurrent sync? Needs codebase analysis of error-handling paths.
|
||||
|
||||
**Phases with standard patterns (skip research-phase):**
|
||||
- **Phase 31a (Version Fix):** Well-understood pattern — add field to envelope, read in fetch. Standard PaperForge index pattern.
|
||||
- **Phase 33 (Dashboard Rendering):** Pattern established across 3 existing modes (global, paper, collection). Deep-reading mode follows the same `_renderXMode()` template.
|
||||
- **Phase 34 (Jump Button):** Established contextual button pattern (`paperforge-contextual-btn` class) with `openLinkText()` navigation. Used elsewhere in `_renderPaperMode()`.
|
||||
- **Phase 36 (Data Wiring):** Reading a JSON file and rendering DOM. Standard pattern used by `_fetchStats()` and all existing renderers.
|
||||
Phases with standard patterns (skip research-phase):
|
||||
- **Phase 1 (Foundation):** pytest fixture hierarchy, test relocation, version checking — well-documented established patterns
|
||||
- **Phase 5 (CI optimization):** GitHub Actions path filters, matrix strategies — official docs are comprehensive
|
||||
|
||||
## Confidence Assessment
|
||||
|
||||
| Area | Confidence | Notes |
|
||||
|------|------------|-------|
|
||||
| Stack | **HIGH** | No new dependencies. All additions are new modules/files in existing well-understood architecture. Verified against actual source code at `paperforge/plugin/main.js` (2067 lines), `paperforge/plugin/styles.css` (1325 lines), `paperforge/worker/asset_index.py` (577 lines), `paperforge/worker/sync.py` (1829 lines). Actual test fixtures confirm `ai_path` field already exists. |
|
||||
| Features | **HIGH** | Feature landscape verified against Obsidian plugin ecosystem (Smart Chat, Copilot, Gemini Scribe, Claude Sessions — 5 plugins analyzed). MVP definition maps directly to v1.8 milestone requirements from PROJECT.md. Anti-features identified by consensus (3 researchers independently flagged auto-recording as a problem). |
|
||||
| Architecture | **HIGH** | All integration points verified against existing source code. Mode detection hierarchy, file ownership boundaries (Python vs JS), and data flow confirmed by code inspection at specific line ranges. Canonical index schema (`formal-library.json` v2) already carries `ai_path` field. Build order derived from dependency graph analysis. |
|
||||
| Pitfalls | **HIGH** | 15 pitfalls identified across 3 severity tiers. Top 5 critical pitfalls each have verified root causes from Obsidian Forum (#31841, #91927), opencode-obsidian Issue #28, nodejs/undici Issue #5002, and paperclipai Issue #3940. Recovery strategies estimated for each pitfall. "Looks Done But Isn't" checklist provides 13 concrete verification items. |
|
||||
| Stack | HIGH | pytest, Vitest, obsidian-test-mocks, responses are well-documented and verified against official sources. Alternatives analysis is thorough. |
|
||||
| Features | HIGH | Feature landscape derived from existing codebase analysis (473+ tests) + official docs. Table stakes, differentiators, and anti-features are clearly scoped. |
|
||||
| Architecture | HIGH | Detailed directory structure, mock system architecture, CI matrix design, fixture hierarchy, and 6 ADRs. All decisions have clear rationale and alternatives considered. |
|
||||
| Pitfalls | HIGH | 13 pitfalls identified with prevention strategies, recovery plans, and phase mapping. Based on existing codebase analysis (12-level mock nesting, single fixture patterns) + external sources (pytest best practices, snapshot testing analysis). |
|
||||
|
||||
**Overall confidence: HIGH**
|
||||
|
||||
All four researchers worked from the same source code (verified at specific line ranges), the same project context (PROJECT.md, STATE.md, AGENTS.md), and came to convergent conclusions. The two conflicts (schema shape, file read strategy) are well-characterized and resolved above. No areas of fundamental disagreement or missing information remain.
|
||||
**Overall confidence:** HIGH
|
||||
|
||||
### Gaps to Address
|
||||
|
||||
- **Agent integration surface for discussion recording:** The exact API/callback for `discussion_recorder.record_session()` when an Agent session completes needs to be confirmed during Phase 35 implementation. Does `/pf-paper` emit a completion event? Where does the Q&A pair extraction happen? This is an implementation detail, not a research gap — it can be resolved during planning.
|
||||
- **Deep-reading.md content parsing robustness:** The Pass 1 summary extraction relies on parsing `**一句话总览**` markers from deep-reading.md. If the agent generates different formatting in edge cases (non-standard headings, multiline bold), parsing could fail. Phase 33 should include a regex fallback that handles variations.
|
||||
- **Performance at scale:** Current linear scan of index items for `_findEntry()` is O(n) and acceptable for ~100 papers. If the library grows past ~1000 papers, the dashboard may lag on mode switch. This is a known gap documented in ARCHITECTURE.md — address in a future performance phase, not v1.8.
|
||||
- **Cross-platform UTF-8 path handling:** The research covers Windows CJK encoding issues thoroughly, but Linux/macOS with non-UTF-8 filesystem encodings (rare edge case) is not covered. No known users on these systems — defer until reported.
|
||||
1. **`obsidian-test-mocks` API coverage validation** — The npm package claims 100% code coverage of `obsidian.d.ts`, but PaperForge uses specific APIs (settings tab, dashboard views, subprocess dispatch). Need to verify the mock covers all usage in `paperforge/plugin/main.js` before Phase 3 implementation.
|
||||
|
||||
2. **Real PaddleOCR response format** — Mock OCR fixture design depends on capturing at least one real API response. If the API has changed or is inaccessible, fixture generation will need a different approach (e.g., API documentation or developer-provided example).
|
||||
|
||||
3. **CI budget constraints** — The plasma matrix assumes GitHub-hosted runners are available. If this is a self-hosted runner or has credit limits, the matrix may need further reduction. Phase 5 should calibrate based on actual limits.
|
||||
|
||||
4. **Windows junction behavior in CI** — Windows GitHub Actions runners may have different junction/symlink behavior than local Windows machines. The Windows E2E test may need adjustment after initial CI runs on Windows.
|
||||
|
||||
## Sources
|
||||
|
||||
### Primary (HIGH confidence)
|
||||
- **PaperForge source code** (`paperforge/plugin/main.js` lines 1-2067, `paperforge/plugin/styles.css` lines 1-1325, `paperforge/worker/asset_index.py` lines 1-577, `paperforge/worker/sync.py` lines 1677-1749, `paperforge/worker/asset_state.py` lines 1-243) — verified mode detection, switch, rendering, index building, workspace migration, lifecycle/health/maturity computation
|
||||
- **PaperForge project context** (`.planning/PROJECT.md`, `.planning/STATE.md`, `AGENTS.md`) — v1.8 milestone definition, thin-shell constraint, bug reports
|
||||
- **Obsidian Developer Docs** (Context7: `/obsidianmd/obsidian-developer-docs`) — Event system, `registerEvent()`, `active-leaf-change`, `debounce()` best practices
|
||||
- **Obsidian Forum #31841** — `active-leaf-change` double-fire behavior confirmed by multiple plugin developers
|
||||
- **Obsidian Forum #91927** — GB2312 to UTF-8 conversion corrupting Chinese files — encoding mismatch pattern
|
||||
- **opencode-obsidian Issue #28** — `btoa()` crash with Chinese characters — verified fix: `Buffer.from(str).toString('base64')`
|
||||
- **pytest documentation:** https://pytest.org/ — fixture scopes, tmp_path, markers, import modes
|
||||
- **GitHub Actions setup-python:** https://github.com/actions/setup-python — matrix patterns, version management
|
||||
- **GitHub Actions docs:** https://docs.github.com/en/actions/guides/building-and-testing-python — CI matrix optimization
|
||||
- **responses library docs:** https://pypi.org/project/responses/ — HTTP mock library
|
||||
- **Project codebase:** `tests/conftest.py`, `tests/test_ocr_state_machine.py`, `tests/test_e2e_cli.py`, `tests/test_e2e_pipeline.py`, `tests/test_plugin_install_bootstrap.py` — existing 473+ test behavioral analysis
|
||||
- **Project version schema:** `paperforge/__init__.py`, `manifest.json`, `pyproject.toml` — multiple version sources
|
||||
|
||||
### Secondary (MEDIUM confidence)
|
||||
- **Obsidian Smart Chat** — Chat thread linking, Dataview dashboards, chat-active/chat-done tracking
|
||||
- **Obsidian Copilot (DeepWiki)** — Chat persistence and history system with markdown files, YAML frontmatter, session grouping
|
||||
- **Claude Sessions plugin** — Session timeline rendering, summary dashboard, Obsidian Bases dashboards
|
||||
- **Gemini Scribe** — Per-note history file pattern, auto-appending
|
||||
- **nodejs/undici Issue #5002** — Multi-byte UTF-8 character corruption at chunk boundaries with CJK text
|
||||
- **paperclipai/paperclip Issue #3940** — GBK-UTF8 encoding mismatch on Windows with CJK child process output
|
||||
- **Excalidraw plugin commit 5c628e0** — Real-world debounce timer cleanup in Obsidian plugin `onunload`
|
||||
- **obsidian-current-view commit ad110f7** — Replacing requestAnimationFrame with setTimeout debounce
|
||||
- **pytest-snapshot:** https://pypi.org/project/pytest-snapshot/ — verified via PyPI search; community-maintained
|
||||
- **obsidian-test-mocks:** https://www.npmjs.com/package/obsidian-test-mocks — verified via Exa search; community-maintained
|
||||
- **Vitest:** https://vitest.dev/ — official docs
|
||||
- **pytest-with-eric.com** (2024-2026): Fixture management, temp directory strategies, flaky test stabilization — community expert patterns
|
||||
- **CircleCI documentation:** Test splitting, parallelism strategies — adapted for GitHub Actions
|
||||
- **snapshot testing analysis (2025-01):** "Why Snapshot Testing Sucks" — targeted behavior-driven assertions
|
||||
|
||||
### Tertiary (LOW confidence)
|
||||
- **PaulGP llms.txt proposal** — AI-readable paper annotations design philosophy (context only, not implementation reference)
|
||||
- **Effortless Academic discussion writing guide** — Q&A-style paper discussion structure (practitioner guide, not technical reference)
|
||||
- **Templater Issue #1629** — `app.vault.modify()` race condition with multiple handlers (relevant but unverified for PaperForge's specific use case)
|
||||
- **obsidian-e2e:** https://www.npmjs.com/package/obsidian-e2e — evaluated and rejected; experimental, needs real Obsidian process
|
||||
|
||||
### Research Files (full details)
|
||||
- **STACK.md:** Complete technology stack with versions, rationale, alternatives, and installation config (.planning/research/STACK.md)
|
||||
- **FEATURES.md:** Full feature landscape with table stakes, differentiators, anti-features, dependencies, and MVP recommendation (.planning/research/FEATURES.md)
|
||||
- **ARCHITECTURE.md:** Complete architecture with directory structure, mock systems, CI matrix, snapshot strategy, 6 ADRs, and build order (.planning/research/ARCHITECTURE.md)
|
||||
- **PITFALLS.md:** 13 pitfalls with prevention strategies, recovery costs, "looks done but isn't" checklist, performance traps, and integration gotchas (.planning/research/PITFALLS.md)
|
||||
|
||||
---
|
||||
|
||||
*Research completed: 2026-05-06*
|
||||
*Research completed: 2026-05-08*
|
||||
*Ready for roadmap: yes*
|
||||
*Conflicts resolved: 2 (schema shape → sessions-based; file read strategy → vault.adapter.read for vault-internal files)*
|
||||
*Gates cleared: Zero new dependencies, zero CLI command changes, thin-shell principle preserved*
|
||||
|
|
|
|||
Loading…
Reference in a new issue