## Context This plugin embeds the OpenCode AI assistant into Obsidian by spawning a local server process and displaying its web UI in an iframe within the Obsidian sidebar. The design prioritizes simplicity and reliability for the MVP. **Constraints:** - Desktop-only (requires Node.js child_process APIs) - Must work within Obsidian's plugin sandbox - OpenCode CLI must be installed separately by the user ## Goals / Non-Goals **Goals:** - Seamless embedding of OpenCode UI in Obsidian sidebar - Reliable server process lifecycle management - Configurable server and project settings - Quick access via ribbon icon and keyboard shortcut **Non-Goals:** - Mobile support (not possible due to Node.js dependencies) - Native UI rendering (OpenCode provides its own web UI) - Multiple simultaneous OpenCode instances - Deep Obsidian integration (file linking, note references) ## Decisions ### 1. Iframe Embedding vs Native Rendering **Decision:** Embed OpenCode's web UI via iframe rather than building native Obsidian UI. **Rationale:** - Can be up and running quickly - OpenCode already provides a full-featured web interface - Reduces maintenance burden - UI updates come from OpenCode automatically - Allows feature parity with standalone OpenCode web experience **Trade-offs:** - Limited ability to customize UI appearance - Requires CORS configuration (`--cors app://obsidian.md`) - Iframe isolation limits some interactions ### 2. Process State Machine **Decision:** Use a 4-state machine: `stopped` → `starting` → `running` | `error` **Rationale:** - Clear, predictable state transitions - Enables reactive UI that shows appropriate content for each state - Prevents race conditions (can't start while starting) **States:** - `stopped`: Server not running, show start button - `starting`: Spawn initiated, polling health endpoint, show spinner - `running`: Health check passed, show iframe - `error`: Spawn failed or health check timeout, show retry option ### 3. Lazy Start Strategy **Decision:** Start server when view opens, not when plugin loads. **Rationale:** - Faster Obsidian startup (server spawn is ~2-5 seconds) - User may not need OpenCode every session - Auto-start available as opt-in setting for users who prefer it **Implementation:** `OpenCodeView.onOpen()` calls `plugin.startServer()` if state is `stopped`. ### 4. Health Check Polling **Decision:** Poll `/global/health` endpoint every 500ms during startup, with 15-second timeout. **Rationale:** - Server needs time to initialize before accepting connections - Polling is simple and reliable - 15 seconds accommodates slow systems without waiting too long **Trade-offs:** - Slight delay before iframe loads (typically 1-3 seconds) - Could use server stdout parsing instead, but health endpoint is more reliable ### 5. Graceful Shutdown **Decision:** SIGTERM first, SIGKILL after 2-second timeout. **Rationale:** - Allows server to clean up resources gracefully - SIGKILL fallback ensures process doesn't hang - 2 seconds is sufficient for typical cleanup ### 6. Project Directory Encoding **Decision:** Encode project directory as base64 in URL path (e.g., `http://127.0.0.1:14096/{base64-path}`). **Rationale:** - OpenCode server supports multiple projects via URL routing - Base64 handles special characters in paths safely - Allows future support for switching projects without server restart ### 7. Callback-Based State Subscriptions **Decision:** Use callback array pattern for state change notifications. **Rationale:** - Simpler than event emitters or observables for this scale - View subscribes to plugin state changes in `onOpen()` - Immediate notification ensures UI stays in sync **Implementation:** ```typescript plugin.onProcessStateChange((state) => { this.currentState = state; this.updateView(); }); ``` ## Risks / Trade-offs | Risk | Mitigation | |------|------------| | OpenCode CLI not installed | Clear error message with settings link | | Port already in use | Health check detects existing server, reuses it | | Server crash during use | View shows error state, user can retry | | Slow server startup | 15-second timeout with spinner feedback | ## Open Questions - Should we support multiple project directories (tabs/switcher)? - Should settings changes hot-reload the iframe without full restart? - Should we add connection status indicator in header?