# Notes & Markdown System Upgrade — Plan

> **Author:** Claude Code (plan-only, no source code modified)
> **Date:** 2026-05-26
> **Branch:** feat/opencode-swappable-backend
> **Status:** Draft — awaiting user approval before Phase 1 kickoff

---

## Executive Summary

InfiniBot already has a surprisingly capable notes foundation: a 835-line Notes view with Edit/Split/Preview modes, a custom Notion-style block editor (`<moltbot-block-editor>`) with slash commands and markdown↔blocks conversion, Notion sync plumbing, tags, categories, pinned notes, and internal `moltbot://page/<rowId>` linking. The gap is not the writing surface — it's the *knowledge-graph layer* on top: no `[[wiki-links]]` autocomplete, no backlinks panel, no graph view, no Cmd-K quick-switcher, no full-text search, no daily notes, no templates, and no mobile parity. The Notes view also hasn't been upgraded to use the block editor that's already built. The 6-month vision is to make InfiniBot the single place where all written thought lives — meeting notes, research, decisions, plans, and daily logs — fully searchable, bidirectionally linked, synced to satellites and optionally to Notion, and readable on mobile. The user should never need to open Notion, Obsidian, or Bear for anything that belongs in InfiniBot.

---

## Current State Matrix

| Capability | Notion | Obsidian | InfiniBot Today | Gap |
|---|---|---|---|---|
| Basic markdown render | ✅ | ✅ | ✅ (`toSanitizedMarkdownHtml`, `marked`) | None |
| Edit / Preview toggle | ✅ | ✅ | ✅ (3-way: Edit/Split/Preview) | None |
| Block-based editing (slash commands) | ✅ | Partial (plugins) | ✅ (`<moltbot-block-editor>` exists) | Notes view not wired to it yet |
| Toolbar (B, I, H, list, code, etc.) | ✅ | ✅ | ✅ (9 buttons in Notes view) | Missing table, callout buttons |
| Tags on notes | ✅ | ✅ (frontmatter) | ✅ (`tags[]` field in schema) | No visual tag filter chips |
| Pinned notes | ✅ | ✅ (plugins) | ✅ (`pinned` field) | None |
| Internal page links | ✅ (`@mention`) | ✅ (`[[Page]]`) | Partial (`moltbot://page/rowId`) | No `[[]]` autocomplete or parsing |
| Backlinks panel | ✅ | ✅ | ❌ | Full build needed |
| Graph view | ❌ | ✅ | ❌ | Full build needed |
| Page hierarchy / sub-pages | ✅ | ✅ (folders) | ❌ | `parent_row_id` col exists in DB — needs UI |
| Properties on pages | ✅ | ✅ (frontmatter) | Partial (description, category) | No custom property editor |
| Inline databases inside a note | ✅ | ❌ | ❌ | Slash command → embed needed |
| Full-text search | ✅ | ✅ | ❌ | SQLite FTS5 needed |
| Quick-switcher (Cmd-K) | ✅ | ✅ | ❌ | Global palette needed |
| Daily notes | ❌ (mostly) | ✅ | ❌ | Simple cron + template |
| Templates | ✅ | ✅ | ❌ | File-based + picker |
| Inline image paste + upload | ✅ | ✅ | ❌ | Paste handler + local FS storage |
| URL → rich preview card | ✅ | Plugins | ❌ | OG scraper needed |
| LaTeX rendering | ❌ | ✅ | Partial (KaTeX in bundle) | Not wired to notes |
| Web clipper | ✅ | ✅ | Partial (Chrome ext exists) | Clip-to-notes action missing |
| Notion sync | N/A | ❌ | ✅ (`notionPageId`, `notionSyncedAt`) | One-way pull only; bidirectional TBD |
| Obsidian vault import | ❌ | N/A | ❌ | One-time import script needed |
| Federation / satellite sync | ❌ | ❌ | Partial (infra exists) | Notes not in federation payload yet |
| Real-time collab | ✅ | ❌ | ❌ | Out of scope v1 |
| Version history | ✅ | Git-based | ❌ | Shadow copy table needed |
| Mobile parity | ✅ | ✅ | ❌ | Expo app has no Notes tab |
| Export (PDF, Markdown) | ✅ | ✅ | Partial (`export-row.ts` exists) | PDF missing; markdown export works |
| Plugins / extensibility | ✅ | ✅ | ❌ | v2+ only |

