diff --git a/openspec/changes/document-mvp-capabilities/design.md b/openspec/changes/document-mvp-capabilities/design.md new file mode 100644 index 0000000..0dfa4ff --- /dev/null +++ b/openspec/changes/document-mvp-capabilities/design.md @@ -0,0 +1,120 @@ +## 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:** +- 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? diff --git a/openspec/changes/document-mvp-capabilities/proposal.md b/openspec/changes/document-mvp-capabilities/proposal.md new file mode 100644 index 0000000..fee34bd --- /dev/null +++ b/openspec/changes/document-mvp-capabilities/proposal.md @@ -0,0 +1,16 @@ +# Change: Document MVP Capabilities + +## Why +The Obsidian OpenCode plugin MVP has been implemented but lacks formal specification. This change retroactively documents the existing functionality to establish a baseline specification that can be expanded as the MVP is polished. + +## What Changes +- **ADDED** `001-mvp-opencode-embed` capability spec covering: + - Server process management (spawn, health check, shutdown, state machine) + - Sidebar view with iframe embedding and state-reactive UI + - Plugin settings with validation + - Commands and ribbon icon integration + +## Impact +- Affected specs: None (new spec) +- Affected code: None (documentation only) +- This is a retroactive documentation effort with no code changes diff --git a/openspec/changes/document-mvp-capabilities/specs/001-mvp-opencode-embed/spec.md b/openspec/changes/document-mvp-capabilities/specs/001-mvp-opencode-embed/spec.md new file mode 100644 index 0000000..7c19eab --- /dev/null +++ b/openspec/changes/document-mvp-capabilities/specs/001-mvp-opencode-embed/spec.md @@ -0,0 +1,139 @@ +## ADDED Requirements + +### Requirement: Server Process Spawning +The plugin SHALL spawn the OpenCode server process using the configured executable path, port, and hostname when the user initiates server start. + +#### Scenario: Successful server spawn +- **WHEN** the user starts the server +- **THEN** the plugin spawns `opencode serve --port --hostname --cors app://obsidian.md` +- **AND** the process runs with the vault directory as the working directory + +### Requirement: Server Health Checking +The plugin SHALL verify server availability by polling a health endpoint during startup. + +#### Scenario: Health check during startup +- **WHEN** the server process is spawned +- **THEN** the plugin polls `GET /global/health` every 500ms +- **AND** transitions to running state when the endpoint returns HTTP 200 +- **AND** transitions to error state if 15 seconds elapse without success + +#### Scenario: Existing server detected +- **WHEN** the user starts the server +- **AND** a server is already running on the configured port +- **THEN** the plugin reuses the existing server +- **AND** transitions directly to running state + +### Requirement: Server Shutdown +The plugin SHALL gracefully terminate the server process when stopping. + +#### Scenario: Graceful shutdown +- **WHEN** the user stops the server +- **THEN** the plugin sends SIGTERM to the process +- **AND** sends SIGKILL after 2 seconds if the process is still running + +### Requirement: Process State Management +The plugin SHALL maintain a state machine with states: stopped, starting, running, and error. + +#### Scenario: State transitions +- **WHEN** the server is not running +- **THEN** the state is `stopped` +- **WHEN** spawn is initiated +- **THEN** the state transitions to `starting` +- **WHEN** health check succeeds +- **THEN** the state transitions to `running` +- **WHEN** spawn fails or health check times out +- **THEN** the state transitions to `error` + +### Requirement: Sidebar View Registration +The plugin SHALL register an ItemView that displays in the Obsidian sidebar. + +#### Scenario: View activation +- **WHEN** the user clicks the ribbon icon or runs the toggle command +- **THEN** the OpenCode view opens in the right sidebar +- **AND** if the view already exists, it is revealed + +### Requirement: View State Rendering +The plugin SHALL render different UI content based on the current process state. + +#### Scenario: Stopped state UI +- **WHEN** the process state is `stopped` +- **THEN** the view displays a "Start OpenCode" button + +#### Scenario: Starting state UI +- **WHEN** the process state is `starting` +- **THEN** the view displays a loading spinner with "Starting OpenCode..." message + +#### Scenario: Running state UI +- **WHEN** the process state is `running` +- **THEN** the view displays a header with controls and an iframe loading the server URL + +#### Scenario: Error state UI +- **WHEN** the process state is `error` +- **THEN** the view displays an error message with "Retry" and "Open Settings" buttons + +### Requirement: Iframe Controls +The plugin SHALL provide controls in the view header when the server is running. + +#### Scenario: Header controls available +- **WHEN** the server is running +- **THEN** the header displays reload, open-in-browser, and stop buttons +- **AND** clicking reload refreshes the iframe +- **AND** clicking open-in-browser opens the server URL in system browser +- **AND** clicking stop terminates the server + +### Requirement: Lazy Server Start +The plugin SHALL start the server automatically when the view is opened if not already running. + +#### Scenario: Auto-start on view open +- **WHEN** the user opens the OpenCode view +- **AND** the server is in `stopped` state +- **THEN** the plugin initiates server start + +### Requirement: Settings Configuration +The plugin SHALL provide configurable settings for server port, hostname, executable path, project directory, and auto-start behavior. + +#### Scenario: Settings persistence +- **WHEN** the user modifies settings +- **THEN** changes are persisted to plugin data +- **AND** the process manager is updated with new settings + +### Requirement: Project Directory Validation +The plugin SHALL validate the project directory setting and support tilde expansion. + +#### Scenario: Valid absolute path +- **WHEN** the user enters an absolute path or path starting with ~ +- **AND** the path exists and is a directory +- **THEN** the setting is saved with the expanded path + +#### Scenario: Invalid path rejection +- **WHEN** the user enters a relative path or non-existent path +- **THEN** a notice is displayed explaining the error +- **AND** the setting is not saved + +### Requirement: Project Directory Auto-Restart +The plugin SHALL restart the server when the project directory setting changes while running. + +#### Scenario: Restart on directory change +- **WHEN** the user changes the project directory +- **AND** the server is currently running +- **THEN** the plugin stops and restarts the server with the new directory + +### Requirement: Commands Registration +The plugin SHALL register commands for toggling the view and controlling the server. + +#### Scenario: Toggle command +- **WHEN** the user runs "Toggle OpenCode panel" command or presses Mod+Shift+O +- **THEN** the view opens if closed, or closes if open + +#### Scenario: Start and stop commands +- **WHEN** the user runs "Start OpenCode server" command +- **THEN** the server starts +- **WHEN** the user runs "Stop OpenCode server" command +- **THEN** the server stops + +### Requirement: Ribbon Icon +The plugin SHALL add a ribbon icon that activates the OpenCode view. + +#### Scenario: Ribbon icon click +- **WHEN** the user clicks the OpenCode ribbon icon +- **THEN** the OpenCode view is activated in the right sidebar diff --git a/openspec/changes/document-mvp-capabilities/tasks.md b/openspec/changes/document-mvp-capabilities/tasks.md new file mode 100644 index 0000000..9d5e4cf --- /dev/null +++ b/openspec/changes/document-mvp-capabilities/tasks.md @@ -0,0 +1,5 @@ +## 1. Documentation +- [x] 1.1 Create proposal.md +- [x] 1.2 Create design.md with architectural decisions +- [x] 1.3 Create specs/001-mvp-opencode-embed/spec.md with requirements and scenarios +- [x] 1.4 Validate with `openspec validate document-mvp-capabilities --strict`