Skip to content

Projects: stable path URLs, human-owned CRUD, edit-only agents#8

Merged
plosson merged 2 commits into
mainfrom
projects
Jul 13, 2026
Merged

Projects: stable path URLs, human-owned CRUD, edit-only agents#8
plosson merged 2 commits into
mainfrom
projects

Conversation

@plosson

@plosson plosson commented Jul 13, 2026

Copy link
Copy Markdown
Owner

What

Two stacked features:

1. Projects & stable URLs (7c30e04)

  • Every document lives in a project: vault/<project>/<doc> — room name, vault path, and web URL are the same string (/main/notes.md).
  • URLs are real paths now (SPA fallback on the server, pushState in the client); view state (preview/comment/filters) stays in the hash.
  • One-shot migration moves pre-project root docs (and .mdio/ sidecars) into main/.
  • Web UI gets a project switcher; MCP peers are fenced into one project via MDIO_PROJECT (required), with project-relative tool paths.

2. Human-owned CRUD, edit-only agents (be2c545)

  • Clean REST space (old /api/docs, /api/history/*, /api/blame/* removed — no backward compat, 0 customers):
    GET/POST   /api/projects
    PATCH/DEL  /api/projects/:p
    GET/POST   /api/projects/:p/docs
    PATCH/DEL  /api/projects/:p/docs/*doc          (PATCH = rename and/or move)
    GET        /api/projects/:p/docs/*doc/history
    GET        /api/projects/:p/docs/*doc/blame
    
  • Rooms exist only for documents on disk — connecting never creates anything. Delete discards pending persists (no resurrection); rename/move flushes, then relocates file + sidecars; open peers are disconnected server-side.
  • Web UI: full project/document CRUD (new/rename/move/delete) with dialogs and error surfacing.
  • MCP is edit-only by design: no CRUD tools, open_document refuses missing paths. Humans own the document set; agents edit, comment, review inside it.

Tests

127 pass across 8 suites, including:

  • tests/crud.test.ts — adversarial API suite: traversal/reserved/duplicate/malformed inputs, sidecar preservation across rename & cross-project move, unpersisted edits flushed on rename but discarded on delete, live peers unable to resurrect deleted docs, 404/409/405 coverage.
  • Playwright e2e: dialog-driven CRUD flow (create/rename/move/delete for both projects and docs), cancel paths, reserved-name error alert, plus the existing three-way concurrent-editing scenarios.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Gnrgu8jdLx12jkWY1B8vb3

plosson and others added 2 commits July 13, 2026 22:41
…scoped MCP

- URLs are stable paths (/project/doc.md) served by an SPA fallback; view
  state stays in the hash. Legacy #doc=/?doc= links still resolve.
- Top-level vault directories are projects; every doc lives inside one.
  One-shot migration moves pre-project root docs (and sidecars) into main/.
- Web UI gains a project switcher; the sidebar is scoped to one project.
- MCP peers are fenced into one project (MDIO_PROJECT, required) and use
  project-relative paths; mdio mcp install gained --project.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gnrgu8jdLx12jkWY1B8vb3
REST API (breaking, no compat kept):
  GET/POST   /api/projects                     list / create
  PATCH/DEL  /api/projects/:p                  rename / delete
  GET/POST   /api/projects/:p/docs             list (relative) / create
  PATCH/DEL  /api/projects/:p/docs/*d          rename+move / delete
  GET        /api/projects/:p/docs/*d/history  NDJSON log
  GET        /api/projects/:p/docs/*d/blame    per-line authorship
Replaces /api/docs, /api/history/*, /api/blame/*. Errors are JSON {error}
with 400/404/409.

Rooms exist only for documents on disk: connecting never creates anything.
Delete discards pending persists (a straggler can't resurrect the file);
rename/move flushes first, then relocates file + sidecars. Open peers are
disconnected server-side.

Web UI: create/rename/delete projects, create/rename/move/delete documents
(prompt/confirm dialogs, API errors surfaced as alerts). Legacy #doc=/?doc=
link shims removed.

MCP is deliberately edit-only: no CRUD tools, and open_document refuses
paths that don't exist — humans own the document set, agents work inside it.

Tests: new adversarial crud suite (traversal/reserved/duplicate inputs,
sidecar preservation across rename/move, no-resurrection-after-delete) and
a dialog-driven Playwright CRUD flow with cancel and error paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gnrgu8jdLx12jkWY1B8vb3
@plosson
plosson merged commit 0482083 into main Jul 13, 2026
2 checks passed
@plosson
plosson deleted the projects branch July 13, 2026 21:00
plosson pushed a commit that referenced this pull request Jul 14, 2026
…ar box

search_text only ever searched the open document; nothing let a human or
an agent ask "which document mentions X?" across a project. As projects
grow past a couple of docs (the model added in #8), that gap bites.

Server: GET /api/projects/:p/search?q=&limit= — case-insensitive substring
across every document, returning {doc, line, column, snippet}. Reads the
live room for open documents (freshest) and the markdown file for the rest
(the canonical source of truth; there is no watcher, so an unopened doc has
no unsaved state to miss). One hit per line, capped.

MCP: search_project(query, maxResults?) wraps it — an agent locates the
right document before open_document, staying within its project. Fits the
edit-only split: it finds documents, it can't create them.

Web UI: a search box in the sidebar; results (doc:line + snippet) replace
the document list while searching and clicking one opens that document.
Stale in-flight queries are ignored; switching projects clears it.

Tests: MCP search across two docs with line numbers, case-insensitivity,
and empty results; REST disk-read with column/snippet, project-scoping
(no cross-project leak), 400/404 guards; a Playwright search-and-open flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Q1wmZU3s6DvjypiSHm2au
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.

1 participant