diff --git a/TODO.md b/TODO.md index e7525ec..867cfe0 100644 --- a/TODO.md +++ b/TODO.md @@ -6,138 +6,7 @@ independently. --- -## ~~1. Template Editor & Per-Entity Template Selection~~ ✅ Done - -### Goal - -Users can create, edit, and manage Liquid templates inside the application. -Categories, tags, and individual posts can select which template to use for -rendering. The bundled templates serve as defaults; user templates override them. - -### Current State - -- Liquid templates are bundled in `src/main/engine/templates/` (3 templates + - partials + macros). -- `PageRenderer` resolves templates from fixed directory roots. -- No user-editable templates, no template CRUD, no per-entity template - selection. -- The `ScriptEngine` + `ScriptsView` combination already implements the exact - pattern needed (file-based storage with YAML metadata, Monaco editor, CRUD, - database index, file sync). - -### Implementation Plan - -#### 1.1 Database Schema - -Add a `templates` table to `schema.ts`: - -| Column | Type | Notes | -|-------------|---------|----------------------------------------------| -| id | text PK | UUID | -| projectId | text FK | References projects | -| slug | text | Unique per project | -| title | text | Display name | -| kind | text | `'post'`, `'list'`, `'not-found'`, `'partial'` | -| filePath | text | Relative path within project `templates/` dir | -| enabled | integer | 0/1 — disabled templates fall back to built-in | -| version | integer | Incremented on each save | -| createdAt | integer | Timestamp | -| updatedAt | integer | Timestamp | - -Add template selection fields: - -- `CategoryMetadata`: add optional `postTemplateSlug` and `listTemplateSlug` - fields (stored in `meta/project.json`). -- Posts table: add optional `templateSlug` column for per-post overrides. -- Tags table: add optional `postTemplateSlug` column for tag-level overrides. - -#### 1.2 Engine Class — `TemplateEngine` - -Follow the `ScriptEngine` pattern exactly: - -- `createTemplate(input)` — write `.liquid` file with YAML frontmatter + - database entry. -- `updateTemplate(id, updates)` — update file + database, increment version. -- `deleteTemplate(id)` — remove file + database entry. -- `getTemplate(id)` / `getAllTemplates()` — read from database, load content - from file. -- `rebuildDatabaseFromFiles()` — scan `templates/` directory, rebuild database - from file metadata. -- `reconcileTemplatesFromGitChanges()` — sync database after git operations. -- `validateTemplate(content)` — attempt Liquid parse, return errors. - -Store templates as `.liquid` files in the project's `templates/` directory with -YAML frontmatter: - -```liquid ---- -id: -projectId: -slug: custom-post -title: Custom Post Layout -kind: post -enabled: true -version: 3 ---- -
-
{{ post.content | markdown }}
-
-``` - -#### 1.3 Template Resolution in PageRenderer - -Modify `PageRenderer` to resolve templates with priority: - -1. Post-specific template override (`posts.templateSlug`) -2. Tag-level template override (first matching tag with a `postTemplateSlug`) -3. Category-level template override (`CategoryMetadata.postTemplateSlug`) -4. Built-in default template - -Add the project's `templates/` directory to `resolvePageRendererTemplateRoots()` -so Liquid's `{% render %}` can find user partials. - -#### 1.4 IPC Handlers - -Register in `handlers.ts`: - -- `templates:create`, `templates:update`, `templates:delete` -- `templates:get`, `templates:getAll` -- `templates:validate` - -Expose in `preload.ts` and update `electronApi.ts` types. - -#### 1.5 UI — `TemplateEditorView` - -Mirror `ScriptsView`: - -- Sidebar activity: add "Templates" icon to ActivityBar. -- Sidebar list: show all templates grouped by kind, with enabled/disabled - state. -- Tab content: Monaco editor with `liquid` or `html` language mode. -- Metadata fields: title, slug, kind dropdown, enabled toggle. -- Actions: save (Ctrl+S), validate syntax, delete. -- Footer: created/updated timestamps. - -#### 1.6 Template Assignment UI - -In `SettingsView`, extend the category metadata section: - -- Add "Post Template" and "List Template" dropdowns per category, populated - from user templates of matching kind. - -In the post editor metadata area: - -- Add optional "Template Override" dropdown (only shows user templates of kind - `post`). - -#### 1.7 Starter Templates - -On project creation, copy the bundled templates into the project's `templates/` -directory so users have a working starting point they can modify. - ---- - -## 2. Post Translation System +## 1. Post Translation System ### Goal @@ -159,7 +28,7 @@ publishing pipeline can generate multilingual output. ### Implementation Plan -#### 2.1 Database Schema +#### 1.1 Database Schema Extend the `posts` table: @@ -172,7 +41,7 @@ No separate junction table needed. A translated post is simply a post with `translationOfId` pointing at its source. This keeps the model simple: each post belongs to exactly one language and optionally references one original. -#### 2.2 YAML Frontmatter +#### 1.2 YAML Frontmatter Extend `postFileUtils.ts` to read/write: @@ -184,7 +53,7 @@ translationOf: On `readPostFile()`, parse these fields. On `writePostFile()`, include them when present. -#### 2.3 PostEngine Extensions +#### 1.3 PostEngine Extensions Add methods: @@ -196,7 +65,7 @@ Add methods: Modify `createPost()` and `updatePost()` to accept and persist the `language` and `translationOfId` fields. -#### 2.4 AI Translation Tools in OpenCodeManager +#### 1.4 AI Translation Tools in OpenCodeManager Add three new methods following the `analyzeMediaImage()` pattern: @@ -221,7 +90,7 @@ Add three new methods following the `analyzeMediaImage()` pattern: Register these as IPC handlers: `chat:detectPostLanguage`, `chat:translatePost`, `chat:generatePostSummary`. -#### 2.5 Import Pipeline Integration +#### 1.5 Import Pipeline Integration In `ImportExecutionEngine`, after a post is imported and published: @@ -234,7 +103,7 @@ In `ImportExecutionEngine`, after a post is imported and published: This is optional and should be configurable per import definition (a checkbox "Auto-detect language and translate" in `ImportAnalysisView`). -#### 2.6 UI — Translation Panel +#### 1.6 UI — Translation Panel In the post editor metadata area, add a "Translations" section: @@ -246,7 +115,7 @@ In the post editor metadata area, add a "Translations" section: In the sidebar post list, optionally show a language badge per post. -#### 2.7 Publishing Pipeline +#### 1.7 Publishing Pipeline In `PageRenderer` and `BlogGenerationEngine`: @@ -256,533 +125,7 @@ In `PageRenderer` and `BlogGenerationEngine`: --- -## 3. MCP Server — Agent-Assisted Content Creation - done - -### Goal - -Host an MCP (Model Context Protocol) server inside the application so external -AI agents (Claude Code, Cursor, etc.) can connect and work with bDS. The core -principle: **read access is open, write access goes through user-reviewable -drafts**. External agents never silently mutate data — they propose changes -that the user accepts or discards inside the agent's UI. - -This enables powerful workflows: an agent can read existing posts, draft a new -one with pre-filled content, propose a Python script or Liquid template, or -suggest image metadata improvements — and the user always has final say before -anything enters the system. - -### Current State - -- `OpenCodeManager` already defines 16 data-access tools and 7 A2UI render - tools with full implementations (`getToolDefinitions()`, `executeTool()`). -- `PreviewServer` provides the architectural pattern for an in-process HTTP - server with lifecycle management. -- Posts already have a `draft` status (DB-only, no disk writes until publish) - — the exact pattern needed for MCP draft posts. -- Scripts and templates are file-first (immediately persisted) — MCP proposals - need an in-memory staging layer before acceptance. -- **`@modelcontextprotocol/sdk` v1.27.1 and `@modelcontextprotocol/ext-apps` - v1.1.2 are installed.** -- **`ProposalStore` engine is implemented and tested (18 tests).** -- **`MCPServer` engine is implemented and tested (70 tests).** Standalone HTTP - server on port 4124, stateless mode (new `McpServer` per request), registers - 5 static resources, 7 resource templates, 8 tools (6 model-facing + - 2 app-only), 3 prompts, and 4 `ui://` review-app resources. -- **`mcp-views.ts` provides review HTML** for posts, scripts, templates, and - metadata diffs via `@modelcontextprotocol/ext-apps` App class. -- **Lifecycle integrated in `main.ts`** — MCP server starts on app ready and - cleans up on before-quit. -- **Origin validation** — rejects requests from non-localhost origins to - prevent DNS rebinding attacks. -- **`accept_proposal` / `discard_proposal` use app-only visibility** via - `registerAppTool` with `visibility: ["app"]` — hidden from the agent LLM. - -### Design Principles - -1. **Use all three MCP primitives** — expose blog data via the appropriate - MCP primitive for each use case: - - **Resources** for passive, read-only data (post content, media metadata, - category/tag lists, blog stats). These are application-controlled — the - host decides when to fetch them. - - **Tools** for parameterized actions (search, drafting, proposing changes). - These are model-controlled — the LLM decides when to call them. - - **Prompts** for user-triggered workflow templates (e.g., "draft a blog - post", "audit content quality"). These surface as slash commands in the - host UI. - -2. **Tool annotations** — every tool declares MCP `annotations` to advertise - its behavior to the host: - - Read-only tools: `{ readOnlyHint: true, openWorldHint: false }` - - Proposal tools: `{ readOnlyHint: false, destructiveHint: false }` - - `accept_proposal`: `{ readOnlyHint: false, destructiveHint: false, - idempotentHint: true }` - - `discard_proposal`: `{ readOnlyHint: false, destructiveHint: true, - idempotentHint: true }` - - All annotations are hints — hosts must not make security decisions based - on them alone, but they help hosts choose appropriate UI (e.g., - auto-approving reads, showing confirmation for destructive actions). - -3. **Tool metadata** — every tool includes `title` (human-readable display - name) and `description` (detailed explanation of what it does and when to - use it). Descriptions are critical — they are what the LLM reads to decide - whether and when to call a tool. - -4. **Draft/Propose pattern for writes via MCP Apps** — every mutation goes - through a user-gated flow using the MCP Apps extension: - - Agent calls a `draft_*` or `propose_*` tool. These tools declare a - `_meta.ui.resourceUri` pointing to a review UI (`ui://` resource). - - The host (Claude Desktop, VS Code, etc.) renders the review app as a - sandboxed iframe inline in the conversation. - - The app shows the proposal (post preview, code block, metadata diff) - with accept/discard buttons. The agent LLM is **not** in the loop. - - User clicks accept → the app calls `accept_proposal` tool via the - MCP App bridge (postMessage) → server commits the change. - - User clicks discard → the app calls `discard_proposal` tool → - server cleans up. - - The tool result flows back to the agent so it can continue. - -5. **Aligned with internal editors** — the MCP App review UIs mirror what - the app's own editors show (post metadata + content, script content + - validation, template content + validation, image metadata diff). This - keeps the user experience consistent whether they work inside bDS or - through an external agent. - -6. **Capability negotiation** — during MCP initialization, the server - declares its supported capabilities: `tools`, `resources`, `prompts`. - For MCP Apps, the extension capability `io.modelcontextprotocol/ui` is - negotiated via the `extensions` field. The server checks whether the - client supports the Apps extension and adjusts proposal tool responses - accordingly (structured preview data for Apps-capable hosts, formatted - text for others). - -7. **Input validation** — all tool inputs are validated at the MCP - boundary (via Zod schemas in `inputSchema`) before forwarding to - engine methods. Do not rely solely on downstream engine validation. - -### Implementation Plan - -#### 3.1 Dependencies - -- `@modelcontextprotocol/sdk` — standard MCP server with transport handling. -- `@modelcontextprotocol/ext-apps` — MCP Apps extension for serving - interactive UI resources. - -#### 3.2 Engine Class — `MCPServer` - -Follow the `PreviewServer` pattern: - -``` -src/main/engine/MCPServer.ts -``` - -- Constructor accepts dependency injection (engines via getters). -- `start(port)` — create standalone HTTP server on port 4124 using - `StreamableHTTPServerTransport` in stateless mode. -- `stop()` — clean shutdown. -- Manages an in-memory `ProposalStore` for pending proposals (scripts, - templates, metadata changes). Posts use the existing draft mechanism instead. -- Serves `ui://` resources for the MCP App review UIs. -- Exposes three MCP primitive types: - - **Resources** — read-only blog data (posts, media, tags, categories, - stats) accessible via URI-based `resources/read`. - - **Tools** — parameterized actions (search, draft, propose, accept, - discard). Agent-facing tools are visible to the LLM; app-internal - tools (`accept_proposal`, `discard_proposal`) are called only by the - MCP App via the App Bridge. - - **Prompts** — user-triggered workflow templates surfaced as slash - commands in the host. - -#### 3.3 Resources (Read-Only Data) - -Expose blog data as MCP Resources using URI templates. Resources are -application-controlled — the host fetches them for context, they are not -actions. Each resource is registered via `resources/list` and read via -`resources/read`. - -| Resource URI | Source | Description | -|----------------------------------|----------------------------------|------------------------------------| -| `bds://posts/{id}` | OpenCodeManager.read_post | Full post content + metadata | -| `bds://posts` | OpenCodeManager.list_posts | Paginated post list | -| `bds://media/{id}` | OpenCodeManager.get_media | Media item metadata | -| `bds://media` | OpenCodeManager.list_media | Paginated media list | -| `bds://tags` | OpenCodeManager.list_tags | All tags with counts | -| `bds://categories` | OpenCodeManager.list_categories | All categories | -| `bds://stats` | OpenCodeManager.get_blog_stats | Blog-wide statistics | -| `bds://posts/{id}/backlinks` | OpenCodeManager.get_post_backlinks | Posts linking to this post | -| `bds://posts/{id}/outlinks` | OpenCodeManager.get_post_outlinks | Posts this post links to | -| `bds://posts/{id}/media` | OpenCodeManager.get_post_media | Media attached to a post | -| `bds://media/{id}/posts` | OpenCodeManager.get_media_posts | Posts using a media item | -| `bds://media/{id}/image` | OpenCodeManager.view_image | Image binary (for visual context) | - -Use `bds://` as the custom URI scheme. Parameterized URIs use MCP resource -templates (`resources/templates/list`). - -Note: `notifications/resources/list_changed` is not emitted because the -server runs in stateless mode (new `McpServer` per request, no persistent -connection). If the server moves to session-based mode in the future, -change notifications should be added. - -List resources (`bds://posts`, `bds://media`) support cursor-based -pagination following the MCP pagination spec. The initial response -includes a `nextCursor` if more results exist; the host passes it back -on the next `resources/read` call. - -#### 3.4 Read Tools (Parameterized Queries) - -Not all read operations fit the Resources model. Parameterized queries -that accept complex search criteria are better modeled as tools with -`readOnlyHint: true`. - -| MCP Tool Name | Source | Annotations | -|------------------------|----------------------------------|------------------------------------------| -| search_posts | OpenCodeManager.search_posts | `readOnlyHint: true, openWorldHint: false` | - -Each tool includes a `title`, a `description` explaining when it should -be used, and a JSON Schema `inputSchema`. `search_posts` accepts query -text, filters (status, category, tag, date range), and pagination -parameters (cursor, limit). - -Exclude `update_post_metadata`, `update_media_metadata` (writes go through -proposals), and A2UI render tools (UI-specific, not useful for external -agents). - -#### 3.5 Proposal Tools (Draft-Based Writes) - -These tools stage content for user review. Each declares a `_meta.ui` field -pointing to a review UI resource, so the host renders an MCP App inline in the -conversation. Proposals have a TTL (e.g., 30 min) and are auto-discarded if -not accepted. All proposal tools use annotations -`{ readOnlyHint: false, destructiveHint: false }`. - -##### 3.5.1 `draft_post` - -Creates a draft post using the existing PostEngine draft workflow. - -**Input:** `{ title, content (markdown), excerpt?, tags?, categoryId? }` - -**Tool definition `_meta`:** -```json -{ "ui": { "resourceUri": "ui://bds/review-post" } } -``` - -**Action:** -1. Call `PostEngine.createPost()` with status `draft`. -2. Set tags and category if provided. -3. Return `{ proposalId (= postId), type: 'draftPost', preview: { title, - excerpt, tags, category, content, wordCount } }`. - -**On user accept (via app):** `PostEngine.publishPost(postId)` — post -transitions to published, markdown file is written to disk. - -**On user discard (via app):** `PostEngine.deletePost(postId)` — draft is -removed from the database. - -##### 3.5.2 `propose_script` - -Stages a Python script in memory for review. - -**Input:** `{ title, content (python source), description? }` - -**Tool definition `_meta`:** -```json -{ "ui": { "resourceUri": "ui://bds/review-script" } } -``` - -**Action:** -1. Validate Python syntax (basic parse check). -2. Store in `ProposalStore` with a generated ID. -3. Return `{ proposalId, type: 'script', preview: { title, slug (generated), - description, content, syntaxValid, syntaxErrors? } }`. - -**On user accept (via app):** `ScriptEngine.createScript()` — file + DB entry -created. - -**On user discard (via app):** Remove from `ProposalStore` — nothing was -persisted. - -##### 3.5.3 `propose_template` - -Stages a Liquid template in memory for review. - -**Input:** `{ title, content (liquid source), kind ('post'|'list'|'not-found'| -'partial') }` - -**Tool definition `_meta`:** -```json -{ "ui": { "resourceUri": "ui://bds/review-template" } } -``` - -**Action:** -1. Validate Liquid syntax via `TemplateEngine.validateTemplate()`. -2. Store in `ProposalStore`. -3. Return `{ proposalId, type: 'template', preview: { title, slug, kind, - content, syntaxValid, syntaxErrors? } }`. - -**On user accept (via app):** `TemplateEngine.createTemplate()` — file + DB -entry created. - -**On user discard (via app):** Remove from `ProposalStore`. - -##### 3.5.4 `propose_media_metadata` - -Stages metadata changes for an existing media item. - -**Input:** `{ mediaId, title?, alt?, caption? }` - -**Tool definition `_meta`:** -```json -{ "ui": { "resourceUri": "ui://bds/review-metadata" } } -``` - -**Action:** -1. Load current metadata via `MediaEngine`. -2. Compute diff (current vs proposed for each changed field). -3. Store in `ProposalStore`. -4. Return `{ proposalId, type: 'mediaMetadata', preview: { filename, - diff: { field, current, proposed }[] } }`. - -**On user accept (via app):** Apply changes via `MediaEngine.updateMedia()`. - -**On user discard (via app):** Remove from `ProposalStore`. - -##### 3.5.5 `propose_post_metadata` - -Stages metadata changes for an existing post (title, excerpt, slug, tags). - -**Input:** `{ postId, title?, excerpt?, slug?, tags? }` - -**Tool definition `_meta`:** -```json -{ "ui": { "resourceUri": "ui://bds/review-metadata" } } -``` - -**Action:** -1. Load current post via `PostEngine`. -2. Compute diff. -3. Store in `ProposalStore`. -4. Return `{ proposalId, type: 'postMetadata', preview: { currentTitle, - diff: { field, current, proposed }[] } }`. - -**On user accept (via app):** Apply via `PostEngine.updatePost()`. - -**On user discard (via app):** Remove from `ProposalStore`. - -#### 3.6 App-Internal Tools (Accept / Discard) - -These tools are called by the MCP App (via the App Bridge's `tools/call` -mechanism), **not** by the agent LLM. They are registered with -`registerAppTool` from `@modelcontextprotocol/ext-apps` using -`visibility: ["app"]`, which signals to compliant hosts that these tools -should not be shown to or invoked by the model. - -##### `accept_proposal` - -**Input:** `{ proposalId }` - -**Annotations:** `{ readOnlyHint: false, destructiveHint: false, -idempotentHint: true }` - -Looks up the proposal type (draftPost, script, template, mediaMetadata, -postMetadata) and executes the commit action: -- draftPost → `PostEngine.publishPost()` -- script → `ScriptEngine.createScript()` -- template → `TemplateEngine.createTemplate()` -- mediaMetadata → `MediaEngine.updateMedia()` -- postMetadata → `PostEngine.updatePost()` - -Returns `{ success, message }`. - -##### `discard_proposal` - -**Input:** `{ proposalId }` - -**Annotations:** `{ readOnlyHint: false, destructiveHint: true, -idempotentHint: true }` - -Executes the cleanup action: -- draftPost → `PostEngine.deletePost()` -- All others → remove from `ProposalStore` - -Returns `{ success, message }`. - -#### 3.7 MCP App Review UIs - -The review UIs are HTML pages served as `ui://` resources by the MCP server. -They render inside the host's sandboxed iframe and use the -`@modelcontextprotocol/ext-apps` App class for bidirectional communication. - -Each review app: -1. Receives the tool result data (proposal preview) from the host. -2. Renders a focused review interface: - - **Post review** (`ui://bds/review-post`): title, metadata fields, - rendered markdown content preview, word count. - - **Script review** (`ui://bds/review-script`): title, syntax-highlighted - Python code, validation status/errors. - - **Template review** (`ui://bds/review-template`): title, kind badge, - syntax-highlighted Liquid code, validation status/errors. - - **Metadata review** (`ui://bds/review-metadata`): side-by-side diff of - current vs proposed values for each changed field. -3. Shows accept and discard buttons. -4. On user action, calls `accept_proposal` or `discard_proposal` via the - App Bridge's `tools/call`. -5. Updates its UI to show the outcome ("Published", "Created", "Discarded"). - -These apps are small, self-contained HTML pages — the "slim MCP apps" concept. -They can share a common layout/style and differ only in the content they -render. Since the host provides the sandboxing, the apps don't need their own -authentication or security layer. - -The review UIs should visually align with what the bDS internal editors show, -so the user experience is consistent. Where practical, share CSS/component -patterns between the bDS renderer UI and the MCP App review UIs. - -#### 3.8 ProposalStore - -In-memory store for pending proposals (not posts — those use the DB draft -mechanism): - -```typescript -interface Proposal { - id: string; - type: 'script' | 'template' | 'mediaMetadata' | 'postMetadata'; - data: Record; // type-specific payload - createdAt: number; - ttlMs: number; // default 30 minutes -} -``` - -- Simple `Map` with periodic cleanup of expired entries. -- On app shutdown, all pending proposals are discarded (they were never - committed). -- No persistence needed — proposals are ephemeral by design. - -For draft posts, the `proposalId` is the post ID itself. The `ProposalStore` -tracks a mapping from `proposalId → 'draftPost'` so the accept/discard -tools know to call PostEngine rather than look in the store. - -#### 3.9 Transport - -**Streamable HTTP** — standalone HTTP server on port 4124 using -`StreamableHTTPServerTransport` in stateless mode (new `McpServer` per -request). A single HTTP endpoint at `/mcp` accepts JSON-RPC POST -requests and responds with `application/json` or `text/event-stream` -(SSE). - -**Security:** - -- Bind to `127.0.0.1` (localhost only). -- Validate the `Origin` header on all requests — reject non-localhost - origins to prevent DNS rebinding attacks. -- Requests without an `Origin` header are allowed (CLI tools like curl - and local MCP clients typically do not send one). - -#### 3.10 Lifecycle Integration - -In `main.ts`: - -- Initialize `MCPServer` in `initialize()`. -- Start alongside `PreviewServer` in `app.whenReady()`. -- Stop in `before-quit` handler (discard all pending proposals). -- Respect active project context (tools operate on the active project). - -#### 3.11 Configuration - -In `SettingsView`, add an "MCP Server" section: - -- Enable/disable toggle. -- Port number (for HTTP transport). -- Show connection instructions (stdio command or URL). -- Proposal TTL setting (default 30 min). - -#### 3.12 MCP Prompts (Workflow Templates) - -Expose MCP Prompts for common agent workflows. Prompts are user-controlled — -they surface as slash commands or command palette entries in the host and -produce pre-structured messages that guide the LLM. - -| Prompt Name | Arguments | Description | -|------------------------|----------------------------|--------------------------------------| -| `draft-blog-post` | `topic?`, `category?` | Guides agent through reading existing posts, understanding the blog's style, and drafting a new post on the given topic | -| `improve-media-metadata` | `scope` (`all`\|`missing`) | Guides agent through reviewing media items and proposing alt text, captions, and titles | -| `content-audit` | `category?` | Guides agent through reviewing posts for quality, broken links, missing metadata, and suggesting fixes | - -Each prompt returns a structured message array (system + user messages) that -sets up the LLM with context about the blog and clear instructions for the -workflow. The host renders the prompt as a one-click action. - -This is a lower-priority addition — prompts enhance discoverability but the -core read/write flow works without them. - -#### 3.13 Example Workflows - -**Agent creates a blog post:** -1. Agent LLM reads `bds://categories` and `bds://tags` resources to - understand the blog. -2. Agent LLM calls `draft_post({ title: "...", content: "...", tags: [...] })`. -3. MCP server creates draft post (DB only), returns preview data. -4. Host sees `_meta.ui.resourceUri`, fetches `ui://bds/review-post`, renders - sandboxed iframe inline in conversation. -5. Review app shows the full post preview with accept/discard buttons. -6. User clicks accept → app calls `accept_proposal` via App Bridge → - server publishes post → app shows "Published" → agent is informed. - -**Agent writes a Python script:** -1. Agent LLM reads `bds://posts` resource to understand the content model. -2. Agent LLM calls `propose_script({ title: "Tag Cleanup", content: "..." })`. -3. MCP server validates syntax, stages in ProposalStore, returns preview. -4. Host renders `ui://bds/review-script` — shows syntax-highlighted code - with validation results. -5. User reviews code, clicks accept → app calls `accept_proposal` → - server creates script via ScriptEngine. - -**Agent suggests image metadata:** -1. Agent LLM reads `bds://media/{id}/image` resource to see the image. -2. Agent LLM calls `propose_media_metadata({ mediaId, alt: "...", - caption: "..." })`. -3. MCP server computes diff, stages in ProposalStore, returns diff preview. -4. Host renders `ui://bds/review-metadata` — shows current vs proposed diff. -5. User reviews diff, clicks accept → metadata is updated. - -#### 3.14 MCP Apps Client Support - -MCP Apps are currently supported by Claude (claude.ai), Claude Desktop, -VS Code GitHub Copilot, Goose, Postman, and MCPJam. For hosts that don't -support MCP Apps, the proposal tools still return structured text content -that the agent can present as formatted text — the user would then need -to instruct the agent to accept or discard via conversation, falling back -to a simpler flow. - -For hosts without Apps support, the MCP **Elicitation** primitive -(`elicitation/request`) can serve as a lighter-weight confirmation -mechanism — the server asks the user to confirm or reject a proposal via -a simple dialog rather than a full review UI. This requires the host to -support the `elicitation` capability. When neither Apps nor Elicitation is -available, fall back to text-based accept/discard in the conversation. - -#### 3.15 Testing - -- Unit tests for tool definition mapping (Anthropic → MCP format). -- Unit tests for resource URI resolution and resource template listing. -- Unit tests for `ProposalStore` (create, accept, discard, TTL expiry). -- Unit tests for accept/discard tool handlers. -- Unit tests for tool annotations (verify correct hints on each tool). -- Unit tests for prompt templates (verify message structure and arguments). -- Integration tests: draft_post → app accept flow, propose_script → app - discard flow. -- Integration tests: start MCP server, send tool calls, verify responses. -- Integration tests: capability negotiation (with/without Apps extension). -- Integration tests: resource read, resource list with pagination, resource - change notifications. -- MCP App UI tests: render review apps, simulate user actions, verify - tool calls through App Bridge. -- Security tests: Origin header validation, session management, rate - limiting (for Streamable HTTP transport). -- Follow existing engine test patterns with mocked dependencies. - ---- - -## 4. AI Post Summary, Title & Slug Suggestions +## 2. AI Post Summary, Title & Slug Suggestions ### Goal @@ -804,7 +147,7 @@ handle the metadata. ### Implementation Plan -#### 4.1 Backend — `analyzePost()` in OpenCodeManager +#### 2.1 Backend — `analyzePost()` in OpenCodeManager Add a new method following the `analyzeMediaImage()` pattern: @@ -827,7 +170,7 @@ Add a new method following the `analyzeMediaImage()` pattern: Register IPC handler: `chat:analyzePost`. -#### 4.2 Frontend — Post Editor AI Button +#### 2.2 Frontend — Post Editor AI Button In the post editor metadata area (`Editor.tsx`, around line 720): @@ -837,7 +180,7 @@ In the post editor metadata area (`Editor.tsx`, around line 720): - On click: call `window.electronAPI.chat.analyzePost(postId, projectLanguage)`. - Show `AISuggestionsModal` with the results. -#### 4.3 Extend AISuggestionsModal +#### 2.3 Extend AISuggestionsModal The modal currently supports `title`, `alt`, `caption` fields. Adapt it to also support a post mode with `title`, `excerpt`, `slug` fields: @@ -858,7 +201,7 @@ interface SuggestionField { } ``` -#### 4.4 Applying Suggestions +#### 2.4 Applying Suggestions On "Apply Selected": @@ -868,7 +211,7 @@ On "Apply Selected": - Slug: only apply if post has never been published. Show a warning and disable the checkbox if the post has `publishedAt` set. -#### 4.5 i18n +#### 2.5 i18n Add keys to all 5 locale files: @@ -878,7 +221,7 @@ Add keys to all 5 locale files: - `aiSuggestions.slugLockedWarning` - `postEditor.quickActions`, `postEditor.analyzeWithAI` -#### 4.6 Excerpt Field in Editor +#### 2.6 Excerpt Field in Editor If the excerpt/summary is not currently editable in the post metadata area, add a multi-line text field for it between title and tags. This is needed both @@ -886,7 +229,7 @@ for manual editing and for applying AI suggestions. --- -## 5. Drag-and-Drop Image Insertion +## 3. Drag-and-Drop Image Insertion ### Goal @@ -907,7 +250,7 @@ as markdown images. ### Implementation Plan -#### 5.1 ProseMirror Drop Plugin +#### 3.1 ProseMirror Drop Plugin Create a new plugin in `src/renderer/plugins/dropImagePlugin.ts` following the `imageResolverPlugin` pattern: @@ -934,7 +277,7 @@ export const dropImagePlugin = $prose(() => { }); ``` -#### 5.2 Drop Handler Flow +#### 3.2 Drop Handler Flow For each dropped file: @@ -950,14 +293,14 @@ For each dropped file: 5. **Resolve** — the existing `imageResolverPlugin` will automatically convert the relative path to a `bds-media://` URL for display. -#### 5.3 Visual Feedback +#### 3.3 Visual Feedback - On `dragover` with image files: add a CSS class to the editor container showing a drop zone indicator (border highlight or overlay). - On `dragleave` / `drop`: remove the indicator. - During import (for large files): show a small inline spinner or toast. -#### 5.4 Integration into MilkdownEditor +#### 3.4 Integration into MilkdownEditor In `MilkdownEditor.tsx`, register the new plugin alongside existing plugins: @@ -971,7 +314,7 @@ import { dropImagePlugin } from '../../plugins/dropImagePlugin'; Pass `postId` and the import callback to the plugin via the editor context or a shared ref. -#### 5.5 Paste Support (Optional Extension) +#### 3.5 Paste Support (Optional Extension) The same plugin can handle `paste` events with image files: @@ -979,7 +322,7 @@ The same plugin can handle `paste` events with image files: - Same import → insert → link flow as drop. - This handles screenshots pasted from the clipboard. -#### 5.6 Error Handling +#### 3.6 Error Handling - Non-image files: ignore silently (don't prevent default, let editor handle text drops normally). @@ -987,7 +330,7 @@ The same plugin can handle `paste` events with image files: - Multiple files: process sequentially, insert at cursor position for first, then append after each previous insertion. -#### 5.7 Testing +#### 3.7 Testing - Unit test the plugin's file validation logic. - Integration test: mock `electronAPI.media.import`, verify correct calls and