- Move resolvePythonExecutable, getPluginVersion, classifyError, ACTIONS, etc. to src/testable.js
- main.js now requires from ./src/testable.js instead of inline duplication
- Tests import from ../src/testable.js (no obsidian dependency)
- Remove vitest obsidian mock setup (no longer needed)
- Fixes L3 Plugin Tests that failed because main.js requires 'obsidian'
- Add named exports for testable functions (resolvePythonExecutable, getPluginVersion, etc.)
- Update test imports from ../src/*.js to ../main.js
- Fix repair action test: disabled flag was removed when ACTIONS was inlined
- Add paperforgeEnrichedEnv() to enrich PATH for GUI Obsidian on macOS/Linux
- Add getPaperforgePythonCmd() preferring Homebrew/pyenv over Apple CLT Python
- Add tryExecPythonVersion() with multi-candidate fallback for pre-check
- Add scanBbtUnderProfiles() using real Zotero Profiles/extensions/ layout
- Add scanBbtDirectChildren() for safe shallow BBT folder detection
- Add macOS /Applications/Zotero.app and Linux Zotero install detection
- Use paperforgeEnrichedEnv() in setup wizard spawn calls
- Add --user flag to pip install on non-Windows
- Pass PaddleOCR key as --paddleocr-key CLI arg instead of env var
- Add Apple CLT stub Python specific error message in _formatSetupError()
Based on PR #1 by Chartreuse310
Original commit: 156f653 (improve setup modal i18n and update directory defaults)
Co-authored-by: CTZ <yoyflying@163.com>
test_ocr_preflight: mock fitz.open (not builtins.open) + set needs_sanitize=True
- Code uses fitz.open(), not builtins.open() — mock was targeting wrong function
- fitz.open() only called when meta.needs_sanitize is true — mock returned {} which skipped it
- These tests were always broken but CI never ran before v2.1
test_e2e_cli: guard against None stdout in doctor_verdict test on Windows CI
L3 runtime.test.mjs: use platform-agnostic path matching
- path.join produces / on Linux but \\\\ on Windows — test used \\\\ only
L2: removed -m cli marker filter (no test had the marker → exit code 5)
L3: committed package-lock.json (npm ci failed without it)
gitignore: added paperforge/plugin/node_modules/
These are CI config issues, not code regressions from v2.1.
- Add Runtime Health section in settings showing Plugin vX vs Python vY
- Add match/mismatch badge (green/red) with version comparison
- Add 'Sync Runtime' button that runs pip install --upgrade
- Add Dashboard yellow drift warning banner on version mismatch
- Extend _formatSetupError to 10 categories (pip/network/SSL/disk/Python/etc)
- Add 'Copy diagnostic' button on setup failure with env info
- Add all i18n keys in both en and zh
- Add CSS for badge states, drift banner, and diagnostic button
- Add clarifying comment in bump.py about canonical version source
- Update root and plugin manifest.json minAppVersion from 1.0.0 to 1.9.0
- Update versions.json compatibility mapping to 1.9.0
- Add pyyaml>=6.0 to pyproject.toml dependencies
Part A: All 8 subprocess call sites now use resolvePythonExecutable with
settings and extraArgs propagation for py -3 support:
- _fetchVersion, _fetchStats, _runAction, _preCheck, _runInstall, _stepComplete,
_autoUpdate, command palette
- Zero bare 'python' spawn/exec calls remain
- Spaces-in-path safe via execFile instead of shell-string exec
Part B: zotero_data_dir required with validation:
- field_zotero_placeholder i18n updated to 'Required'/'必填'
- Wizard placeholder uses t() call instead of hardcoded string
- _validateStep3 checks: non-empty -> exists -> isDirectory -> has storage/
- _validate() rejects missing zotero_data_dir with validate_zotero i18n key
- zotero_data_dir always passed to --zotero-data flag (no conditional)
- Add python_path to DEFAULT_SETTINGS for storing manual override path
- Refactor resolvePythonExecutable(vaultPath, settings) to return {path, source, extraArgs}
- Manual override (settings.python_path) bypasses auto-detection when set and exists
- Detection order: manual -> .paperforge-test-venv -> .venv -> venv -> py -3 -> python -> python3
- py -3 detection uses extraArgs for proper launcher argument passing
- Stale override re-validated on plugin load; _python_path_stale flag set without clearing
- Update all existing call sites to destructure .path from new return type
- migrate_to_workspace: preserve legacy flat notes even without canonical index
- migrate_to_workspace: reconcile legacy library-record do_ocr/analyze flags including false overrides
- migrate_to_workspace: handle non-canonical legacy filenames via frontmatter title
- asset_index: only advertise fulltext_path/deep_reading_path when files exist
- asset_index: prefer note frontmatter over legacy library-records, fall back to legacy
- setup_wizard: require Python >=3.10 instead of >=3.8
- plugin main.js: align Python version messaging to 3.10+
- ocr.py: auto_analyze_after_ocr reads from formal note paths, not library-records
- HARDEN-04: Pass PADDLEOCR_API_TOKEN via env var instead of CLI --paddleocr-key
- HARDEN-05: Replace innerHTML with createEl() DOM API for directory tree rendering
- Move Better BibTeX auto-export to post-install step (exports dir created by setup)
- Clarify wizard overview: show resources_dir/literature_dir/control_dir hierarchy
- Fix post-install flow order: BBT export → enable plugin → dashboard → sync
- Split do_ocr and analyze marks into separate steps (OCR before deep-reading)
- Mark all literature flags via Base views, not manual file editing
- Make installer incremental: preserve existing files, only create missing ones
- Remove destructive junction/link deletion in TUI wizard
- Merge .env instead of overwriting; skip overwrite for AGENTS.md, docs, plugin
- Update TUI, headless, and plugin completion output to match corrected flow
- Add regression test for non-destructive setup behavior
- Metric cards row: papers count, fulltext-ready (with progress bar), deep-read (with progress bar)
- Lifecycle distribution bar chart via _renderBarChart()
- Aggregated health overview via _renderCollectionHealth() showing PDF/OCR/Note/Asset counts
- Single-pass aggregation from domain-filtered canonical index entries
- Empty state via _renderEmptyState() when domain has no items
- Add _setupEventSubscriptions() for active-leaf-change (debounced 300ms) and vault modify (filtered to formal-library.json)
- Add _renderModeHeader() with mode badge (global/paper/collection classes) and context name
- Add mode warning for paper not found in index (D-18)
- Refactor onOpen() to call _setupEventSubscriptions() before _detectAndSwitch()
- Refactor onClose() with full cleanup: workspace/vault off(), clearTimeout, null cached data