Skip to content

fix(annotate): add /api/save-notes POST endpoint to annotate server#884

Merged
backnotprop merged 6 commits into
backnotprop:mainfrom
titosemi:fix/annotate-server-save-notes
Jun 17, 2026
Merged

fix(annotate): add /api/save-notes POST endpoint to annotate server#884
backnotprop merged 6 commits into
backnotprop:mainfrom
titosemi:fix/annotate-server-save-notes

Conversation

@titosemi

Copy link
Copy Markdown
Contributor

Fixes #844

Problem

Saving annotations to Obsidian (Save to Obsidian button) fails silently in annotation mode. The button is visible and clickable but nothing happens because the annotate server lacks the /api/save-notes POST route.

The plan review server has this route and works correctly — only the annotate server was missing it.

Root Cause

packages/server/annotate.ts (Bun source) and apps/pi-extension/server/serverAnnotate.ts (Pi extension copy) did not implement the /api/save-notes handler. When the frontend sends the save request in annotation mode, the server falls through to the HTML catch-all route, returning the SPA HTML instead of JSON.

Changes

  • packages/server/annotate.ts — Added /api/save-notes POST route handler with the same integration logic as serverPlan.ts: accepts obsidian, bear, and octarine configs, runs configured saves in parallel, returns { ok: true, results }.
  • apps/pi-extension/server/serverAnnotate.ts — Same route added to the Pi extension copy of the annotate server.
  • packages/server/integrations.test.ts — Added saveToObsidian unit tests (success and missing vault cases). Consolidated duplicate imports from ./integrations into a single statement.
  • packages/server/annotate.test.ts — HTTP endpoint tests for the new route (success, empty integrations, integration-level error).

Testing

bun test packages/server/annotate.test.ts
  ✓ POST saves to Obsidian vault and returns success
  ✓ POST returns 200 with empty results when no integrations
  ✓ POST with missing vault returns integration error, not server error

bun test packages/server/integrations.test.ts
  ✓ saveToObsidian writes plan file to temp vault
  ✓ saveToObsidian fails when vault path does not exist

bun test tests/parity/route-parity.test.ts
  ✓ All Bun ↔ Pi routes match (including save-notes)

All 205 server tests pass, 0 fail.

@backnotprop

Copy link
Copy Markdown
Owner

thank you!

@backnotprop backnotprop force-pushed the fix/annotate-server-save-notes branch from b52b6d5 to 2b29879 Compare June 17, 2026 02:15
titosemi and others added 4 commits June 16, 2026 19:30
Copies the save-notes route from the plan review server into the
annotate server (Bun source and Pi extension copy), enabling Save to
Obsidian in annotation mode.

Fixes backnotprop#844
Verifies saveToObsidian writes files correctly and handles missing
vaults. HTTP endpoint tests cover success, empty integrations, and
integration-level error (not 500).

Imports consolidation from ./integrations into a single statement.
…lock bug

Move the /api/save-notes logic into shared handler modules
(shared-handlers.ts for Bun, handlers.ts for Pi) following the existing
pattern for handleImage, handleUpload, handleDraftSave. Replaces four
inline copies with two canonical implementations.

Fixes:
- Bun annotate catch block now correctly returns 500 (was logging only)
- Misindented brace in Pi serverAnnotate.ts resolved by extraction
- Revert unrelated port fallback change (keep server.port! for consistency)
- Static imports in integrations.test.ts
- Add /api/save-notes to CLAUDE.md Annotate Server API table
@backnotprop backnotprop force-pushed the fix/annotate-server-save-notes branch from 2b29879 to 416719c Compare June 17, 2026 02:31
@backnotprop

Copy link
Copy Markdown
Owner

Thanks!! I cleaned up conflicts, extracted the save-notes handler into the shared handler modules, and fixed the catch-block bug. Ready for merge.

@backnotprop backnotprop force-pushed the fix/annotate-server-save-notes branch from a7142bb to 447e322 Compare June 17, 2026 03:59
…module

commands.test.ts mocked the annotate server with
`mock.module("@plannotator/server/annotate", ...)`. Bun module mocks are
process-global and cannot be unset (oven-sh/bun#7823, #12823), so the stub
leaked into every suite that runs after it — in particular any test that boots
the real annotate server received a stub with no `.url`.

Make `startAnnotateServer` injectable through the existing CommandDeps
(defaulting to the real import, so production is unchanged) and have the test
pass its stub that way. This keeps the fake local to the opencode suite and
unblocks real annotate-server integration tests.
@backnotprop backnotprop force-pushed the fix/annotate-server-save-notes branch from 447e322 to 611809f Compare June 17, 2026 04:24
…ring

- shared-handlers.test.ts: unit-test handleSaveNotes directly (Obsidian write,
  empty integrations, integration-error reported not thrown, 500 on bad body).
- annotate.test.ts: boot the real annotate server and POST /api/save-notes,
  asserting it is served as JSON (not the SPA HTML catch-all) — the regression
  guard for backnotprop#844. Now possible because the opencode suite no longer installs a
  global annotate module mock.
@backnotprop backnotprop force-pushed the fix/annotate-server-save-notes branch from 611809f to c7ed99a Compare June 17, 2026 04:27
@backnotprop backnotprop merged commit b7ef075 into backnotprop:main Jun 17, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Failed to Save to Obsidian

2 participants