---

## Quick Wins (ranked by effort × impact)

These can each be shipped as a single focused PR without touching the block editor architecture.

| # | Feature | Effort | Impact | Implementation note |
|---|---|---|---|---|
| 1 | **Wire `<moltbot-block-editor>` into Notes view** | 2–4 h | 🔥 Highest | Replace raw `<textarea>` in `notes.ts` (line ~582) with `<moltbot-block-editor>`. It's a drop-in: same `.value` prop, same `change` event, same markdown output. Slash commands and drag-reorder arrive for free. |
| 2 | **`[[Page Title]]` parsing → internal navigation** | 4–8 h | 🔥 High | On save, regex-scan body for `[[...]]`. Resolve against notes list. Write resolved `moltbot://page/<id>` into rendered HTML. In preview, fire existing `navigate` event. No DB schema change needed. |
| 3 | **Tag filter chips in sidebar** | 2–4 h | High | Parse `tags[]` from each note, render clickable chip row above list. Filter list to notes matching selected tags. One-pass client-side — no server change. |
| 4 | **Inline image paste + auto-upload** | 4–8 h | High | In Notes view, add `paste` event listener. If `ClipboardData` has image items, POST to gateway `/notes/attachments` (new tiny endpoint), save to `~/.infinibot/notes/attachments/<uuid>.<ext>`, return URL. Insert `![](url)` at cursor. |
| 5 | **Daily notes (cron + template)** | 2–4 h | Medium-High | Gateway cron at 00:00 local: if no note named `daily/YYYY-MM-DD` exists, create it from `~/.infinibot/notes/templates/daily.md`. "Daily Note" button in Notes sidebar opens today's. |
| 6 | **Templates system** | 4–6 h | Medium-High | Read `~/.infinibot/notes/templates/*.md` at startup. Add "New from template" dropdown in Notes toolbar. On select, create note pre-filled with template content + variable substitution (`{{date}}`, `{{title}}`, `{{cursor}}`). |
| 7 | **Quick-switcher palette (Cmd-K)** | 6–10 h | High | Global `keydown` listener on `Cmd+K`. Overlay with fuzzy-search input across all notes by title + preview text. Arrow-key navigate, Enter opens. Reuse the existing `glass-notification` overlay pattern for the shell. |
| 8 | **LaTeX rendering via KaTeX** | 1–2 h | Medium | KaTeX is already bundled. Configure `marked` renderer in `markdown.ts` to intercept `$...$` (inline) and `$$...$$` (block) delimiters → `katex.renderToString()`. |
| 9 | **Page hierarchy in sidebar** | 4–6 h | Medium | `parent_row_id` column already exists in `DatabaseRow`. Add "Create sub-page" action. Render sidebar as indented tree using recursive template literal. No schema migration needed. |
| 10 | **Markdown export per note** | 1–2 h | Medium | `export-row.ts` already exists. Wire "Export as .md" action in Notes toolbar that calls it and triggers browser download. |

---

## Functional Feature Plan

### Block-Based Editing

**Problem:** The Notes view uses a raw `<textarea>` while `<moltbot-block-editor>` — a full Notion-style block editor with slash commands — already exists at `ui/src/ui/components/block-editor.ts`. Users are editing in a dumb text box when a real editor is ready.

**Design:** Replace the `<textarea>` at `notes.ts:582` with `<moltbot-block-editor .value=${note.body} @change=${save}>`. The block editor wraps `mountBlockEditor` from `project-content-editor.ts`, which already handles: paragraph, heading (H1–H3), bulleted list, numbered list, todo, code block, callout, divider, and image blocks — all via the `/` slash command palette. The editor stores and emits markdown, so the Notes DB schema (`body: string`) is unchanged.

**Drag-to-reorder:** Already implemented inside `mountBlockEditor`. Free with the swap.

**Block-level mentions:** The `moltbot://page/<rowId>` link protocol is already rendered as a styled chip in the preview pane. Phase 2 adds `[[]]` autocomplete so users don't need to type the raw URL.

**Editor library decision:** Use **the existing `<moltbot-block-editor>` — do not import TipTap, BlockNote, Lexical, or ProseMirror.** Here's why:

- TipTap/ProseMirror: ~200KB gzipped, requires a separate extension ecosystem, doesn't play well with Lit's Shadow DOM lifecycle. The collab extension (y-prosemirror) is the only thing Tiptap has that InfiniBot doesn't — but real-time collab is out of scope for v1.
- BlockNote: built on TipTap/ProseMirror, same objections plus it brings its own React dependency.
- Lexical: Meta's editor, also ~150KB, needs React or a custom framework adapter. No Lit binding exists.
- **`<moltbot-block-editor>` (current):** Zero new dependencies, Lit-native, already wired to the gateway, already converts markdown↔blocks, already used in Databases and Projects tabs. The investment already happened — use it.

The only missing block types vs Notion are: toggle/collapsible, synced block, and embed. Add them incrementally inside `project-content-editor.ts` rather than switching libraries.

---

### Links + Backlinks + Graph

**Problem:** Notes have a `linkedTo: string[]` field that is manually maintained. There's no automatic parsing of `[[Page Title]]` links, no backlinks panel, and no graph view.

**Design:**

1. **Wiki-link parsing on save:** When a note is saved, scan `body` for `[[Page Title]]` using a regex. Look up each title in the notes list (case-insensitive). Build a `linked_note_ids` array. Persist to `notes_links` table: `(source_id TEXT, target_id TEXT, PRIMARY KEY (source_id, target_id))`. Index on `target_id` for backlink queries.

2. **Autocomplete in editor:** In the block editor's slash command layer, intercept `[[` as a trigger. Show a floating dropdown populated by a prefix-search on note titles (client-side, fast). Selecting a note inserts `[[Note Title]](moltbot://page/<id>)`.

3. **Backlinks panel:** A collapsible panel below the note body (or in a right sidebar drawer). Queries `notes_links WHERE target_id = currentNote.id`. Renders each linking note as a card with the sentence containing the link. Updates on note save.

4. **Graph view:** A `<canvas>` element rendered with a simple force-directed layout (d3-force — 12KB, already likely bundled with existing visualizations). Nodes = notes, edges = links from `notes_links` table. Click a node to navigate. Color nodes by category. Size by word count. Render behind a "Graph" button in the Notes toolbar. The existing `systems-graph.ts` and `neural-mesh-viz.ts` components can inform the implementation pattern.

**DB:** One new table (`notes_links`), populated on every note save/update. No migration complexity.

---

### Database Integration

**Problem:** Notes and Databases are parallel surfaces. A user can't embed a filtered database view inside a note, and notes can't be referenced as DB rows.

**Design:**

- **Inline database embed:** Add a `/database` slash command in the block editor. Shows a picker of existing DBs. Inserts a block that renders a filtered table view inline (read-only in preview, click to open full DB in its tab). Stored in markdown as an HTML comment: `<!-- infinibot:embed:db:<dbId>:filter:<json> -->`, which the renderer intercepts.
- **Notes as DB rows:** The Notes DB (`db_900c33fe-1f44-43f8-85f9-6be5da634b83`) is already a real `DatabaseStore` with rows. Notes already appear in the Databases tab. The UI just needs to respect the Notes system role consistently — ensure the Notes view and the Databases view show the same data.
- **Cross-reference:** Kanban cards, workflow bodies, and discipline log entries can link to notes via `moltbot://page/<noteId>`. The backlinks panel will surface these cross-surface references.

---

### Embeds + Previews

- **URL paste → OG card:** On paste of a bare URL, POST to a new gateway endpoint `GET /notes/og?url=<url>`. The server fetches the page's `<meta og:*>` tags (1 HTTP request, cached 24h in a simple KV). Returns `{ title, description, image, favicon }`. Client renders a styled card block. Stored as markdown link with a trailing HTML comment carrying the OG data.
- **YouTube / Loom / GitHub Gist:** A blocklist of embed providers in a config object. If a pasted URL matches a known provider, offer "Embed as preview" and render an `<iframe>` or a custom renderer (Gist via GitHub API).
- **LaTeX:** `marked` custom renderer intercepts `$...$` and `$$...$$` → `katex.renderToString()`. KaTeX is already in the bundle per the build output.
- **Image / PDF inline:** Images already render via standard markdown. PDF: render first page via a canvas renderer (pdf.js, ~300KB — load lazily via dynamic import only when a `.pdf` link is clicked).

---

### Templates + Daily Notes

- **Templates:** Read directory `~/.infinibot/notes/templates/*.md` at Notes view load. Expose via a "New from template" button. On select, create a new note with the template body, run variable substitution: `{{date}}` → today's ISO date, `{{title}}` → prompt user, `{{cursor}}` → focus position. Ship 3 starter templates: `daily.md`, `meeting.md`, `plan.md`.
- **Daily notes:** Gateway cron job (via existing `CronCreate` infrastructure) fires at 05:00 local. Creates note `daily/YYYY-MM-DD` from `templates/daily.md` if it doesn't exist. A "Today" button in the Notes sidebar header always opens today's daily note.

---

### Search + Command Palette

- **Full-text search:** Add FTS5 virtual table to the notes SQLite store: `CREATE VIRTUAL TABLE notes_fts USING fts5(name, body, content='notes', content_rowid='rowid')`. Populate on insert/update triggers. Query: `SELECT id, name, snippet(notes_fts, 1, '<mark>', '</mark>', '…', 20) FROM notes_fts WHERE notes_fts MATCH ?`. Returns results in < 5ms on 100K-row notes DB. Surface in the Notes sidebar search input, which currently does client-side filtering.
- **Cmd-K quick-switcher:** Global hotkey listener (`Cmd+K` / `Ctrl+K`) mounted in `app-render.ts`. Shows a full-screen overlay with a search input. As user types, queries FTS5 `notes_fts` + command registry. Commands include: "New Note", "Daily Note", "New from Template", "Open Graph View", navigation to any view. Arrow keys + Enter to select. Escape to close. Pattern: reuse `glass-notification.ts` overlay style.

---

### Sync + Collaboration

- **Federation sync:** Notes currently live in the Notes SQLite store, which is NOT included in the existing federation sync payload. The fix: add `notes.list()` and `notes.upsert()` to the federation diff algorithm (same pattern as kanban/tasks). Conflict resolution: last-write-wins on `updatedAt` timestamp. For same-second conflicts, the master node wins. Flag the conflict in the UI with a diff-view modal.
- **Notion bidirectional sync:** The `notionPageId` and `notionSyncedAt` fields are already on every note. Currently one-way pull. Add push: on save, if `notionPageId` is set, patch the Notion page body via API. Add a "Notion sync" status indicator on the note header.
- **Real-time collab:** Out of scope for v1. Flag: would require operational transform or CRDT (yjs). Design decision: if we add this later, use `yjs` with `y-websocket` over the existing gateway WebSocket. Keep markdown as the ground truth; yjs is only the transport layer.
- **Note-level sharing:** A "Share" button generates a signed token. Gateway serves `GET /public/notes/<token>` as read-only HTML. No auth required for the public URL. Configurable expiry.

---

### Third-Party Integrations (prioritized)

| Integration | Direction | Effort | When |
|---|---|---|---|
| **Notion bidirectional sync** | Push + pull | 2–3 days | Phase 5 (pull works; add push) |
| **Obsidian vault import** | One-time import | 1–2 days | Phase 5 — script reads `.md` files from vault dir, creates notes, parses `[[]]` links, populates `notes_links` |
| **Web clipper (Chrome ext)** | Inbound | 2–3 days | Phase 3 — add "Clip to InfiniBot" action to the existing `assets/chrome-extension/`, POST to `notes.create` via gateway |
| **Voice-to-note** | Inbound | 1–2 days | Phase 4 — existing voice infrastructure records audio, transcribes (Whisper via gateway), creates note with timestamp markers |
| **GitHub Gist sync** | Bidirectional | 1–2 days | Phase 6 — each note with `gist_id` property syncs body on save; pull on open |
| **Markdown export (whole vault)** | Outbound | 4–8 h | Phase 2 — zip all notes as `.md` files + index, serve as download |
| **Obsidian live sync** (ongoing) | Bidirectional | 3–5 days | Phase 6 — watch `~/.obsidian-vault/` for changes, mirror into notes DB |
| **Email-to-note** | Inbound | 2–3 days | Phase 6 — Mailgun/Postmark inbound hook → `notes.create`; set up dedicated address |
| **Roam Research import** | One-time import | 1–2 days | Phase 6 — parse Roam JSON export format |
| **Apple Notes / Bear import** | One-time import | 2–3 days | Phase 6 — parse Bear SQLite export or Apple Notes backup |

**Not worth prioritizing now:** Google Docs sync (API complexity, format loss), Evernote import (deprecated export format), Confluence (enterprise-only).

---

### Mobile Parity

The `mobile/App.js` Expo app exists but has no Notes tab. Plan:

- **Phase 5:** Add Notes tab to mobile. Uses the same gateway API (`notes.list`, `notes.get`, `notes.upsert`). Renders notes using a React Native markdown renderer (`react-native-markdown-display` — lightweight). Create and edit via a simple textarea with the same 9-button toolbar.
- **Offline-first:** Cache the last 50 notes (by `updatedAt`) in Expo's `AsyncStorage`. On reconnect, diff against server and push any local changes. Conflict: same last-write-wins rule.
- **Federation sync:** Mobile doesn't run a full gateway. It talks to the master gateway via the existing federation API. Notes are included in the federation payload after Phase 1 sync work.

---

### Plugins / Extensibility (v2+)

Not building this in v1. Design decision now so the implementation doesn't foreclose it:

- Notes blocks should have a stable `type` string and a `data: Record<string, unknown>` payload. Custom plugins register a renderer for their type. This is the same pattern as Obsidian's `registerMarkdownCodeBlockProcessor` — keep it familiar.
- Plugin sandbox: run plugin code in a `<iframe sandbox>` with a `postMessage` bridge. No access to the gateway by default; plugins request capabilities explicitly.
- Flag this as a v2 design decision. The block schema in `src/content/blocks-markdown.ts` should reserve an `extension` block type now to avoid a breaking change later.

---

## Unification Strategy

Today, markdown body editing happens in four separate places with inconsistent behavior:

| Surface | Current editor | Notes |
|---|---|---|
| Notes view (`notes.ts`) | Raw `<textarea>` | No slash commands, no block dragging |
| Workflows view (`workflows.ts`) | `<doc-agent-panel>` (wraps block editor) | Has preview/raw toggle |
| DB row bodies (`databases.ts`) | `<moltbot-block-editor>` | Full block editor |
| Projects content (`project-content-editor.ts`) | `mountBlockEditor` directly | Full block editor with persistence |

**Recommendation:** `<moltbot-block-editor>` becomes the single shared markdown entry point for all surfaces. It's already a drop-in (`change` event → markdown string). The migration path:

1. Phase 1: Notes view swaps `<textarea>` → `<moltbot-block-editor>`.
2. Phase 2: Audit `doc-agent-panel` (used in workflows + AI IDE) — replace its internal editor with `<moltbot-block-editor>` or confirm it already delegates to it.
3. Phase 3: Discipline log/journal (if it has a markdown field) uses the same component.
4. **Backward compatibility:** All existing `body: string` fields are markdown. `markdownToBlocks()` converts on mount. `blocksToMarkdown()` converts on save. Day-1 behavior is identical to what users see today.
5. **Feature flag:** Add `data-block-editor="true"` attribute to the block editor mount point. Set to `false` to fall back to `<moltbot-markdown-editor>`. Enables rollback without a code change.

---

## Phased Rollout

| Phase | Scope | Time | Key wins delivered |
|---|---|---|---|
| **1** | Wire `<moltbot-block-editor>` into Notes; `[[wiki-links]]` parsing → internal navigation; inline image paste → `~/.infinibot/notes/attachments/`; tag filter chips | 1 week | Slash commands in Notes; real page links; image paste |
| **2** | Backlinks panel; Cmd-K quick-switcher; FTS5 search; markdown export; `notes_links` table | 1 week | Knowledge graph starts; fast search; keyboard-first nav |
| **3** | Templates system; daily notes cron; LaTeX rendering; web clipper Chrome ext action; URL → OG card | 1 week | Structured writing workflows; KaTeX math; browser clipping |
| **4** | Voice-to-note; page hierarchy UI; OG embeds (YouTube, Gist); version history (shadow table) | 1–2 weeks | Audio capture → notes; nested notes; embed previews |
| **5** | Graph view (d3-force canvas); Notion bidirectional push; Obsidian vault import; mobile Notes tab + offline cache | 2 weeks | Visual knowledge map; full Notion sync; mobile parity |
| **6** | GitHub Gist sync; email-to-note; federation sync (notes in diff payload); sharing (public note links); plugin scaffold | 2 weeks | Notes everywhere; sharing; extensibility stub |

**Total: ~8 weeks to full Notion/Obsidian parity** on all features except real-time collab (v2) and a full plugin ecosystem (v2).

---

## Open Questions — Resolved

**Q: Editor library — TipTap vs BlockNote vs Lexical vs custom Lit?**
**A: Use `<moltbot-block-editor>` (already built).** Zero new dependencies. Lit-native. Already handles the full block type set needed for v1. Adding TipTap would introduce ~200KB of gzipped JS, a React-hostile architecture, and a duplication of existing work. If real-time collab is added in v2, evaluate yjs adapters for the existing editor before switching libraries.

**Q: Storage format — pure markdown vs MD+JSON-blocks vs all-JSON?**
**A: Markdown-first.** The `body` column stays as a markdown string. `markdownToBlocks()` converts to blocks for the editor; `blocksToMarkdown()` converts back on save. This guarantees portability (export, Obsidian import, Notion sync) and avoids lock-in. If a block needs metadata that doesn't survive the markdown round-trip (e.g., embed OG data), store it as an HTML comment in the markdown body: `<!-- infinibot:meta:{...} -->`.

**Q: Conflict resolution for satellite/master sync?**
**A: Last-write-wins on `updatedAt`, master wins on tie.** For notes (unlike code), this is acceptable — notes are rarely edited simultaneously on two devices. Log all conflicts to a `notes_conflicts` table (keep both versions for 30 days). Expose a UI to resolve conflicts when detected.

**Q: Image storage — local FS vs cloud?**
**A: Local FS first (`~/.infinibot/notes/attachments/<uuid>.<ext>`).** Gateway serves them at `/notes/attachments/<uuid>`. For mobile and satellite sync, include attachment metadata in the federation diff and sync files on demand (lazy). Add an optional S3-compatible bucket config (`INFINIBOT_ATTACHMENTS_BUCKET`) for users who want cloud storage — but don't require it.

**Q: Plugin sandbox?**
**A: Decide the interface now, build in v2.** Reserve `extension` block type in the schema. Defer sandbox implementation.

---

## Success Metrics

| Metric | Target |
|---|---|
| Note creation (new note → first character typed) | < 5 seconds |
| FTS5 full-text search latency | < 100ms p95 at 10K notes |
| Backlinks panel load time | < 200ms |
| Mobile sync latency (satellite → master) | < 3 seconds on LAN |
| Data loss across satellite/master sync | Zero (conflict log required) |
| % of user's daily writing in InfiniBot vs external tools | > 80% within 8 weeks of Phase 1 |
| Bundle size increase (all phases combined) | < 150KB gzipped |

---

## Risks

| Risk | Likelihood | Mitigation |
|---|---|---|
| Block editor regressions when wiring into Notes view | Medium | Feature-flag the swap; keep `<moltbot-markdown-editor>` as fallback; write a smoke test on the `change` event pipeline |
| `notes_links` table getting stale (notes deleted without cleaning links) | Medium | `ON DELETE CASCADE` on `notes_links.source_id` FK |
| Federation sync adding notes doubles sync payload size | Medium | Notes sync incrementally (delta by `updatedAt`) — same pattern as kanban |
| Obsidian vault import failing on unusual frontmatter or plugins | Low | Import is one-time; parse best-effort, skip unknown frontmatter keys, log skipped files |
| d3-force graph view performance at 1K+ notes | Low | Virtualize: only render nodes within 2 hops of the current note by default; full graph opt-in |
| Bundle size bloat from PDF.js | Medium | Dynamic import — only loaded when user clicks a `.pdf` link |

---

## Immediate Next Steps

1. **User approves scope or amends** — particularly the editor library decision and storage format.
2. **Phase 1 kickoff — coder agent:** Spawn a `claude_coder` subagent with task: "Wire `<moltbot-block-editor>` into `ui/src/ui/views/notes.ts`, replacing the raw `<textarea>`. Add `[[wiki-links]]` parser (regex on save, lookup by name, emit `navigate` event on click). Add tag filter chips to the sidebar."
3. **Create `notes_links` DB table** in the next migration pass (1 hour task, can be done in Phase 1 PR).
4. **Review `project-content-editor.ts`** to confirm all block types needed for notes are already implemented — add any missing ones (callout, embed placeholder) before wiring Notes view.
