mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
docs: research control center recovery patterns
This commit is contained in:
parent
68f342ddf9
commit
29a0e27077
4 changed files with 1434 additions and 10 deletions
|
|
@ -1,11 +1,11 @@
|
|||
> **Branch:** `feat/ocr-rebuild-ux` | **Last Updated:** 2026-07-14
|
||||
> **Active work:** [Wayfinder: Make the PaperForge control center self-explanatory](https://github.com/LLLin000/PaperForge/issues/65) is charted. The current-contract audit is resolved; capability vocabulary and the two reference-product research tickets form the live frontier.
|
||||
> **Active work:** [Wayfinder: Make the PaperForge control center self-explanatory](https://github.com/LLLin000/PaperForge/issues/65). The current-contract audit and both reference-product research tickets are resolved; capability vocabulary (#69) and managed-runtime architecture (#70) are the live frontier.
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> **Current state:** Retrieval recovery is merged and live. OCR multi-key rebuild/redo now streams progress, supports cooperative stop between papers, and refreshes canonical maintenance state. The control-plane audit found four critical contract gaps: headless setup can skip runtime installation yet return success, Python and TypeScript disagree on legacy path precedence, global `setup_complete` can mask unavailable modules, and stale runtime snapshots can render as ready.
|
||||
>
|
||||
> Next: define the capability envelope in [#69](https://github.com/LLLin000/PaperForge/issues/69) while completing [Obsidian-native setup/settings research](https://github.com/LLLin000/PaperForge/issues/67) and [desktop installation/health/recovery research](https://github.com/LLLin000/PaperForge/issues/68); do not choose the managed-runtime architecture or start production implementation until its blockers close.
|
||||
> Next: define the canonical capability/action vocabulary in [#69](https://github.com/LLLin000/PaperForge/issues/69) and choose the managed-runtime architecture in [#70](https://github.com/LLLin000/PaperForge/issues/70). Their outputs unblock the control-center and maintenance prototypes; do not start production implementation before #73 closes.
|
||||
## 1. Architecture
|
||||
|
||||
### 1.1 The problem (pre-v2)
|
||||
|
|
@ -76,7 +76,7 @@ raw observations → structural signatures → stable anchors/families → zone
|
|||
| **M metadata search** | ✅ sql.js and Python CLI paths restored |
|
||||
| **@ deep search** | ✅ deep CLI invocation and result-envelope consumption restored |
|
||||
| **Vector build control** | ✅ canonical SQLite lifecycle and health state restored |
|
||||
| **Control-plane contracts** | 🟡 Current-state audit complete; capability vocabulary and managed-runtime decisions pending |
|
||||
| **Control-plane contracts** | 🟡 Audit + Obsidian/desktop reference research complete; capability vocabulary and managed-runtime decisions pending |
|
||||
|
||||
### 2.3 Fix Status
|
||||
|
||||
|
|
@ -147,10 +147,10 @@ The OCR maintenance slice now has a canonical All/Recommended state model, selec
|
|||
Remaining legacy OCR issues (carried forward):
|
||||
## 4. Active Queue
|
||||
|
||||
1. 🔴 **[Control-center Wayfinder](https://github.com/LLLin000/PaperForge/issues/65)** — map charted; current-contract audit resolved.
|
||||
2. 🔴 **[Capability-state vocabulary](https://github.com/LLLin000/PaperForge/issues/69)** — unblocked by the audit; define the shared module-state/action envelope.
|
||||
3. 🟡 **Reference-product research** — complete [Obsidian-native](https://github.com/LLLin000/PaperForge/issues/67) and [desktop runtime/recovery](https://github.com/LLLin000/PaperForge/issues/68) evidence.
|
||||
4. ⏳ **Managed runtime and prototypes** — runtime choice remains blocked on #68; prototypes remain blocked on state/runtime decisions.
|
||||
1. 🔴 **[Control-center Wayfinder](https://github.com/LLLin000/PaperForge/issues/65)** — map charted; audit and external research gates resolved.
|
||||
2. 🔴 **[Capability-state vocabulary](https://github.com/LLLin000/PaperForge/issues/69)** — define the shared module-state, reason-code, severity, transition, and action envelope.
|
||||
3. 🔴 **[Managed runtime](https://github.com/LLLin000/PaperForge/issues/70)** — choose ownership, install, update, rollback, health, and cross-platform contracts using the completed desktop evidence.
|
||||
4. ⏳ **Control-center prototypes** — #71 and #72 remain blocked on #69/#70; #73 integrates the accepted model.
|
||||
5. 🟡 **Downstream tooling** — section-aware chunking and separate figure/table handling.
|
||||
6. ⏳ **Compatibility naming cleanup** — deferred post-release.
|
||||
### 4.1 Immediate Next Steps
|
||||
|
|
@ -162,6 +162,8 @@ Remaining legacy OCR issues (carried forward):
|
|||
- [x] Grill and domain-model the control-center destination
|
||||
- [x] Create the Wayfinder map, eight child tickets, and native dependency graph
|
||||
- [x] Audit current setup, readiness, recovery, cache, and migration contracts ([#66](https://github.com/LLLin000/PaperForge/issues/66))
|
||||
- [x] Study Obsidian-native setup/settings patterns ([#67](https://github.com/LLLin000/PaperForge/issues/67))
|
||||
- [x] Study desktop installation/health/recovery patterns ([#68](https://github.com/LLLin000/PaperForge/issues/68))
|
||||
- [ ] Close or follow up [OCR rebuild: streaming progress + maintenance UI redesign](https://github.com/LLLin000/PaperForge/issues/64)
|
||||
- [ ] Reconcile the remaining slices of [Unified rebuild UX](https://github.com/LLLin000/PaperForge/issues/63)
|
||||
- [ ] #21 Display hash staleness in status output
|
||||
|
|
@ -309,6 +311,8 @@ Remaining legacy OCR issues (carried forward):
|
|||
| 2026-07-14 | Successful updates leave maintenance; quality anomalies are opt-in reports | Routine quality scores create permanent, non-actionable noise. Maintenance shows only stale/failed/corrupt work with a concrete action; unacceptable OCR is reported through a user-reviewed GitHub Issue draft. |
|
||||
| 2026-07-14 | Control-plane facts come from independent backend capability probes | A global setup boolean and frontend inference cannot represent partial readiness or stale state; every module needs a reason code, one primary action, and revision/freshness evidence. |
|
||||
| 2026-07-14 | Preserve durable domain truth; migrate JSON runtime snapshots to versioned caches | SQLite build state and per-paper OCR metadata already support recovery, while independently written JSON snapshots have no coherency or staleness protocol. |
|
||||
| 2026-07-14 | Keep normal readiness in the control center, not permanent status-bar chrome | Obsidian status bars work for active operations and exceptional attention; duplicating six-module health there creates noise and conflicts with the actionable-only maintenance model. |
|
||||
| 2026-07-14 | Diagnostics stay local until the user reviews an issue draft | Docker-style opaque uploads require a support service and hide bundle contents; PaperForge instead exports redacted data locally and opens a prefilled GitHub draft without token storage or automatic submission. |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -407,6 +411,7 @@ python -m ruff check paperforge/worker/ocr_*.py
|
|||
| 2026-07-14 | OCR rebuild streaming + maintenance UX | Added flushed rebuild/redo progress contracts, full keyed redo, cross-platform cooperative stop, canonical All/Recommended filters, selected batch actions, and cache migration. Verification: 99 focused Python tests, 93 plugin tests, typecheck/build, and live 734/700-row Obsidian state with no captured errors. | §2-4 |
|
||||
| 2026-07-14 | Control-center Wayfinder charted | Defined the six-module destination (安装 / 文献库 / OCR / 记忆 / 维护 / 帮助), module-independent readiness, actionable-only maintenance, opt-in OCR issue drafts, and a three-ticket research frontier. | [Wayfinder map](https://github.com/LLLin000/PaperForge/issues/65) |
|
||||
| 2026-07-14 | Control-center current-contract audit | Inventoried setup, configuration, runtime, readiness, persistence, cache, failure, and recovery contracts across plugin and Python. Closed #66 with a ranked contradiction matrix and preserve/migrate/remove plan; unblocked #69 while #70 remains blocked on desktop/runtime evidence. | [Contract audit](https://github.com/LLLin000/PaperForge/issues/66#issuecomment-4968837257) |
|
||||
| 2026-07-14 | Obsidian and desktop recovery research | Closed #67 and #68 with primary-source pattern reports. Preserved the six-module IA, independent capability probes, Obsidian-managed plugin updates, module-scoped recovery, local diagnostics, and user-reviewed issue drafts; unblocked #70. | [#67](https://github.com/LLLin000/PaperForge/issues/67#issuecomment-4970653461), [#68](https://github.com/LLLin000/PaperForge/issues/68#issuecomment-4970660288) |
|
||||
|
||||
## 9. Historical Detail Archive
|
||||
|
||||
|
|
|
|||
764
docs/research/2026-07-14-desktop-runtime-recovery-patterns.md
Normal file
764
docs/research/2026-07-14-desktop-runtime-recovery-patterns.md
Normal file
|
|
@ -0,0 +1,764 @@
|
|||
# Desktop Runtime / Recovery Patterns for Managed-Runtime Architecture
|
||||
|
||||
**Date:** 2026-07-14
|
||||
**Issue:** [#68 — Research desktop installation/health/recovery patterns](https://github.com/LLLin000/PaperForge/issues/68)
|
||||
**Parent map:** [#65 — Make the PaperForge control center self-explanatory](https://github.com/LLLin000/PaperForge/issues/65)
|
||||
**Input to:** [#70 — Managed-runtime architecture](https://github.com/LLLin000/PaperForge/issues/70)
|
||||
**Products studied:** VS Code, Docker Desktop, Raycast, Ollama, LM Studio, Homebrew, Obsidian (plugin system), Electron autoUpdater
|
||||
|
||||
---
|
||||
|
||||
## Executive summary
|
||||
|
||||
PaperForge requires a managed runtime architecture that coordinates an Obsidian plugin (TypeScript/Electron) and a Python backend across **安装 / 文献库 / OCR / 记忆 / 维护 / 帮助** modules — each with independent health, recovery, and update semantics. This report studies mature desktop tools to extract patterns that apply to PaperForge's constraints:
|
||||
|
||||
1. **Constrained host**: Obsidian plugins run inside an Electron renderer but do not own the host app lifecycle, autoUpdater, or native addon lifecycle. Python backend runs via subprocess or HTTP.
|
||||
2. **Cross-platform**: Windows, macOS, and Linux all primary.
|
||||
3. **Two-layer runtime**: TypeScript plugin + Python backend, each with its own lifecycle.
|
||||
4. **Local-first, privacy-sensitive**: Medical research data; diagnostic export and issue reporting must never leak identifiable content.
|
||||
5. **Derived-artifact-heavy**: OCR outputs, embeddings, vector indices — disposable but expensive to rebuild.
|
||||
|
||||
### Key findings
|
||||
|
||||
| Pattern | Observed in | PaperForge fit |
|
||||
|---|---|---|
|
||||
| **Independent module health probes** with stable reason codes | VS Code (Runtime Status), Docker Desktop (diagnose), Raycast (Extension Diagnostics) | **Adopt**: Backend capability probes own readiness; plugin renders without reclassification |
|
||||
| **Extension isolation principle** | VS Code (Extension Bisect) | **Note**: Binary-search isolation is VS Code-specific. PaperForge retains the principle of systematic diagnostic isolation but does not implement a bisect command. |
|
||||
| **Per-module actionable maintenance, never global OK** | VS Code (per-extension Runtime Status), Raycast (Issue Dashboard per-extension) | **Adopt**: Already aligned with #66 audit finding |
|
||||
| **Reset with graduated destructiveness** | Docker Desktop (restart → clean/purge data → factory reset) | **Adopt**: Clear three-tier recovery model |
|
||||
| **Local diagnostic export with user review before issue creation** | VS Code (prefilled Report Issue), Docker Desktop (local diagnose collection, user uploads ID separately) | **Adopt**: User reviews local diagnostic bundle before creating a GitHub Issue draft. No upload service; no opaque ID. |
|
||||
| **Version pinning / rollback** | Ollama (model tags), VS Code (Install Another Version), Homebrew (pin) | **Adapt**: Tag all model/runtime versions; rollback mechanism to be chosen in #70. Acceptance criteria: restore known-good state. |
|
||||
| **Separate CLI and GUI diagnostics** | LM Studio (lms CLI + Desktop), Docker Desktop (GUI + CLI diagnose) | **Adopt**: Python CLI diagnostics + plugin diagnostics view |
|
||||
| **Auto-update with compatibility gate** | Raycast (API version check), VS Code (compatibility check), Electron (Squirrel/MSIX) | **Adopt**: Never force-update the Python runtime or plugin without compatibility verification |
|
||||
| **JIT model loading with TTL eviction** | LM Studio (JIT Loader + Idle TTL + Auto-Evict), Ollama (single-load scheduler) | **Reject** for \#68 scope (model loading is already handled; revisit if memory pressure becomes a problem) |
|
||||
| **Setup verification / repair commands** | Homebrew (doctor, update-reset), Docker Desktop (factory reset) | **Adopt**: \`paperforge doctor\` as a unified diagnostic entry point |
|
||||
| **Plugin update via Obsidian** | Obsidian (manual community plugin update) | **Accept constraint**: PaperForge plugin uses Obsidian's community plugin update flow. Python backend update mechanism chosen in #70. |
|
||||
| **Rollback via package manager pinning** | Homebrew (pin), Ollama (brew pin / apt-mark hold) | **Note**: Pin pattern prevents accidental upgrades. PaperForge defers rollback mechanism to #70. |
|
||||
|
||||
---
|
||||
|
||||
## 1. Product deep dives
|
||||
|
||||
### 1.1 VS Code (Electron, managed extensions)
|
||||
|
||||
**Runtime ownership model:**
|
||||
- VS Code runs extensions in isolated **Extension Host** processes (Node.js, web, or hybrid). \`extensionKind\` determines placement: \`workspace\` runs on the workspace host, \`ui\` runs near the UI. The host selection prefers Node.js, falls back to web-only hosts. Extensions cannot crash the main editor.
|
||||
- Source: [Extension Host | VS Code Extension API](https://code.visualstudio.com/api/advanced-topics/extension-host)
|
||||
|
||||
**Module health model:**
|
||||
- The **Runtime Status tab** (Extensions view → extension → Runtime Status) shows per-extension: activation time, activation event, and any warnings/errors. Activation time breakdown (code load / activate call / resolved) has been requested but not yet implemented (\#174255).
|
||||
- Extensions are lazily activated via **Activation Events** (\`onCommand\`, \`onView\`, \`onStartupFinished\`, etc.) to avoid startup bloat.
|
||||
- Source: [July 2021 (1.59) — Runtime Status tab](https://code.visualstudio.com/updates/v1_59)
|
||||
|
||||
**Update/rollback behavior:**
|
||||
- **Auto-update:** Extensions update automatically by default (configurable via \`extensions.autoUpdate\`; default delay 2 hours via \`extensions.autoUpdateDelay\`). Auto-update only applies compatible extensions — incompatible ones are held until VS Code itself is updated.
|
||||
- **Rollback:** Right-click → **Install Another Version** → pick any previous version from the marketplace. CLI equivalent: \`code --install-extension publisher.extension@1.2.3\`.
|
||||
- **Pin version:** No explicit pin UI, but installing a specific version and disabling auto-update achieves the same effect.
|
||||
- Source: [Extension Marketplace](https://code.visualstudio.com/docs/configure/extensions/extension-marketplace)
|
||||
|
||||
**Repair/reset actions:**
|
||||
- **Extension Bisect** (Help → Start Extension Bisect): Binary search over enabled extensions to isolate a faulty one in O(log N) steps. Works like \`git bisect\`: answer "Good Now" or "This Is Bad" at each step. First step disables all extensions.
|
||||
- **Disable all extensions**, **Restart Extension Host** (Developer: Reload Window With Extensions Disabled).
|
||||
- **Reset VS Code** (via \`--user-data-dir\` switch or resetting settings to defaults).
|
||||
- Source: [Resolving extension issues with bisect](https://code.visualstudio.com/blogs/2021/02/16/extension-bisect)
|
||||
|
||||
**Diagnostic export:**
|
||||
- **Developer: Show Running Extensions** → copies detailed activation/performance data to clipboard.
|
||||
- Log level setting, developer console (Chromium DevTools), and \`--verbose\` CLI flag.
|
||||
- Extension bisect session state can be shared.
|
||||
|
||||
**Issue-report flow:**
|
||||
- **Report Issue** from Help menu → opens browser with pre-filled template (VS Code version, extensions, logs).
|
||||
- Extensions bisect result can be attached to extension-specific GitHub issues.
|
||||
- GitHub issue templates in each extension repository.
|
||||
|
||||
**Security/privacy boundary:**
|
||||
- Extensions run in the Extension Host process, isolated from the main VS Code process. Multiple extensions typically share one host.
|
||||
- Extensions declare capabilities in their manifest but are not individually permission-gated; users approve the extension as a whole, not per-API-call.
|
||||
- Web extensions run in a tighter sandbox (no Node.js APIs).
|
||||
|
||||
**Cross-platform behavior:**
|
||||
- Extensions host architecture identical across Windows, macOS, Linux.
|
||||
- Remote extensions (WSL, SSH, Dev Containers) require a window reload after update instead of the local in-process restart.
|
||||
|
||||
---
|
||||
|
||||
### 1.2 Docker Desktop (VM-based runtime, hybrid native+VM)
|
||||
|
||||
**Runtime ownership model:**
|
||||
- Docker Desktop runs a **Linux VM** (via Hyper-V on Windows, Apple Hypervisor on macOS, a qemu VM on Linux) that hosts the Docker engine and containers. The Desktop app is a native Electron/GUI wrapper that communicates with the VM.
|
||||
- The VM is owned and managed by Docker Desktop — users do not directly manage it.
|
||||
|
||||
**Module health model:**
|
||||
- **Dashboard** shows: Engine running, containers status, resource usage (CPU/memory/disk), Kubernetes status, extensions status.
|
||||
- **Settings > Troubleshoot** section groups all health and recovery actions.
|
||||
- Automatic health checks via \`docker desktop status\` (CLI, v4.60+).
|
||||
- Source: [Docker Desktop CLI reference](https://docs.docker.com/desktop/features/desktop-cli/)
|
||||
|
||||
**Update/rollback behavior:**
|
||||
- **Auto-update:** Docker Desktop checks for updates automatically; user is prompted to download and install.
|
||||
- **Rollback:** Manual — download and install a previous version from the release notes page. No built-in rollback command.
|
||||
- **Channel selection:** Stable, Beta, or Dev release channels.
|
||||
- Source: [Docker Desktop release notes](https://docs.docker.com/desktop/release-notes)
|
||||
|
||||
**Repair/reset actions (three-tier):**
|
||||
|
||||
| Tier | Action | What it does | Data loss |
|
||||
|---|---|---|---|
|
||||
| 1 | Restart | Restarts Docker Desktop process | None |
|
||||
| 2 | Clean / Purge data | Removes all containers/images/volumes (reported to also purge volumes — [docker/for-mac#6758](https://github.com/docker/for-mac/issues/6758)) | All container data, images, volumes |
|
||||
| 3 | Reset to factory defaults | Destroys VM, resets all settings, credentials, Kubernetes data; restores to first-install state | Everything |
|
||||
|
||||
- Source: [Troubleshoot Docker Desktop](https://docs.docker.com/desktop/troubleshoot-and-support/troubleshoot/)
|
||||
|
||||
**Diagnostic export:**
|
||||
- **\`docker desktop diagnose\`** (CLI, v4.60+) collects system info, logs, and configuration into a diagnostic bundle.
|
||||
- **\`docker desktop diagnose --upload\`** uploads the bundle and returns a **Diagnostic ID** (UUID/timestamp format).
|
||||
- **In-app:** Troubleshoot > Get support → generates and optionally uploads diagnostics.
|
||||
- Manual fallback: \`com.docker.diagnose\` binary in the install directory.
|
||||
- **Log location:** \`$HOME/.docker/desktop/log/\`
|
||||
- Source: [docker desktop diagnose](https://docs.docker.com/reference/cli/docker/desktop/diagnose/)
|
||||
|
||||
**Issue-report flow:**
|
||||
- Paid users: use Diagnostic ID with Docker Support ticket.
|
||||
- Free users: **report a GitHub bug** with the Diagnostic ID.
|
||||
- The diagnostic ID is a reference to an already-uploaded bundle; it reduces what the user pastes into a ticket but does not itself create a privacy boundary.
|
||||
- Source: [Troubleshoot Docker Desktop](https://docs.docker.com/desktop/troubleshoot-and-support/troubleshoot/)
|
||||
|
||||
**Security/privacy boundary:**
|
||||
- Docker Desktop runs as a user-level process (not system-wide on modern setups).
|
||||
- Diagnostics bundle may contain container names, image names, and environment variable _names_ (not values, per the privacy policy).
|
||||
- **Restricted Mode** (Docker Desktop settings) can disable usage statistics collection.
|
||||
|
||||
**Cross-platform behavior:**
|
||||
- macOS, Windows, and Linux all run Linux containers inside a managed VM; the hypervisor and storage paths differ by platform.
|
||||
- The **backup/restore** procedure differs per platform: \`docker_data.vhdx\` + WSL distros on Windows; \`Docker.raw\` on macOS/Linux.
|
||||
- \`docker desktop diagnose\` binary path differs per OS.
|
||||
- Source: [Backup and restore Docker Desktop data](https://docs.docker.com/desktop/settings-and-maintenance/backup-and-restore/)
|
||||
|
||||
---
|
||||
|
||||
### 1.3 Raycast (Native macOS desktop, managed extensions)
|
||||
|
||||
**Runtime ownership model:**
|
||||
- Raycast extensions run as **Node.js processes** managed by the Raycast app (AppKit native app). A custom React reconciler translates React updates into native AppKit updates via JSON render tree + JSON Patch diff — extensions run in separate processes, communication happens over the render tree protocol.
|
||||
- Extensions are distributed through the **Raycast Store**. Version and API compatibility are checked before installation.
|
||||
- Source: [How the Raycast API and extensions work](https://www.raycast.com/blog/how-raycast-api-extensions-work)
|
||||
|
||||
**Module health model:**
|
||||
- **Extension Diagnostics** command: Shows:
|
||||
- Loaded Commands (all currently loaded extensions)
|
||||
- Commands with Background Refresh
|
||||
- Latest Events (activation, errors)
|
||||
- **Extension Issues Dashboard** (\`raycast.com/extension-issues\`): Real-time visibility into user-reported errors, with per-issue details (stack trace, breadcrumbs, release dates, environment).
|
||||
- Source: [Changelog — Extension Diagnostics](https://developers.raycast.com/misc/changelog.md)
|
||||
|
||||
**Update/rollback behavior:**
|
||||
- **Auto-update:** Extensions are auto-updated by Raycast only if compatible with the current Raycast version and API version. Users cannot disable auto-update per extension.
|
||||
- **Versioning:** Single implicit "latest" version. No version pinning in the store model.
|
||||
- **Rollback:** Not supported through the official mechanism. Developers must push a fix as a new version.
|
||||
- **Migration:** Raycast provides automated API migrations and deprecation plans for breaking changes.
|
||||
- Source: [Versioning — Raycast Developers](https://developers.raycast.com/information/versioning)
|
||||
|
||||
**Repair/reset actions:**
|
||||
- **Restart Raycast** (clear all extension state).
|
||||
- **Reinstall extension** (uninstall → install).
|
||||
- **Deactivate menu bar commands** without removing them.
|
||||
- **Force production mode** for dev extensions (Preferences > Advanced > "Use Node production environment").
|
||||
|
||||
**Diagnostic export:**
|
||||
- **Extension Diagnostics** command (built-in).
|
||||
- **Developer tools:** DevTools-accessible via React DevTools.
|
||||
- **Console logs** (development only; disabled for store extensions).
|
||||
- **Error overlay** in-app for unhandled exceptions/Promise rejections (production shows a generic message).
|
||||
|
||||
**Issue-report flow:**
|
||||
- **Extension Issues Dashboard** aggregates all user-reported errors by extension.
|
||||
- For developers: per-issue details include stack trace, breadcrumbs, release dates, environment.
|
||||
- No user-initiated issue creation flow — errors are auto-captured.
|
||||
|
||||
**Security/privacy boundary:**
|
||||
- Extensions are sandboxed (no filesystem access beyond what the Raycast API provides).
|
||||
- Environment modes: store extensions run in Node production mode; development extensions run in development mode.
|
||||
- Console logging disabled for store extensions (privacy-conscious).
|
||||
|
||||
**Cross-platform behavior:**
|
||||
- Currently **macOS only**. No Windows/Linux version.
|
||||
|
||||
---
|
||||
|
||||
### 1.4 Ollama (Local AI runtime, Go daemon + CLI)
|
||||
|
||||
**Runtime ownership model:**
|
||||
- Ollama is a **Go daemon** (\`ollama serve\`) that manages a **Llama-based model runner** as a child process. The server is a single-binary with embedded model runner.
|
||||
- **Scheduler** (\`server/sched.go\`): Only one model actively loaded at a time. Load management is single-threaded; already-loaded models handle concurrent requests. GPU-aware placement with eviction/relocation for memory pressure.
|
||||
- Source: [Ollama server routes.go](https://github.com/ollama/ollama/blob/c42e9d24/server/routes.go), [server/sched.go](https://github.com/ollama/ollama/blob/e09b3f9f/server/sched.go)
|
||||
|
||||
**Module health model:**
|
||||
- **\`ollama ps\`** — list running models with their PID, VRAM usage, and expiration.
|
||||
- **\`ollama list\`** — list all downloaded models with tags.
|
||||
- **\`ollama show <model>\`** — detailed model manifest (modelfile, parameters, quantization, digest).
|
||||
- **Graceful shutdown** on SIGINT/SIGTERM — scheduler cleanup, model unload, server shutdown.
|
||||
- Source: [Ollama API docs](https://github.com/ollama/ollama/blob/9db4bdbad6a4981ad761aa2b603e69e8fb83212c/docs/api.md)
|
||||
|
||||
**Update/rollback behavior:**
|
||||
- **Binary updates**: Download new binary from [ollama.com](https://ollama.com) — binary replaces previous.
|
||||
- **Docker**: \`ollama/ollama:0.x.y\` version tags; rollback = restart container with older tag.
|
||||
- **Package manager**: \`brew pin ollama\` (macOS) or \`apt-mark hold ollama\` (Linux) to pin version.
|
||||
- **Model pinning**: Always pin quantization tags (\`llama3.2:8b-instruct-q4_K_M\`); floating tags (\`latest\`) can cause silent regressions.
|
||||
- **Model digest**: Since v0.4.0, models have reliable digest hashes for true rollback verification.
|
||||
- Source: [Ollama GitHub README — model tags and versioning](https://github.com/ollama/ollama?tab=readme-ov-file#model-library); [Ollama API docs — /api/tags and model management](https://github.com/ollama/ollama/blob/c42e9d24/docs/api.md)
|
||||
|
||||
**Repair/reset actions:**
|
||||
- **Restart daemon** (\`ollama serve\`).
|
||||
- **Delete and re-pull model**.
|
||||
- **\`~/.ollama/models\`** — model cache directory; clear individual model blobs to force re-download.
|
||||
- **Graceful shutdown** handles cleanup of running model sessions.
|
||||
|
||||
**Diagnostic export:**
|
||||
- **\`ollama serve\`** runs with visible logs; log level via environment variable (\`OLLAMA_DEBUG=1\`).
|
||||
- GPU discovery and VRAM status logged at startup.
|
||||
- **API endpoints**: \`GET /api/tags\`, \`GET /api/show\`, \`GET /api/ps\` for current state.
|
||||
- Source: [Ollama server routes.go](https://github.com/ollama/ollama/blob/c42e9d24/server/routes.go)
|
||||
|
||||
**Issue-report flow:**
|
||||
- **GitHub Issues** on [ollama/ollama](https://github.com/ollama/ollama).
|
||||
- Debug bundle: \`ollama serve\` logs + model manifest + system info.
|
||||
- Discord community for troubleshooting.
|
||||
|
||||
**Security/privacy boundary:**
|
||||
- Local-only by default; \`OLLAMA_HOST=0.0.0.0\` for network access.
|
||||
- Models run locally; no telemetry by default.
|
||||
- No user authentication mechanism.
|
||||
|
||||
**Cross-platform behavior:**
|
||||
- macOS, Windows, Linux — single binary per platform.
|
||||
- Linux: systemd service file included.
|
||||
- Windows: installer manages PATH and auto-start.
|
||||
- Platform differences in GPU detection (CUDA vs Metal vs ROCm).
|
||||
|
||||
---
|
||||
|
||||
### 1.5 LM Studio (Local AI runtime, Desktop App + daemon + CLI)
|
||||
|
||||
**Runtime ownership model:**
|
||||
- Three-component architecture:
|
||||
- **Desktop App**: GUI for interactive use, model discovery, multi-turn chat, RAG, visual config
|
||||
- **llmster** (headless daemon): Server-native core, suitable for remote/automated environments
|
||||
- **lms** (CLI): Unified control plane for both Desktop and llmster
|
||||
- Model management: **JIT Loader** (load model on first request), **Idle TTL** (~60 minutes default), **Auto-Evict** (unloads older JIT models to free memory).
|
||||
- Source: [LM Studio Developer Docs — CLI, daemon, architecture](https://lmstudio.ai/docs/developer)
|
||||
|
||||
**Module health model:**
|
||||
- **\`lms ps --json\`**: List loaded models with state (Loaded / Idle / Auto-Evict).
|
||||
- **\`lms daemon status\`**: Check daemon health.
|
||||
- **\`lms server start/stop/status\`**: API server lifecycle management.
|
||||
- Server modes: GUI mode (inside desktop app) and Headless mode (standalone \`llmster\` process).
|
||||
- Source: [LM Studio CLI reference — lms ps, lms daemon, model management](https://lmstudio.ai/docs/cli)
|
||||
|
||||
**Update/rollback behavior:**
|
||||
- **Auto-update** within Desktop App (prompts user to download).
|
||||
- **CLI** (\`lms\`) can update independently.
|
||||
- **Rollback**: Manual — download previous version from release archives.
|
||||
- **Model versioning**: No built-in version pinning for downloaded models; rely on Hugging Face commit hashes.
|
||||
|
||||
**Repair/reset actions:**
|
||||
- **Restart daemon** (\`lms daemon restart\`).
|
||||
- **Reinstall model** (delete and re-download).
|
||||
- **Clear cache** (\`~/.lmstudio/models\` — manual cleanup).
|
||||
|
||||
**Diagnostic export:**
|
||||
- **\`lms log stream\`**: Real-time log streaming with options:
|
||||
- \`--source model\` (default): model IO logs
|
||||
- \`--source server\`: HTTP API server logs
|
||||
- \`--source runtime\`: runtime logs
|
||||
- \`--filter input\`, \`--filter output\`: directional filtering
|
||||
- \`--json\`: structured output
|
||||
- \`--stats\`: tok/sec and prediction statistics
|
||||
- **Server logs** stored at \`~/.lmstudio/server-logs\`.
|
||||
- Source: [lms log stream](https://lmstudio.ai/docs/cli/serve/log-stream), [src/subcommands/log.ts](https://github.com/lmstudio-ai/lms/blob/bf809b57/src/subcommands/log.ts)
|
||||
|
||||
**Issue-report flow:**
|
||||
- **GitHub Issues** on [lmstudio-ai/lms](https://github.com/lmstudio-ai/lms) or lmstudio-ai/lmstudio (private).
|
||||
- **Discord community**.
|
||||
- Diagnostic data shared via log stream exports or server-logs directory.
|
||||
|
||||
**Security/privacy boundary:**
|
||||
- API token authentication (optional; \`x-api-key\` header).
|
||||
- Local-only by default.
|
||||
- Desktop App and llmster share the same core; data never leaves the machine.
|
||||
- \`~/.lmstudio/\` config directory contains user settings but no telemetry.
|
||||
|
||||
**Cross-platform behavior:**
|
||||
- macOS, Windows, Linux.
|
||||
- Desktop App and llmster available on all platforms.
|
||||
- GPU backend varies by platform (CUDA, Metal, Vulkan).
|
||||
|
||||
---
|
||||
|
||||
### 1.6 Homebrew (Package manager — included for update/rollback/repair patterns)
|
||||
|
||||
**Runtime ownership model:**
|
||||
- Homebrew is a **Ruby-based package manager**. Formulae (packages) and Casks (GUI apps) are installed into \`/usr/local\` (Intel) or \`/opt/homebrew\` (Apple Silicon) — the **Cellar** for formulae, \`/Applications\` for casks.
|
||||
- **No daemon**; \`brew\` is invoked as a CLI command.
|
||||
|
||||
**Update/rollback behavior:**
|
||||
- **\`brew update\`**: Updates Homebrew itself and formulae indexes from GitHub.
|
||||
- **\`brew upgrade <formula>\`**: Upgrades specific formula to latest.
|
||||
- **\`brew pin <formula>\`**: Prevents a formula from being upgraded.
|
||||
- **\`brew unpin <formula>\`**: Removes the pin.
|
||||
- **\`brew switch <formula> <version>\`**: Switch to an installed older version (if multiple versions are keg-only).
|
||||
- **\`brew update-reset\`**: Destructive — resets Homebrew and taps to origin/master, discarding local changes.
|
||||
- **Auto-cleanup**: Old versions removed ~30 days after upgrade.
|
||||
- Source: [Homebrew Manpage](https://github.com/Homebrew/brew/blob/9b56b133a546d0d40cd6b020290f6a59fc14729f/docs/Manpage.md)
|
||||
|
||||
**Diagnostic/repair:**
|
||||
- **\`brew doctor\`**: Checks system for potential problems. Warns with non-zero exit if issues found. Key checks: untracked files in brew repo, stray config files (e.g., \`.curlrc\`), outdated Homebrew, missing dependencies.
|
||||
- **\`brew missing\`**: Checks for missing formula dependencies.
|
||||
- **\`brew cleanup -n\`**: Dry-run cleanup of old versions.
|
||||
- Source: [Homebrew Common Issues](https://docs.brew.sh/Common-Issues)
|
||||
|
||||
---
|
||||
|
||||
### 1.7 Obsidian plugin system (host environment for PaperForge)
|
||||
|
||||
**Plugin runtime:**
|
||||
- Plugins run as **Electron renderer process JavaScript** (TypeScript compiled to \`main.js\`).
|
||||
- Plugins have broad access: "they can read files, access the internet, and install other programs" — Obsidian cannot restrict permissions by plugin.
|
||||
- **Restricted Mode** (default on) disables all community plugins.
|
||||
- Source: [Plugin security](https://github.com/obsidianmd/obsidian-help/blob/master/en/Extending%20Obsidian/Plugin%20security.md)
|
||||
|
||||
**Plugin update mechanism:**
|
||||
- **No auto-update**: Users must manually go to Settings → Community Plugins → Check for updates → Update individual or all.
|
||||
- **Manual install**: Download ZIP, extract to \`.obsidian/plugins/<plugin-id>/\`.
|
||||
- **Beta plugins**: BRAT plugin manages beta updates (can set frozen version).
|
||||
- Source: [Community plugins](https://obsidian.md/help/community-plugins)
|
||||
|
||||
**Plugin review/diagnostics:**
|
||||
- **Automated scanning** on every plugin version (security, code quality, malware detection).
|
||||
- **Scorecards** for plugin safety (declared access: network, filesystem, clipboard).
|
||||
- **Developer tools**: DevTools (Ctrl+Shift+I) for debugging; Hot Reload plugin for auto-reload during development.
|
||||
- **No built-in diagnostic export** for plugins — rely on console.log and DevTools.
|
||||
- Source: [The future of Obsidian plugins](https://obsidian.md/blog/future-of-plugins/)
|
||||
|
||||
**Key constraint for PaperForge:**
|
||||
- Obsidian plugin API does not expose Electron's \`autoUpdater\`.
|
||||
- Plugins can use native Node addons but these carry ABI/packaging risk across Obsidian versions. Python backend runs via subprocess or HTTP.
|
||||
- No per-plugin permission model — plugins are all-or-nothing.
|
||||
|
||||
---
|
||||
|
||||
### 1.8 Electron autoUpdater (reference for update architecture)
|
||||
|
||||
**Capabilities:**
|
||||
- **macOS**: Uses Squirrel.Mac (ZIP-based updates via JSON feed).
|
||||
- **Windows**: Uses Squirrel.Windows (NuGet packages + RELEASES file) or MSIX (direct MSIX/JSON feed with optional \`allowAnyVersion\` for downgrades).
|
||||
- **Linux**: No built-in updater; use OS package manager.
|
||||
- Source: [Electron autoUpdater docs](https://github.com/electron/electron/blob/main/docs/api/auto-updater.md)
|
||||
|
||||
**Key API:**
|
||||
\`\`\`javascript
|
||||
autoUpdater.setFeedURL({ url: 'https://update.example.com/update' });
|
||||
autoUpdater.checkForUpdates(); // Don't call twice
|
||||
// Events: 'update-available', 'update-downloaded', 'error', 'checking-for-update'
|
||||
autoUpdater.quitAndInstall();
|
||||
\`\`\`
|
||||
|
||||
**Rollback:**
|
||||
- **Squirrel.Windows**: No built-in rollback; uninstall and install old version.
|
||||
- **MSIX**: Downgrades supported when \`allowAnyVersion: true\` is passed to \`setFeedURL()\`.
|
||||
- Source: [Electron autoUpdater](https://electronjs.org/docs/latest/api/auto-updater)
|
||||
|
||||
---
|
||||
|
||||
## 2. Cross-cutting pattern matrix
|
||||
|
||||
### 2.1 Runtime ownership models
|
||||
|
||||
| Product | Runtime type | Process model | Plugin isolation |
|
||||
|---|---|---|---|
|
||||
| VS Code | Electron app | Shared Extension Host process(es) | Process-level isolation (main process separate from host); multiple extensions share a host |
|
||||
| Docker Desktop | VM + native app | VM managed by GUI; CLI communicates with engine | Container-level; VM is the isolation boundary |
|
||||
| Raycast | Native macOS app | Separate Node.js processes per extension | Process-level; React reconciler bridges UI |
|
||||
| Ollama | Go daemon | Single daemon spawns Llama runner subprocess | Subprocess-level; GPU memory is the shared resource |
|
||||
| LM Studio | Desktop + daemon + CLI | Three-component; shared core library | JIT-loaded model processes with TTL eviction |
|
||||
| **PaperForge model** | Obsidian plugin + Python backend | Plugin in Electron renderer, Python as subprocess or REST API (decision in #70) | Process-level between plugin and Python |
|
||||
|
||||
### 2.2 Module health model comparison
|
||||
|
||||
| Product | Health interface | Granularity | Staleness protection |
|
||||
|---|---|---|---|
|
||||
| VS Code | Runtime Status tab | Per-extension | Activation time, event, warnings — refreshed on extension reload |
|
||||
| Docker Desktop | Dashboard + CLI status | Engine, containers, K8s, extensions | Real-time by default |
|
||||
| Raycast | Extension Diagnostics command | Per-extension commands + Background Refresh | Refreshed on command invocation |
|
||||
| Ollama | \`ollama ps\` / API | Per-model process | Real-time by polling |
|
||||
| LM Studio | \`lms ps --json\` | Per-model state (Loaded/Idle/Auto-Evict) | Real-time by polling |
|
||||
| **PaperForge target** | Backend capability probes → plugin renders | Per-module (安装/文献库/OCR/记忆/维护/帮助) | TTL gate with revision/computed_at per response |
|
||||
|
||||
### 2.3 Update / rollback matrix
|
||||
|
||||
| Product | Update mechanism | Rollback mechanism | Version pinning |
|
||||
|---|---|---|---|
|
||||
| VS Code | Auto-update with 2h delay | Install Another Version (UI/CLI) | Disable auto-update per extension |
|
||||
| Docker Desktop | Auto-update prompt | Manual download of old version | Release channel selection (stable/beta/dev) |
|
||||
| Raycast | Auto (implicit, latest only) | Not supported (dev must push fix) | Not supported |
|
||||
| Ollama | Binary download / Docker tag | Docker tag rollback / brew pin | Model tag pinning + digest verification |
|
||||
| LM Studio | App auto-update prompt | Manual download of old version | None for models |
|
||||
| Homebrew | \`brew upgrade\` | None (but \`brew pin\` prevents upgrades) | \`brew pin\` / \`brew switch\` |
|
||||
| Obsidian plugins | Manual (Check for Updates) | Manual (download old version) | BRAT can freeze version |
|
||||
| **PaperForge target** | Auto-check compatibility → prompt | Restore previous version; mechanism chosen in #70 | Version metadata tracked; pinning mechanism chosen in #70 |
|
||||
|
||||
### 2.4 Repair/reset actions (three-tier model)
|
||||
|
||||
| Tier | VS Code | Docker Desktop | Homebrew | PaperForge analog |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Reload Window | Restart Docker Desktop | \`brew update\` | Restart plugin / restart Python backend |
|
||||
| 2 | Extension Bisect | Clean / Purge data | \`brew doctor\` → fix warnings | Module-specific rebuild (OCR redo, re-embed, re-index) |
|
||||
| 3 | Reset settings / \`--user-data-dir\` | Reset to factory defaults | \`brew update-reset\` / reinstall | Module-scoped destructive reset → recreate module's derived artifacts only; source materials preserved |
|
||||
|
||||
### 2.5 Diagnostic export maturity
|
||||
|
||||
| Product | Export method | Content | Privacy boundary |
|
||||
|---|---|---|---|
|
||||
| VS Code | Show Running Extensions (clipboard) | Extension names, versions, activation times, errors | No PII by default; logs may contain file paths |
|
||||
| Docker Desktop | \`docker desktop diagnose --upload\` | System info, logs, config; returns opaque Diagnostic ID | ID is shared with support; bundle uploaded first |
|
||||
| Raycast | Extension Issues Dashboard (server-side) | Stack traces, breadcrumbs, release dates, environment | User reports are auto-captured; developer sees anonymized data |
|
||||
| Ollama | \`OLLAMA_DEBUG=1\` logs | Model manifest, GPU info, server logs | Local-only |
|
||||
| LM Studio | \`lms log stream --json\` | Model IO, server logs, runtime logs with tok/sec stats | Local-only; token-level content in logs (user-review before sharing) |
|
||||
| **PaperForge target** | \`paperforge doctor --export\` | Module capability states, log tail, config (redacted), OCR health | User reviews local diagnostic bundle and redacts before creating a GitHub Issue draft; no upload service |
|
||||
|
||||
---
|
||||
|
||||
## 3. PaperForge fit analysis
|
||||
|
||||
### 3.1 Architectural constraints
|
||||
|
||||
PaperForge operates in a unique architectural position:
|
||||
|
||||
1. **Constrained host**: Obsidian plugin does not own the host app lifecycle or autoUpdater. Native Node addons carry ABI/packaging risk. Python backend runs via subprocess or HTTP.
|
||||
2. **Two-layer system** — the TypeScript plugin renders UI and accepts user input; the Python backend does all computational work (OCR, embedding, memory, sync).
|
||||
3. **Cross-platform required** — Windows, macOS, and Linux all primary.
|
||||
4. **Privacy-sensitive** — medical research literature, may contain patient data in PDFs.
|
||||
5. **Derived artifacts are expensive** — full OCR redo on a 30-page paper takes minutes; full re-embed of 500+ papers takes hours.
|
||||
|
||||
### 3.2 What works for constrained Obsidian plugin + Python backend
|
||||
|
||||
**Patterns that transfer directly:**
|
||||
|
||||
| Pattern | Source | How it applies |
|
||||
|---|---|---|
|
||||
| Independent module health probes | VS Code, Docker Desktop, Raycast | Each PaperForge module (安装/文献库/OCR/记忆/维护/帮助) has its own capability probe returning state + reason_code + action. Plugin renders without reclassification. |
|
||||
| Three-tier repair (restart → rebuild → reset) | Docker Desktop | PaperForge: (1) restart Python backend, (2) rebuild single module's derived artifacts, (3) module-scoped destructive reset that rebuilds all derived artifacts for that module while preserving source materials. |
|
||||
| Local diagnostic export with privacy review | Docker Desktop (diagnostic collection), VS Code (prefilled issue report) | `paperforge doctor --export` generates a local diagnostic bundle; user reviews and redacts before optional GitHub Issue creation. No upload service. |
|
||||
| Version pinning for runtime | Homebrew (pin), Ollama (brew pin / apt-mark hold) | Version metadata tracked; exact pinning and upgrade mechanism chosen in #70. Upgrade should be explicit, never automatic. |
|
||||
| Compatibility gate on update | VS Code (compatible extensions only), Raycast (API version check) | Python backend version vs. plugin API version checked before any update is applied. |
|
||||
| Per-module actionable maintenance | VS Code (per-extension Runtime Status) | Maintenance view shows only modules needing action, with one concrete action per state. No global "everything is fine" indicator. |
|
||||
| User reviews diagnostic before issue creation | VS Code (prefilled Report Issue), Docker (local diagnose, user reviews before sharing ID) | PaperForge creates a GitHub Issue draft; user reviews and redacts before submitting. |
|
||||
|
||||
**Patterns that need adaptation:**
|
||||
|
||||
| Pattern | Source | Adaptation needed |
|
||||
|---|---|---|
|
||||
| Extension isolation principle | VS Code | PaperForge adopts the principle of systematic fault isolation — ability to toggle between known-good and current state — without implementing a literal binary-search command. |
|
||||
| JIT model loading with TTL | LM Studio, Ollama | Reject for this scope: it addresses model memory pressure, not runtime installation or recovery. Revisit only if measurements show a problem. |
|
||||
| Rollback via package manager pin | Homebrew, Ollama | Pin pattern is noted; PaperForge defers runtime management mechanism to #70. |
|
||||
| Host-app update mechanism | Electron, Obsidian | PaperForge cannot own the Obsidian host updater. Plugin updates follow Obsidian's community-plugin flow; backend update ownership is decided in #70. |
|
||||
|
||||
**Patterns to reject:**
|
||||
|
||||
| Pattern | Reason for rejection |
|
||||
|---|---|
|
||||
| Docker-style VM isolation | Overkill for PaperForge; process-level subprocess isolation is sufficient |
|
||||
| Single-implicit-latest-version (Raycast store model) | PaperForge needs explicit version pinning for reproducibility |
|
||||
| Auto-cleanup of old versions (Homebrew 30-day) | Derived artifact rebuild is too expensive to auto-cleanup; must be explicit user action |
|
||||
| Cross-process AppKit rendering (Raycast) | Not applicable; PaperForge renders in Obsidian's existing UI framework |
|
||||
|
||||
### 3.3 Security/privacy boundary implications
|
||||
|
||||
PaperForge holds medical research literature that **may contain identifiable patient information** in PDFs. The diagnostic export pattern must be stricter than general desktop tools:
|
||||
|
||||
1. **All diagnostic exports are opt-in** — never auto-uploaded.
|
||||
2. **Diagnostic bundle structure**:
|
||||
- **Safe fields** (always included): module capability states, plugin version, Python version, config keys (not values), error codes, log severity counts.
|
||||
- **Review-required fields** (user must redact before sharing): log tail lines, paper keys, file paths, environment variable _names_.
|
||||
- **Never-included** (stripped by default): environment variable values, paper titles, PDF text, file contents, network request payloads.
|
||||
3. **GitHub Issue draft** is user-reviewed before submission.
|
||||
|
||||
---
|
||||
|
||||
## 4. Adopt / Adapt / Reject decisions
|
||||
|
||||
### 4.1 Adopt (use as-is with configuration only)
|
||||
|
||||
| # | Decision | Evidence | Rationale |
|
||||
|---|---|---|---|
|
||||
| A1 | **Independent module capability probes** returning states | VS Code Runtime Status, Docker Desktop dashboard, Raycast Extension Diagnostics | Directly resolves the #66 audit finding. Each module probe owns its health. |
|
||||
| A2 | **Three-tier repair model** (restart → module rebuild → module reset) | Docker Desktop Troubleshoot tiers | Graduated escalation. Each tier scoped to a single module. T1 is fast and safe; T3 rebuilds all derived artifacts for that module while preserving source materials. |
|
||||
| A3 | **User reviews diagnostic before GitHub issue creation** | VS Code (prefilled Report Issue), Docker (local diagnose, user shares ID) | Privacy boundary: user sees what will be shared before sharing. PaperForge uses local export + user-reviewed GitHub Issue draft; no upload token service. VS Code's prefilled Report Issue is the closer precedent for the draft pattern. |
|
||||
| A4 | **Compatibility gate before any update** | VS Code, Raycast | Prevents update-induced breakage. |
|
||||
| A5 | **Plugin renders backend facts without reclassification** | VS Code Runtime Status, Raycast Dashboard | Backend emits canonical state; plugin renders them. No frontend heuristic. |
|
||||
| A6 | **Per-module actionable maintenance view** | VS Code, Raycast | Only modules needing action are shown, with exactly one action per state. |
|
||||
| A7 | **Python CLI diagnostic command** (\`paperforge doctor\`) | Homebrew \`doctor\`, Docker Desktop \`diagnose\`, LM Studio \`lms log stream\` | Unified entry point for health check, log export, and repair. |
|
||||
|
||||
### 4.2 Adapt (modify for PaperForge's context)
|
||||
|
||||
| # | Decision | Source pattern | Adaptation |
|
||||
|---|---|---|---|
|
||||
| D1 | **Version pinning** | Homebrew \`pin\`, Ollama model tags | Version metadata should be tracked for runtime and models. Exact pinning mechanism chosen in #70. |
|
||||
| D2 | **Diagnostic isolation principle** | VS Code Extension Bisect | Adopt the principle of systematic isolation (known-good vs. current state) without implementing a bisect command. |
|
||||
| D3 | **Rollback via versioned installs** | VS Code "Install Another Version", Ollama Docker tags | Releases carry version metadata; system tracks previous version identity for rollback. Exact mechanism chosen in #70. |
|
||||
| D4 | **Diagnostic bundle with privacy tiers** | Docker Desktop \`diagnose\` | Three tiers: safe, review-required, never-included. Schema part of diagnostic contract. |
|
||||
| D5 | **Two-channel update** (plugin via Obsidian, backend via \`paperforge update\`) | Electron autoUpdater (two-channel), Obsidian manual updates | Independent channels. Plugin: Obsidian flow. Backend update source chosen in #70. |
|
||||
| D6 | **Idempotent and resumable repair** | Docker Desktop multi-tier, VS Code bisect resumable | Checkpoint state per repair step. Cooperative stop reused from #64. |
|
||||
|
||||
### 4.3 Reject (not applicable or counterproductive)
|
||||
|
||||
| # | Decision | Reason |
|
||||
|---|---|---|
|
||||
| R1 | JIT model loading with TTL eviction (LM Studio/Ollama) | Adds complexity without demonstrated need. Revisit if memory pressure arises. |
|
||||
| R2 | VM-level isolation (Docker Desktop) | Process-level subprocess isolation is sufficient. |
|
||||
| R3 | Single-implicit-latest-version store model (Raycast) | PaperForge needs explicit version pinning for reproducibility. |
|
||||
| R4 | Auto-cleanup of old versions (Homebrew 30-day) | Derived artifact rebuild is too expensive. User explicitly deletes or rebuilds. |
|
||||
| R5 | Cross-process AppKit rendering (Raycast) | PaperForge renders in Obsidian's existing UI. |
|
||||
| R6 | Electron autoUpdater integration | Not accessible from Obsidian plugin context. |
|
||||
| R7 | Per-extension permission model (none exists in Obsidian) | Accept Obsidian's all-or-nothing model; design internal controls to compensate. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Managed-runtime architecture implications for #70
|
||||
|
||||
### 5.1 Runtime identity and command resolver
|
||||
|
||||
**Observation across studied products**: VS Code, Docker Desktop, Raycast, Ollama, LM Studio, and Homebrew each expose a single, well-known runtime identity (code binary, docker CLI + app, Raycast app, ollama binary, lms CLI, brew command). The Obsidian plugin host is the exception — it provides no single runtime identity for its plugins.
|
||||
|
||||
- VS Code: \`code\` binary with well-defined extension host
|
||||
- Docker Desktop: \`docker\` CLI + Desktop app
|
||||
- Raycast: Raycast app owns all extension processes
|
||||
- Ollama: \`ollama\` serves as both CLI and daemon
|
||||
- LM Studio: \`lms\` CLI for all runtime control
|
||||
- Homebrew: \`brew\` command
|
||||
- Obsidian plugins: each loads in the Electron renderer
|
||||
|
||||
**Implication for PaperForge**: The audit finding (#66) that Python and TypeScript resolve conflicting path values must be resolved by selecting **one command runtime**. Two viable models:
|
||||
|
||||
1. **Plugin-invokes-Python via subprocess** (current model but with one canonical resolver). Requires: managed Python runtime installation, version pinning, diagnostic endpoint via CLI.
|
||||
2. **Python-backend-as-service** (REST API from plugin to a local Python HTTP server). Requires: daemon lifecycle management, health check endpoint, port conflict resolution.
|
||||
|
||||
**Recommendation**: Let the evidence inform but do not choose for #70. Both models have precedent (CLI: Homebrew, Ollama; Service: Docker Desktop, LM Studio). The evidence supports either.
|
||||
|
||||
### 5.2 Module health probe contract
|
||||
|
||||
VS Code (Runtime Status), Docker Desktop (Dashboard), and Raycast (Extension Diagnostics) all describe module-health state with a consistent shape:
|
||||
|
||||
\`\`\`
|
||||
module: { state, reason_code, summary, action, revision, computed_at }
|
||||
\`\`\`
|
||||
|
||||
**For PaperForge** (reusing the #66 audit proposal verbatim):
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"module": "ocr",
|
||||
"state": "ready | needs_input | needs_action | running | limited | unavailable | unknown",
|
||||
"reason_code": "stable-machine-code",
|
||||
"summary": "localized-human-summary-key",
|
||||
"action": {
|
||||
"id": "configure_ocr",
|
||||
"kind": "open_setting | run_command | open_url | create_issue_draft",
|
||||
"destructive": false
|
||||
},
|
||||
"source_revision": "sha256-of-manifest",
|
||||
"computed_at": "2026-07-14T12:00:00Z"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
Rules (confirmed by cross-product patterns):
|
||||
- State is **factual**; plugin never infers severity from counts.
|
||||
- \`unknown\` and stale are **never** success.
|
||||
- Every \`needs_action\` has **exactly one** primary next action.
|
||||
- Destructive actions declare data preserved, replaced, and rollback availability.
|
||||
|
||||
### 5.3 Update and rollback contract
|
||||
|
||||
From VS Code, Ollama, Homebrew, and Electron patterns:
|
||||
|
||||
| Operation | Requirement |
|
||||
|---|---|
|
||||
| **Discovery** | Expose an explicit update check. Whether checks also run automatically, and at what cadence, is a product decision for #70. |
|
||||
| **Compatibility gate** | Before any download, verify compatibility: plugin API ↔ backend version, backend ↔ embedding model, schema version ↔ data. |
|
||||
| **Download** | Atomic download with checksum verification. Resume support for large downloads (models). |
|
||||
| **Apply** | Plugin follows Obsidian's update flow. Backend application mechanism is chosen in #70 and must be atomic. |
|
||||
| **Rollback** | Restore previous known-good state. Version metadata tracked to identify candidates. Exact rollback mechanism chosen in #70. |
|
||||
| **Failure handling** | If update applies but probe returns bad state, auto-suggest rollback. Never leave system in partially-updated state. |
|
||||
|
||||
### 5.4 Diagnostic and issue-report contract
|
||||
|
||||
From VS Code (prefilled Report Issue) and Docker Desktop (local diagnose → user-reviewed ID sharing):
|
||||
|
||||
\`\`\`
|
||||
paperforge doctor # Quick health overview (plugin view)
|
||||
paperforge doctor --export <path> # Full diagnostic bundle to local path
|
||||
paperforge doctor --check <module> # Single module deep probe
|
||||
\`\`\`
|
||||
|
||||
The report flow:
|
||||
1. User experiences an issue.
|
||||
2. User runs \`paperforge doctor --export diagnostics.zip\`.
|
||||
3. User reviews the diagnostic bundle (auto-redacted for PII) and may redact further.
|
||||
4. User selects "Create Issue Draft" → opens a pre-filled GitHub Issue draft with a redacted text summary. The user may manually attach the reviewed export file.
|
||||
5. User reviews the draft and submits manually. No automatic upload; no opaque Diagnostic ID.
|
||||
|
||||
### 5.5 Cross-platform compatibility
|
||||
|
||||
From Docker Desktop and Ollama (both true cross-platform):
|
||||
|
||||
- Python runtime must be managed identically across platforms (mechanism chosen in #70).
|
||||
- File paths: \`paperforge.json\` uses platform-independent path references resolved by the config resolver.
|
||||
- Subprocess signals: \`SIGTERM\` for cooperative stop on Linux/macOS; \`stdin\` control line for Windows (confirmed working in #64).
|
||||
- Diagnostic export: bundle format must be platform-agnostic (JSON + zipped logs).
|
||||
- Update sources must work across platforms; exact mechanism chosen in #70.
|
||||
|
||||
---
|
||||
|
||||
## 6. Acceptance criteria PaperForge can reuse
|
||||
|
||||
### 6.1 Health probe acceptance
|
||||
|
||||
\`\`\`
|
||||
GIVEN a module (e.g., OCR) that is fully set up
|
||||
WHEN the capability probe runs
|
||||
THEN state = "ready", reason_code is stable, action is empty
|
||||
AND computed_at is within TTL of current time
|
||||
|
||||
GIVEN a module with stale derived artifacts
|
||||
WHEN the capability probe runs
|
||||
THEN state = "needs_action", reason_code identifies the artifact type
|
||||
AND action.id is the concrete rebuild command
|
||||
|
||||
GIVEN a module that has never been set up
|
||||
WHEN the capability probe runs
|
||||
THEN state = "needs_input" or "unavailable"
|
||||
AND action.id points to the setup command
|
||||
\`\`\`
|
||||
|
||||
### 6.2 Update acceptance
|
||||
|
||||
\`\`\`
|
||||
GIVEN the current backend version is N
|
||||
WHEN \`paperforge update\` checks for updates
|
||||
THEN it discovers version N+1 with a compatible API contract
|
||||
AND the download produces a checksum-verified artifact
|
||||
|
||||
WHEN the user rolls back to a previous version
|
||||
THEN version N is restored
|
||||
AND all module probes return state that matches version N's known-good state
|
||||
\`\`\`
|
||||
|
||||
### 6.3 Diagnostic export acceptance
|
||||
|
||||
\`\`\`
|
||||
GIVEN a diagnostic export to path X
|
||||
WHEN the user inspects X
|
||||
THEN the bundle contains:
|
||||
- Module capability states for all 6 modules
|
||||
- Plugin version and build metadata
|
||||
- Python backend version
|
||||
- Configuration keys (NOT values for sensitive fields)
|
||||
- Last N lines of log (tagged as "review-required")
|
||||
- Reason codes for any non-ready module
|
||||
|
||||
GIVEN an export marked "review-required" fields
|
||||
WHEN the bundle would be uploaded
|
||||
THEN the user has reviewed and approved the review-required fields
|
||||
\`\`\`
|
||||
|
||||
### 6.4 Repair acceptance
|
||||
|
||||
\`\`\`
|
||||
GIVEN a module in "needs_action" state with a destructive action
|
||||
WHEN the user triggers the action
|
||||
THEN the system warns about data that will be replaced and whether rollback is available
|
||||
AND the action proceeds only after explicit user confirmation
|
||||
|
||||
GIVEN a rebuild operation that can be interrupted
|
||||
WHEN the user sends a stop signal
|
||||
THEN the current operation completes, and the system records a resumable checkpoint
|
||||
AND the module state returns to "needs_action" (not "unknown")
|
||||
\`\`\`
|
||||
|
||||
### 6.5 Cross-platform acceptance
|
||||
|
||||
\`\`\`
|
||||
GIVEN operations on Windows, macOS, and Linux
|
||||
WHEN any health probe, update, repair, or diagnostic operation runs
|
||||
THEN it produces the same capability envelope structure
|
||||
AND it succeeds or fails with the same reason codes
|
||||
AND file paths are platform-independent in diagnostic output
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
## 7. Unresolved questions
|
||||
|
||||
1. **Python calling pattern for #70**: Subprocess vs. REST API? Both precedented. The choice affects health probe design (CLI exit codes vs. HTTP status codes), diagnostic flow, and update mechanism.
|
||||
|
||||
2. **Embedding model update frequency**: If PaperForge's embedding model updates monthly, does updating the model count as a "module action" in the 6-module model, or a sub-action of the 记忆 module?
|
||||
|
||||
3. **Obsidian plugin submission timing**: The automated scanning pipeline (announced by Obsidian) is rolling out. PaperForge must ensure the diagnostic and issue-report flow does not trigger false positives in the scanner (e.g., plugin making network requests must be declared).
|
||||
|
||||
4. **Offline mode**: Not all products handle offline the same way. Docker Desktop, VS Code auto-update, and Raycast need periodic online access for updates. Ollama and LM Studio serve cached models fully offline. PaperForge must document which operations degrade gracefully offline (health probes: yes; updates: no; local diagnostic export: yes).
|
||||
|
||||
5. **Rollback data integrity**: Does rollback restore previously downloaded model weights, or does the user re-download from Hugging Face?
|
||||
|
||||
---
|
||||
|
||||
## Evidence index
|
||||
|
||||
| Finding | Source | URL |
|
||||
|---|---|---|
|
||||
| VS Code Extension Host isolation | VS Code Extension API docs | https://code.visualstudio.com/api/advanced-topics/extension-host |
|
||||
| VS Code Runtime Status tab | VS Code 1.59 release notes | https://code.visualstudio.com/updates/v1_59 |
|
||||
| VS Code Extension Bisect | VS Code blog | https://code.visualstudio.com/blogs/2021/02/16/extension-bisect |
|
||||
| VS Code Install Another Version / rollback | Extension Marketplace docs | https://code.visualstudio.com/docs/configure/extensions/extension-marketplace |
|
||||
| Docker Desktop CLI diagnose | Docker Desktop CLI reference | https://docs.docker.com/desktop/features/desktop-cli/ |
|
||||
| Docker Desktop troubleshoot and reset | Docker Desktop troubleshoot | https://docs.docker.com/desktop/troubleshoot-and-support/troubleshoot/ |
|
||||
| Docker Desktop diagnose command | Docker CLI reference | https://docs.docker.com/reference/cli/docker/desktop/diagnose/ |
|
||||
| Docker Desktop backup/restore | Docker docs | https://docs.docker.com/desktop/settings-and-maintenance/backup-and-restore/ |
|
||||
| Docker Desktop purge data issue | docker/for-mac#6758 | https://github.com/docker/for-mac/issues/6758 |
|
||||
| Raycast extension architecture | Raycast blog | https://www.raycast.com/blog/how-raycast-api-extensions-work |
|
||||
| Raycast extension diagnostics | Raycast changelog | https://developers.raycast.com/misc/changelog.md |
|
||||
| Raycast debug an extension | Raycast dev docs | https://developers.raycast.com/basics/debug-an-extension |
|
||||
| Raycast versioning | Raycast dev docs | https://developers.raycast.com/information/versioning |
|
||||
| Ollama server routes.go | GitHub (source) | https://github.com/ollama/ollama/blob/c42e9d24/server/routes.go |
|
||||
| Ollama server/sched.go | GitHub (source) | https://github.com/ollama/ollama/blob/e09b3f9f/server/sched.go |
|
||||
| Ollama API docs | GitHub | https://github.com/ollama/ollama/blob/9db4bdbad6a4981ad761aa2b603e69e8fb83212c/docs/api.md |
|
||||
| Ollama model tags and versioning | GitHub README | https://github.com/ollama/ollama?tab=readme-ov-file#model-library |
|
||||
| LM Studio developer docs | lmstudio.ai | https://lmstudio.ai/docs/developer |
|
||||
| LM Studio API server | lmstudio.ai | https://lmstudio.ai/docs/api |
|
||||
| LM Studio CLI reference | lmstudio.ai | https://lmstudio.ai/docs/cli |
|
||||
| LM Studio lms log stream | lmstudio.ai | https://lmstudio.ai/docs/cli/serve/log-stream |
|
||||
| Homebrew manpage | GitHub | https://github.com/Homebrew/brew/blob/9b56b133a546d0d40cd6b020290f6a59fc14729f/docs/Manpage.md |
|
||||
| Homebrew common issues | Homebrew docs | https://docs.brew.sh/Common-Issues |
|
||||
| Obsidian community plugins | Obsidian help | https://obsidian.md/help/community-plugins |
|
||||
| Obsidian plugin security | GitHub | https://github.com/obsidianmd/obsidian-help/blob/master/en/Extending%20Obsidian/Plugin%20security.md |
|
||||
| Obsidian future of plugins | Obsidian blog | https://obsidian.md/blog/future-of-plugins/ |
|
||||
| Electron autoUpdater | Electron docs | https://github.com/electron/electron/blob/main/docs/api/auto-updater.md |
|
||||
| Electron updates tutorial | Electron docs | https://github.com/electron/electron/blob/main/docs/tutorial/updates.md |
|
||||
| PaperForge control-center audit | Working tree | \`docs/research/2026-07-14-control-center-contract-audit.md\` |
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Comparative decision table
|
||||
|
||||
| Capability | VS Code | Docker Desktop | Raycast | Ollama | LM Studio | Homebrew | PaperForge target |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Multi-module runtime | Yes: Ext. Host | Yes: VM+GUI | Yes: processes | No: single daemon | Yes: 3 components | No: single tool | Yes: plugin+Python |
|
||||
| Per-module health | Yes: Runtime Status | Yes: Dashboard | Yes: diagnostics cmd | Yes: `ollama ps` | Yes: `lms ps` | No: `brew doctor` is global | Yes: capability probes |
|
||||
| Actionable maintenance | Yes | Yes | Yes | No: manual model management | No: manual model management | Yes | Yes: module-scoped repair |
|
||||
| Update compatibility gate | Yes | No: manual rollback | Yes | No: binary swap | No: manual | Partial: pin holds version | Required: plugin/backend/model |
|
||||
| Rollback mechanism | Install Another Version | Manual download | Not supported | Docker/brew pin | Manual download | `brew switch` | Deferred to #70 |
|
||||
| Diagnostic export | Clipboard | `diagnose --upload` | Issues Dashboard | Debug logs only | `lms log stream` | `brew doctor` text | `paperforge doctor --export` |
|
||||
| Privacy review before share | Prefilled draft | Partial: uploads before ID sharing | No: auto-captured | Local only | Local only | Local only | User reviews local export and draft |
|
||||
| Systematic fault isolation | Extension Bisect | None | None | None | None | None | Principle only; no bisect command |
|
||||
| Cross-platform | Windows/macOS/Linux | Windows/macOS/Linux | macOS | Windows/macOS/Linux | Windows/macOS/Linux | macOS/Linux | Windows/macOS/Linux |
|
||||
| Offline capable | Most features | Engine is local; updates/support need network | Store checks need network | Cached models | Cached models | Updates need network | Health probes and local diagnosis |
|
||||
653
docs/research/2026-07-14-obsidian-native-control-patterns.md
Normal file
653
docs/research/2026-07-14-obsidian-native-control-patterns.md
Normal file
|
|
@ -0,0 +1,653 @@
|
|||
# Obsidian Native Control Patterns — PaperForge Fit Analysis
|
||||
|
||||
- **Date:** 2026-07-14
|
||||
- **Wayfinder ticket:** [Issue #67](https://github.com/LLLin000/PaperForge/issues/67)
|
||||
|
||||
---
|
||||
|
||||
## Research Method
|
||||
|
||||
Six plugins plus official API docs were studied from source code and official documentation:
|
||||
|
||||
| # | Plugin | Version Studied | Repository | Key Areas |
|
||||
|---|--------|----------------|------------|-----------|
|
||||
| 1 | **Dataview** | 0.5.70 | [blacksmithgu/obsidian-dataview](https://github.com/blacksmithgu/obsidian-dataview) | Settings tab, imperative `display()`, feature toggles, refresh debouncing |
|
||||
| 2 | **Templater** | 2.18.1 | [SilentVoid13/Templater](https://github.com/SilentVoid13/Templater) | Per-vault dangerous settings, `ConfirmDangerousSettingModal`, modal-based config, decl settings adoption |
|
||||
| 3 | **Obsidian Git** | 2.38.6 | [Vinzent03/obsidian-git](https://github.com/Vinzent03/obsidian-git) | External runtime detection (`git` binary), `checkRequirements()` pattern, status bar, `displayError()` |
|
||||
| 4 | **Tasks** | 8.0.0 | [obsidian-tasks-group/obsidian-tasks](https://github.com/obsidian-tasks-group/obsidian-tasks) | Dynamic settings from `generalSettings`, debug/log options, heading state tracking, i18n |
|
||||
| 5 | **Obsidian Sample Plugin** | — | [obsidianmd/obsidian-sample-plugin](https://github.com/obsidianmd/obsidian-sample-plugin) | Canonical `PluginSettingTab` implementation |
|
||||
| 6 | **Obsidian Core API** | 1.13.0+ | [docs.obsidian.md](https://docs.obsidian.md/) | Declarative Settings API, SecretStorage, Platform, Notices, Modals, Commands |
|
||||
|
||||
**PaperForge manifest context:** `isDesktopOnly: true`, `minAppVersion: 1.9.0` — confirmed from `manifest.json` in the repo.
|
||||
|
||||
---
|
||||
|
||||
## Pattern Catalogue
|
||||
|
||||
### 1. Settings Tab Architecture
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
**Imperative (v0.9.7+):** Extend `PluginSettingTab`, implement `display()`, build DOM with `new Setting(containerEl)`.
|
||||
|
||||
```ts
|
||||
import { PluginSettingTab, Setting } from 'obsidian';
|
||||
|
||||
class MySettingTab extends PluginSettingTab {
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
new Setting(containerEl)
|
||||
.setName('Toggle')
|
||||
.addToggle(t => t.setValue(val).onChange(async v => { /* save */ }));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Source: [Settings.md — obsidian-developer-docs](https://github.com/obsidianmd/obsidian-developer-docs/blob/31946e5a/en/Plugins/User%20interface/Settings.md)
|
||||
|
||||
**Declarative (1.13.0+):** Override `getSettingDefinitions()`, return typed definition objects. Obsidian handles rendering, search indexing, persistence, and validation.
|
||||
|
||||
```ts
|
||||
class MySettingTab extends PluginSettingTab {
|
||||
getSettingDefinitions() {
|
||||
return [{
|
||||
name: 'Enable feature',
|
||||
control: { type: 'toggle', key: 'enabled' },
|
||||
}];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Source: [Migrate to declarative settings — obsidian-developer-docs](https://github.com/obsidianmd/obsidian-developer-docs/blob/main/en/Plugins/Guides/Migrate%20to%20declarative%20settings.md)
|
||||
|
||||
#### Evidence from Plugins
|
||||
|
||||
| Plugin | Approach | minAppVersion | Notes |
|
||||
|--------|----------|--------------|-------|
|
||||
| Dataview | Imperative `display()` | 0.12+ | Manual tab state, `Setting` per row |
|
||||
| Obsidian Git | Imperative `display()` | 0.12+ | Manual tab, `icon` on settings tab |
|
||||
| Templater | Declarative (recent) + imperative fallback | 1.13.0 (recent) | Dual support via Path B in older versions |
|
||||
| Tasks | Imperative `display()` | 0.12+ | Uses `customFunctions` map for extensibility |
|
||||
|
||||
**Strength of imperative:** Full control over layout, conditional rendering, async loading, custom component composition.
|
||||
**Limitation of imperative:** No built-in search indexing, manual persistence wiring, no keyboard nav.
|
||||
|
||||
**Strength of declarative:** Free search, auto-save, validation, sub-pages, groups with visibility predicates, lists, buttons.
|
||||
**Limitation of declarative:** Requires 1.13.0+, limited to supported control types, `render` callbacks lose auto-save.
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's current `display()` is 2394 lines of imperative code with custom tab bar. This is **unsustainable** — it duplicates the tab infrastructure Obsidian provides natively. Recommend **Path B (dual support)**: adopt declarative `getSettingDefinitions()` for simple controls (toggles, dropdowns, text inputs, secret components) while keeping imperative `render` callbacks for the custom sections (skills list, status health, embed build controls).
|
||||
|
||||
---
|
||||
|
||||
### 2. Tab and Section Navigation
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
Use `.setHeading()` on `Setting` objects to demarcate sections. Official guidance: "General settings should be at the top… should not have a heading."
|
||||
|
||||
```ts
|
||||
new Setting(containerEl).setName("Appearance").setHeading();
|
||||
```
|
||||
|
||||
Source: [Settings.md](https://github.com/obsidianmd/obsidian-developer-docs/blob/31946e5a/en/Plugins/User%20interface/Settings.md)
|
||||
|
||||
For multi-tab layouts, plugins implement **custom CSS-tab toggling** (like PaperForge already does). The declarative API supports **sub-pages** natively via the `page` property in definitions.
|
||||
|
||||
**Obsidian Git** uses a single tab with icons in the settings sidebar (`icon: "git-pull-request"` on the tab class).
|
||||
|
||||
**Tasks** uses heading tracking (`headingOpened: HeadingState`) to remember which sections the user has collapsed, persisted in `data.json`.
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's custom tab bar (`.paperforge-settings-tabs`) is well-implemented. Replace the inline `<style>` injection with entries in `styles.css`. The tab labels should map to PaperForge's six target modules: 安装 / 文献库 / OCR / 记忆 / 维护 / 帮助. There is no canonical six-tab layout among studied plugins, but PaperForge's current 4-tab separation (setup / features / maintenance / release-notes) can be expanded to six by splitting the current 功能 tab into per-module tabs.
|
||||
|
||||
---
|
||||
|
||||
### 3. Onboarding / First-Launch Setup
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
Most plugins **do not have a setup wizard**. They present all settings in the tab and let users configure at will. Exceptions exist for plugins requiring external dependencies:
|
||||
|
||||
**Obsidian Git** uses `init()` → `checkRequirements()` which returns `"missing-git" | "missing-repo" | "valid"`. On `"missing-git"`, it calls `displayError()` with a descriptive Notice. On `"missing-repo"`, it shows a Notice with instructions and offers **commands** (`Create new repo`, `Clone existing repo`) that users can run from the command palette.
|
||||
|
||||
Source: [Obsidian Git main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) lines 600-650
|
||||
|
||||
```ts
|
||||
const result = await this.gitManager.checkRequirements();
|
||||
switch (result) {
|
||||
case "missing-git":
|
||||
this.displayError(`Cannot run git command. Trying to run: '${gitPath}' .`);
|
||||
break;
|
||||
case "missing-repo":
|
||||
new Notice("Can't find a valid git repository. Please create one via the given command or clone an existing repo.", 10000);
|
||||
break;
|
||||
case "valid":
|
||||
this.gitReady = true;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Templater** uses per-vault dangerous settings with a **confirmation modal** (`ConfirmDangerousSettingModal`) before enabling risky operations like system command execution. On mobile (where `Platform.isDesktop` is false), the checkbox is replaced with updated button text.
|
||||
|
||||
Source: [Templater commit 70a24ce](https://github.com/SilentVoid13/Templater/commit/70a24cefece47be55018b989b255b224a9b3b718)
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's dedicated 安装 tab and `PaperForgeSetupModal` fit Obsidian's modal pattern, but initialization is overly coupled to settings rendering. **Adapt the Obsidian Git pattern**: separate startup capability checks from the wizard UI. The control center should expose setup as the primary action for modules that need input; an onboarding-dismissed marker may control first-run presentation, but `setup_complete` must not represent runtime readiness.
|
||||
|
||||
---
|
||||
|
||||
### 4. Dependency Detection & External Runtime Boundaries
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
**Obsidian Git** is the canonical reference. It:
|
||||
|
||||
1. Defines `checkRequirements(): Promise<'missing-git' | 'missing-repo' | 'valid'>`
|
||||
2. In `init()`, creates the appropriate `GitManager` implementation (`SimpleGit` on desktop, `IsomorphicGit` on mobile)
|
||||
3. Uses `Platform.isDesktopApp` to select implementation:
|
||||
```ts
|
||||
get useSimpleGit(): boolean { return Platform.isDesktopApp; }
|
||||
```
|
||||
4. Stores the Git binary path in `localStorage` (via `LocalStorageSettings`), not `data.json`, so it persists across plugin updates
|
||||
5. Reports clear errors via `displayError()` — which calls `new Notice()` with the error message
|
||||
|
||||
Source: [Obsidian Git main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) lines 600-650, `init()` method
|
||||
|
||||
**Templater** on mobile uses a reduced UI (`ConfirmDangerousSettingModal` without checkbox), acknowledging that mobile has different interaction constraints.
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's `resolvePythonExecutable()` already implements a similar strategy — searching for Python in standard locations, checking custom paths, marking stale paths. However, the **error reporting** is mixed: some errors go to `Notice`, others to `console.log`, and the settings tab redraws on every failure. **Adopt the Git pattern exactly**: a dedicated `checkRequirements()` Promise that returns a typed result union, called from `onload()` and re-checkable via command. All external runtime errors should route through a single `displayError()` method that shows a Notice with duration === 8000ms for errors.
|
||||
|
||||
PaperForge's `manifest.json` already has `isDesktopOnly: true`, which means Obsidian blocks loading the plugin on mobile entirely. No Platform guards are needed for the current deployment surface. The `Platform` patterns documented here are a prerequisite reference **if mobile support is ever added**.
|
||||
|
||||
---
|
||||
|
||||
### 5. Error Display Patterns (Notices, Modals, Status Bar)
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
**Notice:** Transient message at bottom of viewport. Duration in ms (default 4000). Use for:
|
||||
- Operation completion (`sync done`, `OCR started`)
|
||||
- Transient errors (`command failed: ...`)
|
||||
- Informational messages (`No results found`)
|
||||
|
||||
```ts
|
||||
new Notice("Message", 5000); // 5 second duration
|
||||
```
|
||||
|
||||
Source: [Notices — docs.obsidian.md](https://docs.obsidian.md/Plugins/User+interface/Notices)
|
||||
|
||||
**Modal:** Persistent dialog for decisions or multi-step flows. Extend `Modal`, implement `onOpen()` / `onClose()`. Use for:
|
||||
- Privacy/consent dialogs
|
||||
- Multi-field setup steps
|
||||
- Confirmation before destructive operations
|
||||
|
||||
Source: [Modals — docs.obsidian.md](https://docs.obsidian.md/Plugins/User+interface/Modals)
|
||||
|
||||
**Status Bar:** `addStatusBarItem()` returns an `HTMLElement`. Use for persistent status (e.g., git branch, sync time).
|
||||
|
||||
**Obsidian Git** demonstrates all three:
|
||||
- `displayError()` → `new Notice()` with error text
|
||||
- `GeneralModal` for user input (clone URL, directory selection)
|
||||
- `BranchModal` for branch switching
|
||||
- `StatusBar` and `BranchStatusBar` for persistent git state
|
||||
- `setPluginState()` drives status bar content
|
||||
|
||||
Source: [Obsidian Git main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts)
|
||||
|
||||
**Tasks** uses `createFragmentWithHTML` for rich descriptions in settings, and `loggingOptions` with per-module log levels for diagnostics.
|
||||
|
||||
Source: [Tasks Settings.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/Settings.ts)
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge already uses all three patterns but inconsistently:
|
||||
- Notice duration varies (0, 4000, 5000, 6000, 8000 ms) — **standardize**: 4000 for success, 6000 for warnings, 8000 for errors
|
||||
- `console.log` for debug info — **migrate** to `loggingOptions` with per-module levels (adopt Tasks pattern)
|
||||
- Status bar unused — **reserve** it for active operations or one actionable failure; normal readiness remains in the control center
|
||||
- Error messages mix Chinese and English — **keep** i18n through the existing `t()` function
|
||||
|
||||
---
|
||||
|
||||
### 6. Command Availability & Conditional Commands
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
```ts
|
||||
this.addCommand({
|
||||
id: 'command-id',
|
||||
name: 'Command name',
|
||||
callback: () => { /* ... */ }
|
||||
});
|
||||
```
|
||||
|
||||
For conditional availability, use `checkCallback`:
|
||||
|
||||
```ts
|
||||
this.addCommand({
|
||||
id: 'conditional-command',
|
||||
name: 'Do something only when ready',
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (!this.gitReady) return false;
|
||||
if (checking) return true;
|
||||
// perform action
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Source: [Commands — docs.obsidian.md](https://docs.obsidian.md/Plugins/User+interface/Commands)
|
||||
|
||||
**Obsidian Git** uses `isAllInitialized()` as its capability probe — it checks the actual `gitManager` state:
|
||||
|
||||
```ts
|
||||
async isAllInitialized(): Promise<boolean> {
|
||||
if (!this.gitReady) {
|
||||
await this.init({ fromReload: true });
|
||||
}
|
||||
return this.gitReady;
|
||||
}
|
||||
```
|
||||
|
||||
Source: [Obsidian Git main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) lines 770-775
|
||||
|
||||
**Dataview** registers commands with simple callbacks (force refresh, drop cache, rebuild view) — no guards because the commands work regardless of external state.
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge registers commands from `ACTIONS` definition array but uses **no `checkCallback` guards** — every command unconditionally runs `resolvePythonExecutable()`. This means commands appear enabled even when Python is missing.
|
||||
|
||||
**Adopt `checkCallback` with principle-only guidance** — each command's availability must be gated by an independent capability probe, not by `setup_complete`. Probes must be:
|
||||
- **Lightweight**: cached results, not full `execFile` calls on every palette open
|
||||
- **Specific**: each command probes the exact runtime capability it needs
|
||||
- **Re-evaluated**: on settings change or explicit refresh
|
||||
|
||||
**Important:** Path resolution (`resolvePythonExecutable`) is not capability readiness — a file existing at a path does not guarantee the binary works or has the required packages installed. Separate these concerns in the capability model.
|
||||
|
||||
The `setup_complete` flag is for **onboarding flow gating** (whether to show the wizard), not for runtime capability checks. Exact probe design is deferred to Issue #69 (Settings Redesign).
|
||||
|
||||
---
|
||||
|
||||
### 7. Secret/Credential Handling
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
**Current PaperForge pattern:** Stores API keys directly in `data.json` as plaintext:
|
||||
```ts
|
||||
paddleocr_api_key: string;
|
||||
vector_db_api_key: string;
|
||||
```
|
||||
|
||||
Source: [PaperForge constants.ts](https://github.com/LLLin000/PaperForge/blob/master/paperforge/plugin/src/constants.ts)
|
||||
|
||||
**Obsidian 1.11.4+ native pattern:** Use `SecretStorage` + `SecretComponent`:
|
||||
|
||||
```ts
|
||||
import { SecretComponent } from 'obsidian';
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('API key')
|
||||
.addComponent(el => new SecretComponent(this.app, el)
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.onChange(value => {
|
||||
this.plugin.settings.mySetting = value; // stores NAME, not value
|
||||
this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
// At runtime:
|
||||
const secret = app.secretStorage.getSecret(this.settings.mySetting);
|
||||
```
|
||||
|
||||
Source: [Store secrets — obsidian-developer-docs](https://github.com/obsidianmd/obsidian-developer-docs/blob/main/en/Plugins/Guides/Store%20secrets.md)
|
||||
|
||||
**Obsidian Git** stores Git credentials through `LocalStorageSettings` (Obsidian's `app.saveLocalStorage()` API), not in `data.json`. Obsidian Git settings that formerly held credentials are migrated to localStorage.
|
||||
|
||||
Source: [Obsidian Git main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) `migrateSettings()` method at lines 480-500
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
**Adopt SecretStorage** for `paddleocr_api_key` and `vector_db_api_key`. This requires bumping `minAppVersion` from `1.9.0` to at least `1.11.4`.
|
||||
|
||||
**Min-version tradeoff:** PaperForge's current `minAppVersion: 1.9.0` supports Obsidian releases from mid-2024. SecretStorage (1.11.4) was released approximately late 2025. By mid-2026, a 1.11.4 floor is likely sustainable — but the project must explicitly decide:
|
||||
- **Option A:** Bump to 1.11.4, adopt SecretStorage, remove plaintext keys from settings
|
||||
- **Option B:** Stay at 1.9.0, keep plaintext keys, migrate later
|
||||
|
||||
This decision should be informed by PaperForge's actual user version distribution (not available during this research).
|
||||
|
||||
The migration function must: (1) detect existing keys in `data.json`, (2) import them into SecretStorage, (3) remove them from settings on next load. A migration crash must not lose credentials — keep originals in `data.json` until SecretStorage confirms the write.
|
||||
|
||||
---
|
||||
|
||||
### 8. Progressive Disclosure & Collapsible Sections
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
Plugins implement collapsible sections with CSS + click handlers:
|
||||
|
||||
**Tasks:** Uses `headingOpened` in settings to persist which sections are expanded:
|
||||
```ts
|
||||
headingOpened: HeadingState = { 'section-id': true };
|
||||
```
|
||||
|
||||
Source: [Tasks Settings.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/Settings.ts)
|
||||
|
||||
**PaperForge:** Already implements this pattern with `_advCollapsed` + CSS toggle. The `getDisclosureState`/`toggleDisclosureState` utility at `src/utils/disclosure.ts` wraps the pattern cleanly.
|
||||
|
||||
**Templater:** Uses modals for complex settings (folder templates, regex templates) instead of inline collapsible sections, keeping the main settings tab clean.
|
||||
|
||||
Source: [Templater commit 8d41bf9](https://github.com/SilentVoid13/Templater/commit/8d41bf9a35187d221e26fba284ba3a94ba27af8e)
|
||||
|
||||
**Declarative API equivalent:** Groups with `visible` predicates:
|
||||
```ts
|
||||
{
|
||||
type: 'group',
|
||||
name: 'Advanced Settings',
|
||||
visible: () => settings.showAdvanced,
|
||||
children: [ /* ... */ ],
|
||||
}
|
||||
```
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's collapsible "Advanced" section and skills collapsible groups are well-implemented. Suggested improvements:
|
||||
- Persist collapse state in `data.json` (currently `_advCollapsed` lives in the tab instance, so it resets on re-render)
|
||||
- **Consider Templater's pattern**: move complex multi-field configuration (embed build controls, vector DB deps install) into dedicated modals, keeping the settings tab focused
|
||||
|
||||
---
|
||||
|
||||
### 9. Platform Constraints
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
```ts
|
||||
import { Platform } from 'obsidian';
|
||||
// Platform.isDesktop, Platform.isMobile
|
||||
// Platform.isIosApp, Platform.isAndroidApp
|
||||
// Platform.isPhone, Platform.isTablet
|
||||
```
|
||||
|
||||
Source: [Platform API — docs.obsidian.md](https://docs.obsidian.md/Reference/TypeScript+API/Platform)
|
||||
|
||||
**Obsidian Git** uses `Platform.isDesktopApp` to choose between `SimpleGit` (desktop, native binary) and `IsomorphicGit` (mobile, JS implementation).
|
||||
|
||||
**Templater** removes the checkbox in `ConfirmDangerousSettingModal` on mobile and uses updated button text instead.
|
||||
|
||||
Source: [Templater commit 70a24ce](https://github.com/SilentVoid13/Templater/commit/70a24cefece47be55018b989b255b224a9b3b718)
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's `manifest.json` declares `isDesktopOnly: true`, which means Obsidian blocks the plugin from loading on mobile entirely. No runtime Platform guards are needed for the current deployment surface.
|
||||
|
||||
**If mobile support is ever added**, the following patterns become prerequisites:
|
||||
1. All `execFile`/`spawn`/`exec` calls must be guarded by `Platform.isDesktopApp` checks
|
||||
2. Mobile-relevant operations must fall back to a Node.js/WASM implementation (analogous to Obsidian Git's `IsomorphicGit`)
|
||||
3. Python runtime detection must return `"not-available"` on mobile
|
||||
4. The settings tab must handle the absence of Python gracefully
|
||||
|
||||
This report documents the Platform patterns from mature plugins as a **reference for that future decision**, not as a current gap.
|
||||
|
||||
---
|
||||
|
||||
### 10. Diagnostics, Support & Logging
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
**Tasks** provides a structured logging pattern:
|
||||
|
||||
```ts
|
||||
interface LogOptions {
|
||||
minLevels: {
|
||||
'': string; // root logger
|
||||
'tasks': string; // module-level
|
||||
'tasks.Cache': string;
|
||||
'tasks.Events': string;
|
||||
'tasks.File': string;
|
||||
'tasks.Query': string;
|
||||
'tasks.Task': string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Source: [Tasks Settings.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/Settings.ts) lines 90-107
|
||||
|
||||
Each module logs at its configured minimum level (`debug` | `info` | `warn` | `error`). The settings UI exposes an About section with a "Show debug log" button.
|
||||
|
||||
**Dataview** logs version info on load:
|
||||
```ts
|
||||
console.log(`Dataview: version ${this.manifest.version} (requires obsidian ${this.manifest.minAppVersion})`);
|
||||
```
|
||||
|
||||
Source: [Dataview main.ts](https://github.com/blacksmithgu/obsidian-dataview/blob/master/src/main.ts) line 120
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge logs only via `console.log`/`console.warn` with no structured logging. **Adopt the Tasks pattern**: define `LogOptions` with per-module levels (`sync`, `ocr`, `embed`, `memory`, `doctor`) and create a debug section in the 维护 tab that shows recent log entries and provides a "Copy diagnostics" button for support.
|
||||
|
||||
---
|
||||
|
||||
### 11. Settings Persistence & Migration
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
**Tasks** has a mature migration pattern:
|
||||
|
||||
```ts
|
||||
function migrateSettings(loadedSettings: any): Partial<Settings> {
|
||||
const migratedSettings = { ...loadedSettings };
|
||||
if ('includes' in migratedSettings && !('presets' in migratedSettings)) {
|
||||
migratedSettings.presets = migratedSettings.includes;
|
||||
delete migratedSettings.includes;
|
||||
}
|
||||
// Add future migrations here as needed
|
||||
return migratedSettings;
|
||||
}
|
||||
```
|
||||
|
||||
Source: [Tasks Settings.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/Settings.ts)
|
||||
|
||||
**Obsidian Git** also migrates settings in `migrateSettings()`:
|
||||
```ts
|
||||
async migrateSettings(): Promise<void> {
|
||||
if (this.settings.mergeOnPull != undefined) {
|
||||
this.settings.syncMethod = this.settings.mergeOnPull ? "merge" : "rebase";
|
||||
this.settings.mergeOnPull = undefined;
|
||||
await this.saveSettings();
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Source: [Obsidian Git main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts)
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge has no centralized schema-versioned settings migration — defaults are applied with `Object.assign()` which only works for new installations. **Adopt the Tasks migration pattern**: add a `migrateSettings()` function called from `loadSettings()` that handles:
|
||||
- SecretStorage key import (if minAppVersion is bumped)
|
||||
- Adding new default fields to existing configs
|
||||
- Handling `_python_path_stale` cleanup
|
||||
|
||||
---
|
||||
|
||||
### 12. Internationalization (i18n)
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
**Tasks** uses a custom `i18n` module with JSON locale files:
|
||||
```ts
|
||||
import { i18n } from '../i18n/i18n';
|
||||
new Setting(containerEl)
|
||||
.setName(i18n.t('settings.format.displayName.tasksEmojiFormat'))
|
||||
```
|
||||
|
||||
Source: [Tasks SettingsTab.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/SettingsTab.ts)
|
||||
|
||||
**PaperForge** already has an i18n system (`i18n.ts` + `t()` function) covering Chinese and English.
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's i18n is ahead of the majority of Obsidian plugins. Keep it. Minor suggestion: call `setLanguage(this.app)` earlier in `onload()` (before any UI registration) to prevent initial language flash.
|
||||
|
||||
---
|
||||
|
||||
### 13. Release Notes & Update Notification
|
||||
|
||||
#### Obsidian Convention
|
||||
|
||||
Most plugins use `CHANGELOG.md` only. **Obsidian Git** references its `CHANGELOG.md` for release notes.
|
||||
|
||||
**PaperForge** implements a dedicated release-notes tab rendered from a structured `release-notes.json` file, with version comparison against `last_seen_version`. This goes beyond what the studied plugins do.
|
||||
|
||||
#### PaperForge Fit
|
||||
|
||||
PaperForge's release-notes tab is a well-designed feature that should be kept. Minor improvement: visually indicate when a new version has been released since last open (like VS Code's changelog badge), using `last_seen_version` comparison in `_checkReleaseNotes()`.
|
||||
|
||||
---
|
||||
|
||||
## Evidence Matrix
|
||||
|
||||
| Pattern | Source Plugin | Source URL | Observable Code Seam | Strength | Limitation | PaperForge Fit |
|
||||
|---------|--------------|-----------|---------------------|----------|------------|---------------|
|
||||
| Settings Tab (imperative) | Dataview | [src/main.ts](https://github.com/blacksmithgu/obsidian-dataview/blob/master/src/main.ts) | `addSettingTab(new GeneralSettingsTab(...))` | Full DOM control | No search indexing | Keep for complex tabs; adopt declarative for simple controls |
|
||||
| Declarative Settings | Templater | [commit 957a175](https://github.com/SilentVoid13/Templater/commit/957a17541a3bc87eac0fe6e4729ccbeaef42f199) | `getSettingDefinitions()` override | Free search, auto-save, validation | Requires 1.13.0+ | Path B (dual support) |
|
||||
| Dependency Detection | Obsidian Git | [src/main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) L600-660 | `checkRequirements()` → union result | Clear error states, retry flow | N/A | Adopt directly |
|
||||
| External Runtime Selection | Obsidian Git | [src/main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) | `Platform.isDesktopApp` for git impl | Platform-adaptive | N/A | Reference for future mobile support |
|
||||
| Secret/Key Storage | Obsidian Core | [Store secrets guide](https://github.com/obsidianmd/obsidian-developer-docs/blob/main/en/Plugins/Guides/Store%20secrets.md) | `SecretComponent` + `app.secretStorage.getSecret()` | Vault-keyed, cross-plugin, no plaintext | Requires 1.11.4+ | Adopt for API keys; requires minAppVersion decision |
|
||||
| Per-Vault Dangerous Settings | Templater | [commit 957a175](https://github.com/SilentVoid13/Templater/commit/957a175) | `ConfirmDangerousSettingModal`, vault-scoped flags | Social engineering mitigation | More UI clicks | Consider for auto-update/exec features |
|
||||
| Settings Migration | Tasks | [src/Config/Settings.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/Settings.ts) | `migrateSettings()` function | Backward compat, explicit | Must be maintained | Adopt directly |
|
||||
| Structured Logging | Tasks | [src/Config/Settings.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/Settings.ts) L90-107 | `LogOptions.minLevels` per module | Debuggable, user-controllable | Configuration overhead | Adopt for sync/ocr/embed |
|
||||
| Conditional Commands | Obsidian Git | [src/main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) | `isAllInitialized()` guard via `checkCallback` | Prevents no-op execution | N/A | Adopt with independent capability probes |
|
||||
| Status Bar | Obsidian Git | [src/main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) | `addStatusBarItem()` + `StatusBar` class | Persistent, glanceable | Limited real estate | Adopt for sync status |
|
||||
| Error Display | Obsidian Git | [src/main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) | `displayError()` → `new Notice()` | Consistent routing | Duration not standardized | Standardize durations |
|
||||
| Privacy/Consent Modal | PaperForge (self) | [src/views/modals.ts](https://github.com/LLLin000/PaperForge) | `PaperForgeOcrPrivacyModal` | Clear consent flow | N/A | Keep |
|
||||
| Collapsible Sections (persisted) | Tasks | [src/Config/Settings.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/Settings.ts) | `headingOpened: HeadingState` | Persisted state | Minor complexity | Adopt persistence |
|
||||
| Setup Wizard Modal | PaperForge (self) | [src/views/modals.ts](https://github.com/LLLin000/PaperForge) | `PaperForgeSetupModal` | Step-by-step | Coupled to settings tab | Decouple from settings |
|
||||
| Plugin Version Logging | Dataview | [src/main.ts](https://github.com/blacksmithgu/obsidian-dataview/blob/master/src/main.ts) L120 | `console.log` on load | Simple, informative | Not user-visible | Adopt with logging module |
|
||||
| i18n | Tasks | [src/Config/SettingsTab.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/b157cadb/src/Config/SettingsTab.ts) | `i18n.t('key')` throughout | Full localization | Maintenance cost | Keep existing |
|
||||
| Release Notes Tab | PaperForge (self) | [src/settings.ts](https://github.com/LLLin000/PaperForge) | `release-notes.json` + render tab | Structured, version-tracked | N/A | Keep |
|
||||
| Modal-Based Complex Config | Templater | [commit 8d41bf9](https://github.com/SilentVoid13/Templater/commit/8d41bf9) | `FileRegexTemplateModal`, `FolderTemplateModal` | Clean settings tab | Extra modal navigation | Consider for embed build controls |
|
||||
|
||||
---
|
||||
|
||||
## Adopt / Adapt / Reject Decisions
|
||||
|
||||
### Adopt (directly integrate without significant change)
|
||||
|
||||
| Pattern | Rationale |
|
||||
|---------|-----------|
|
||||
| `checkRequirements()` result union | Replaces ad-hoc `resolvePythonExecutable` + status string checks; enables typed error states |
|
||||
| `migrateSettings()` function | PaperForge has no centralized schema-versioned settings migration; needed for schema evolution |
|
||||
| `LogOptions` with per-module levels | Structured debugging essential for external runtime issues |
|
||||
| `checkCallback` with principle guidance | Prevents users from clicking commands that cannot work; probes must be independent capability checks, not setup_complete. Path resolution ≠ capability readiness |
|
||||
| Minimal status bar for active work or one actionable failure | Keeps transient operation state glanceable without duplicating the normal control-center dashboard |
|
||||
| `displayError()` / `displayMessage()` | Consolidates scattered Notice creation patterns |
|
||||
| Persist collapse state in `data.json` | Currently resets on every re-render |
|
||||
|
||||
### Adapt (modify for PaperForge context)
|
||||
|
||||
| Pattern | Adaptation |
|
||||
|---------|------------|
|
||||
| Declarative Settings (Path B) | Adopt simple controls (toggles, dropdowns, text) while keeping imperative `render` for skills list, embed build, health display |
|
||||
| SecretStorage | API keys → SecretStorage; config URL/model stays in settings. Migration function required. Requires minAppVersion decision (1.9.0 → ≥1.11.4) |
|
||||
| Per-vault dangerous settings | Not applicable to `auto_update_on_startup` — plugin `data.json` is already vault-local per Obsidian's plugin isolation model |
|
||||
| Templater's modal-based complex config | Move embed build controls + vector DB dependency install into dedicated modals instead of inline collapse |
|
||||
| Notice duration standardization | 4000ms success, 6000ms warnings, 8000ms errors — currently inconsistent |
|
||||
|
||||
### Reject (not suitable for PaperForge)
|
||||
|
||||
| Pattern | Rationale |
|
||||
|---------|-----------|
|
||||
| Pure declarative settings (Path A) | PaperForge has too many custom UI components (skills list, live status, embed progress) that can't be expressed declaratively |
|
||||
| Heading tracking for all sections | Overkill for PaperForge's multi-tab layout; current tab-based navigation is sufficient |
|
||||
| Full Svelte/React settings (some plugins use frameworks) | Would require build system changes; PaperForge's vanilla TS approach matches the current `esbuild` setup |
|
||||
|
||||
---
|
||||
|
||||
## Pattern Guidance for the Six PaperForge Modules
|
||||
|
||||
The approved information architecture remains **安装 / 文献库 / OCR / 记忆 / 维护 / 帮助**. This research does not define the final controls; Issues #69 and #71 own that design.
|
||||
|
||||
| Module | Verified current seams | Pattern guidance |
|
||||
|---|---|---|
|
||||
| 安装 | `paperforge/plugin/src/settings.ts`, `views/modals.ts`, `services/python-bridge.ts` | Keep progressive setup and validation in a focused modal. Separate capability checks from settings rendering and from onboarding history. |
|
||||
| 文献库 | Path settings and sync actions in `settings.ts` and `main.ts` | Simple stored values may use declarative settings after a minimum-version decision; live validation and sync actions remain imperative. |
|
||||
| OCR | Privacy modal in `views/modals.ts`; maintenance rendering in `settings.ts` and `services/ocr-maintenance-ui.ts` | Preserve explicit consent. Gate actions on OCR capability facts, not on a global setup flag. |
|
||||
| 记忆 | Runtime snapshots in `services/memory-state.ts`; controls in `settings.ts` | Render backend-owned memory and vector states independently. Keep progress and rebuild actions imperative. |
|
||||
| 维护 | OCR maintenance service, doctor/repair command paths, and maintenance tab rendering | Show only actionable or currently running work. Put advanced diagnostics behind disclosure and keep destructive actions explicit. |
|
||||
| 帮助 | `release-notes.json`, release-notes rendering, and `last_seen_version` | Keep dynamic release notes and support guidance together. Issue reporting must open a user-reviewed draft rather than submit automatically. |
|
||||
|
||||
Across all six modules, reuse one capability vocabulary and one action vocabulary once #69 defines them. Avoid duplicating backend classification rules in the plugin.
|
||||
|
||||
### Implementation Notes (cross-module)
|
||||
|
||||
- **Tab architecture:** Keep the current custom CSS-tab pattern through the redesign; consider declarative sub-pages only if `minAppVersion` is deliberately raised to 1.13.0+.
|
||||
- **Declarative mix:** Simple settings can use declarative definitions; complex live sections remain imperative. Dual support avoids forcing a version decision in this research ticket.
|
||||
- **Settings persistence:** Add a centralized schema-versioned migration path before changing stored settings, especially before any SecretStorage adoption.
|
||||
- **i18n:** Route all new user-facing settings text through the existing `t()` function.
|
||||
|
||||
|
||||
### Cross-Module: Status Bar
|
||||
|
||||
Use the status bar exclusively for active background operations or a single failure needing user attention. Normal readiness state belongs in the control center (dashboard view), not in the status bar.
|
||||
|
||||
Pattern source: [Obsidian Git main.ts](https://github.com/Vinzent03/obsidian-git/blob/master/src/main.ts) uses status bar only for git action state and branch display, driven by `setPluginState()` — it shows the current operation (pulling, committing) when active, and is minimal otherwise.
|
||||
|
||||
### Cross-Module: Conditional Command Availability
|
||||
|
||||
Commands that depend on external runtime availability **must** use `checkCallback` for conditional enablement. Probes must be independent capability checks, not `setup_complete`:
|
||||
|
||||
```ts
|
||||
this.addCommand({
|
||||
id: 'paperforge-sync',
|
||||
name: 'PaperForge: Sync Library',
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (!capabilityProbe.pythonAvailable()) return false;
|
||||
if (checking) return true;
|
||||
// perform sync
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Capability probes must be lightweight (cached results, not subprocess calls) and re-evaluated on settings change or explicit refresh.
|
||||
|
||||
**Important:** Path resolution (`resolvePythonExecutable`) checks that a file exists at the configured path — that is **path resolution, not capability readiness**. A file existing at a path does not guarantee the binary works or has the required packages installed. Separate these concerns:
|
||||
- **Path resolution** → lights-on check (during `checkRequirements()`)
|
||||
- **Capability readiness** → per-command probe (Python can actually run the target module)
|
||||
|
||||
Exact probe design is deferred to Issue #69 (Settings Redesign).
|
||||
|
||||
## Unresolved Questions
|
||||
|
||||
1. **Minimum Obsidian version target:** PaperForge's current `minAppVersion: 1.9.0` predates SecretStorage (1.11.4+) and declarative settings (1.13.0+). Adopting either pattern requires a version bump. This research cannot determine the user version distribution — that data lives in the plugin's Obsidian dashboard stats. The team should check dashboard analytics and decide whether the 1.9.0 floor is still necessary.
|
||||
|
||||
2. **Settings tab DOM weight on lower-end desktops:** The 2394-line `display()` method constructs the full DOM for all tabs on every call. Lazy rendering (build tab DOM only when selected) would improve initial render time.
|
||||
|
||||
3. **SecretStorage migration safety:** Existing users have API keys in `data.json`. The migration must import existing keys into SecretStorage on first launch after upgrade, then remove them from `data.json`. If the migration crashes, users lose credentials. Strategy: keep originals in `data.json` until SecretStorage confirms the write.
|
||||
|
||||
4. **Status bar on mobile (if ever supported):** Obsidian's status bar exists on mobile but has limited space. Follow the same principle: only active operations or a single failure; normal readiness in the control center.
|
||||
|
||||
5. **i18n maintenance burden:** PaperForge's i18n is comprehensive, but adding a new locale requires translating ~200+ strings. Consider whether community contributions for additional locales are expected.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Source URLs
|
||||
|
||||
| Resource | URL |
|
||||
|----------|-----|
|
||||
| Obsidian Settings (imperative) | https://github.com/obsidianmd/obsidian-developer-docs/blob/31946e5a/en/Plugins/User%20interface/Settings.md |
|
||||
| Declarative Settings Migration | https://github.com/obsidianmd/obsidian-developer-docs/blob/main/en/Plugins/Guides/Migrate%20to%20declarative%20settings.md |
|
||||
| Secret Storage Guide | https://github.com/obsidianmd/obsidian-developer-docs/blob/main/en/Plugins/Guides/Store%20secrets.md |
|
||||
| Platform API | https://docs.obsidian.md/Reference/TypeScript+API/Platform |
|
||||
| Notices API | https://docs.obsidian.md/Reference/TypeScript+API/Notice |
|
||||
| Modals API | https://docs.obsidian.md/Reference/TypeScript+API/Modal |
|
||||
| Commands API | https://docs.obsidian.md/Plugins/User+interface/Commands |
|
||||
| Dataview | https://github.com/blacksmithgu/obsidian-dataview |
|
||||
| Templater | https://github.com/SilentVoid13/Templater |
|
||||
| Obsidian Git | https://github.com/Vinzent03/obsidian-git |
|
||||
| Tasks | https://github.com/obsidian-tasks-group/obsidian-tasks |
|
||||
| Sample Plugin | https://github.com/obsidianmd/obsidian-sample-plugin |
|
||||
| PaperForge Manifest | manifest.json in repo (isDesktopOnly: true, minAppVersion: 1.9.0) |
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# OCR-v2 Active Queue
|
||||
> Status: OCR-v2 is stable; OCR maintenance streaming is implemented, the control-center UX is charted, and the current setup/readiness/recovery contract audit is resolved.
|
||||
> Status: OCR-v2 is stable; OCR maintenance streaming is implemented, the control-center UX is charted, and the audit plus both reference-product research gates are resolved.
|
||||
> Last updated: 2026-07-14
|
||||
|
||||
## Current checkpoint
|
||||
|
|
@ -10,6 +10,8 @@
|
|||
- The maintenance tab now exposes all papers plus the canonical `_needs_derived_rebuild()` recommendation set, selected batch actions, an above-table progress state, and full refresh on completion.
|
||||
- Source Corpus data remains authoritative and was not modified during verification. Only the deployed plugin bundle and disposable maintenance cache were refreshed.
|
||||
- [Current-contract audit](https://github.com/LLLin000/PaperForge/issues/66#issuecomment-4968837257) identified the migration boundary: preserve durable OCR/SQLite truth and recovery actions; replace global setup state, duplicate runtime/config resolution, and freshness-free snapshots.
|
||||
- [Obsidian-native research](https://github.com/LLLin000/PaperForge/issues/67#issuecomment-4970653461) preserves the six-module IA, progressive setup, capability-gated commands, and minimal transient status chrome.
|
||||
- [Desktop runtime/recovery research](https://github.com/LLLin000/PaperForge/issues/68#issuecomment-4970660288) establishes module-scoped repair, compatibility-gated updates, local redacted diagnostics, and user-reviewed issue drafts.
|
||||
|
||||
## Verification status
|
||||
|
||||
|
|
@ -21,8 +23,8 @@
|
|||
|
||||
## Frontier
|
||||
|
||||
- [ ] Define the shared module capability-state and action vocabulary in [#69](https://github.com/LLLin000/PaperForge/issues/69), now unblocked by the audit.
|
||||
- [ ] Complete [Obsidian-native patterns](https://github.com/LLLin000/PaperForge/issues/67) and [desktop installation/health/recovery patterns](https://github.com/LLLin000/PaperForge/issues/68); keep the managed-runtime decision blocked until #68 closes.
|
||||
- [ ] Define the shared module capability-state and action vocabulary in [#69](https://github.com/LLLin000/PaperForge/issues/69).
|
||||
- [ ] Choose the managed-runtime ownership, installation, update, rollback, and health contracts in [#70](https://github.com/LLLin000/PaperForge/issues/70); its audit and research blockers are closed.
|
||||
- [ ] Keep routine OCR quality outside maintenance; successful updates leave the queue, while unacceptable results use a user-reviewed GitHub Issue draft.
|
||||
- [ ] Close or follow up [Unified rebuild UX](https://github.com/LLLin000/PaperForge/issues/63) after the OCR slice lands.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue