build: build the plugin-side change-feed emitter, the command-queue client, and the write-permission toggle UI, per the spec fr #6

Merged
Joel merged 1 commit from build/6e2931f2 into main 2026-09-03 02:04:37 +00:00
Owner

build the plugin-side change-feed emitter, the command-queue client, and the write-permission toggle UI, per the spec from the read step

This task is one step of my plan: Build real-time Obsidian partnership infrastructure — change feed, write parity, command queue, permission model

Result of earlier step 1 (read: read connor/obsidian.py, the vault handlers in connor/service.py, and obsidian-c):
Done reading. Here are my notes and the four specs.

Reading notes: what the code does today

Vault to Connor is already push, not poll. The plugin registers four vault events at src/main.ts:166-176. Create and modify go through a 4-second per-file debounce (queue, lines 377-386) into pushFile (388-400), which POSTs /vault/ingest with the full text. Delete POSTs the same route with deleted: true (404-410). Rename is not an event of its own: it is a delete of the old path plus a queued push of the new one (172-176). The service handler at connor/service.py:976-983 hands each to ingest_note (connor/obsidian.py:159-188), which deletes every memory row for the note's source_ref and re-ingests fresh. So a rename re-embeds the whole note. A failed push is only logged (396-399, 409): nothing is retried, nothing tells the service a change was missed. Only files passing eligible() (360-375) travel: markdown only, never under writeRoot, and only where a folder flag or the legacy allowlist says yes. The word "polling" in the task doesn't match anything live. The only poll that ever existed was the plugin polling /vault/outbox every 30 seconds, and that is gone (ma

Result of earlier step 2 (backend: build the service-side change-feed ingestion endpoint, the write/modify parity e):
Done and green. The full suite runs 968 tests, OK: the 947 at baseline plus my 21 new ones, and nothing else changed. Here are my finishing notes.

One thing to know first

The four specs from my read step were clipped in memory after the reading notes, so I could not build from the spec text verbatim. I rebuilt each piece from those notes plus the decisions on record for item #34 and plan 47a864fb. Where I had to choose a wire shape, I chose the smallest one that fits what the service already does. Please read the contracts below against what you expected.

What I built, in connor/obsidian.py, connor/service.py, connor/schema.sql

The change feed. One POST of ordered events per vault replaces the one-file-at-a-time push. Each event is {"kind", "path", "text"?, "old_path"?, "seq"?}, kinds create, modify, delete, rename. A rename now moves the mirror rows to the new source ref instead of deleting and re-embedding the whole note. The vault remembers the last seq applied: a batch replayed after a lost 200 is skipped, a seq that jumps past the cursor comes back flagged gap so the plugin does a full resync, and one bad event is one bad result rather than a sunk batch


Built by Connor's frontend backburner (dispatch 6e2931f2) in an isolated clone; shipped deterministically by ship.py. The full build notes were spoken in conversation and stored in memory (agent-dispatch:6e2931f2). Review is the gate.

build the plugin-side change-feed emitter, the command-queue client, and the write-permission toggle UI, per the spec from the read step This task is one step of my plan: Build real-time Obsidian partnership infrastructure — change feed, write parity, command queue, permission model Result of earlier step 1 (read: read connor/obsidian.py, the vault handlers in connor/service.py, and obsidian-c): Done reading. Here are my notes and the four specs. ## Reading notes: what the code does today **Vault to Connor is already push, not poll.** The plugin registers four vault events at `src/main.ts:166-176`. Create and modify go through a 4-second per-file debounce (`queue`, lines 377-386) into `pushFile` (388-400), which POSTs `/vault/ingest` with the full text. Delete POSTs the same route with `deleted: true` (404-410). Rename is not an event of its own: it is a delete of the old path plus a queued push of the new one (172-176). The service handler at `connor/service.py:976-983` hands each to `ingest_note` (`connor/obsidian.py:159-188`), which deletes every memory row for the note's `source_ref` and re-ingests fresh. So a rename re-embeds the whole note. A failed push is only logged (396-399, 409): nothing is retried, nothing tells the service a change was missed. Only files passing `eligible()` (360-375) travel: markdown only, never under writeRoot, and only where a folder flag or the legacy allowlist says yes. The word "polling" in the task doesn't match anything live. The only poll that ever existed was the plugin polling `/vault/outbox` every 30 seconds, and that is gone (ma Result of earlier step 2 (backend: build the service-side change-feed ingestion endpoint, the write/modify parity e): Done and green. The full suite runs 968 tests, OK: the 947 at baseline plus my 21 new ones, and nothing else changed. Here are my finishing notes. ## One thing to know first The four specs from my read step were clipped in memory after the reading notes, so I could not build from the spec text verbatim. I rebuilt each piece from those notes plus the decisions on record for item #34 and plan 47a864fb. Where I had to choose a wire shape, I chose the smallest one that fits what the service already does. Please read the contracts below against what you expected. ## What I built, in `connor/obsidian.py`, `connor/service.py`, `connor/schema.sql` **The change feed.** One POST of ordered events per vault replaces the one-file-at-a-time push. Each event is `{"kind", "path", "text"?, "old_path"?, "seq"?}`, kinds create, modify, delete, rename. A rename now moves the mirror rows to the new source ref instead of deleting and re-embedding the whole note. The vault remembers the last seq applied: a batch replayed after a lost 200 is skipped, a seq that jumps past the cursor comes back flagged `gap` so the plugin does a full resync, and one bad event is one bad result rather than a sunk batch --- Built by Connor's frontend backburner (dispatch `6e2931f2`) in an isolated clone; shipped deterministically by `ship.py`. The full build notes were spoken in conversation and stored in memory (`agent-dispatch:6e2931f2`). Review is the gate.
build the plugin-side change-feed emitter, the command-queue client, and the write-permission toggle UI, per the spec from the read step

This task is one step of my plan: Build real-time Obsidian partnership infrastructure — change feed, write parity, command queue, permission model

Result of earlier step 1 (read: read connor/obsidian.py, the vault handlers in connor/service.py, and obsidian-c):
Done reading. Here are my notes and the four specs.

## Reading notes: what the code does today

**Vault to Connor is already push, not poll.** The plugin registers four vault events at `src/main.ts:166-176`. Create and modify go through a 4-second per-file debounce (`queue`, lines 377-386) into `pushFile` (388-400), which POSTs `/vault/ingest` with the full text. Delete POSTs the same route with `deleted: true` (404-410). Rename is not an event of its own: it is a delete of the old path plus a queued push of the new one (172-176). The service handler at `connor/service.py:976-983` hands each to `ingest_note` (`connor/obsidian.py:159-188`), which deletes every memory row for the note's `source_ref` and re-ingests fresh. So a rename re-embeds the whole note. A failed push is only logged (396-399, 409): nothing is retried, nothing tells the service a change was missed. Only files passing `eligible()` (360-375) travel: markdown only, never under writeRoot, and only where a folder flag or the legacy allowlist says yes. The word "polling" in the task doesn't match anything live. The only poll that ever existed was the plugin polling `/vault/outbox` every 30 seconds, and that is gone (ma

Result of earlier step 2 (backend: build the service-side change-feed ingestion endpoint, the write/modify parity e):
Done and green. The full suite runs 968 tests, OK: the 947 at baseline plus my 21 new ones, and nothing else changed. Here are my finishing notes.

## One thing to know first

The four specs from my read step were clipped in memory after the reading notes, so I could not build from the spec text verbatim. I rebuilt each piece from those notes plus the decisions on record for item #34 and plan 47a864fb. Where I had to choose a wire shape, I chose the smallest one that fits what the service already does. Please read the contracts below against what you expected.

## What I built, in `connor/obsidian.py`, `connor/service.py`, `connor/schema.sql`

**The change feed.** One POST of ordered events per vault replaces the one-file-at-a-time push. Each event is `{"kind", "path", "text"?, "old_path"?, "seq"?}`, kinds create, modify, delete, rename. A rename now moves the mirror rows to the new source ref instead of deleting and re-embedding the whole note. The vault remembers the last seq applied: a batch replayed after a lost 200 is skipped, a seq that jumps past the cursor comes back flagged `gap` so the plugin does a full resync, and one bad event is one bad result rather than a sunk batch

Built by my frontend backburner (dispatch 6e2931f2), diff verified by git; shipped by ship.py. Nothing merges without review.
Joel approved these changes 2026-09-03 02:04:32 +00:00
Joel merged commit cfb0318ead into main 2026-09-03 02:04:37 +00:00
Joel deleted branch build/6e2931f2 2026-09-03 02:04:37 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ZSDev/obsidian-connor!6
No description provided.