orkmode/doc/9P_DESIGN.md

6.9 KiB

Orkmode 9P Namespace Design v2

Structure

ork/
├── ctl                     # rdwr: "reload", "quit", "rescan"
├── idx                     # read: list of org files (name\tpath\ttitle)
├── keywords                # read: configured TODO keywords
├── new                     # rdwr: create org file, returns filename
│
├── agenda/                 # computed views
│   ├── today               # read: items scheduled/deadline today
│   ├── week                # read: items this week
│   └── todos               # read: all open TODOs across files
│
└── {doc}/                  # e.g., "todo" for todo.org
    ├── raw                 # read/write: full org file content
    ├── body                # read/write: full org file content (alias)
    ├── meta                # read: title, author, filetags (tsv)
    ├── new                 # rdwr: create section, returns UUID
    │
    └── {uuid}/             # section by UUID
        ├── headline        # read: full headline "* TODO [#A] Title :tag:"
        ├── title           # read/write: plain title text
        ├── keyword         # read/write: TODO/DONE/etc or empty
        ├── priority        # read/write: A/B/C or empty
        ├── tags            # read/write: colon-separated "work:urgent"
        ├── level           # read: headline level (1-6)
        ├── scheduled       # read/write: "<2025-01-15 Wed>"
        ├── deadline        # read/write: "<2025-01-20 Mon>"
        ├── closed          # read/write: "<2025-01-21 Tue>"
        ├── properties      # read/write: "KEY=value\n" lines
        ├── body            # read/write: section content (not children)
        ├── new             # rdwr: create child section, returns UUID
        │
        └── {child-uuid}/   # nested children (same structure)
            └── ...

Operations by File

Path Mode Read Write Notes
ctl rdwr — command "reload" rescans, "quit" exits
idx r file list — name\tpath\ttitle per line
keywords r keyword config — TODO NEXT | DONE
capture w — org text Appends to inbox.org
agenda/* r computed — Filtered section lists
{doc}/raw rw full content replace all Escape hatch
{doc}/meta r metadata —
{doc}/{id}/headline r full line — Reconstructed
{doc}/{id}/title rw plain text update
{doc}/{id}/keyword rw keyword update
{doc}/{id}/priority rw A/B/C/empty update
{doc}/{id}/tags rw colon-sep replace Write "work:urgent"
{doc}/{id}/scheduled rw timestamp update Write "<2025-01-15>"
{doc}/{id}/deadline rw timestamp update
{doc}/{id}/properties rw key=val lines replace
{doc}/{id}/body rw content replace Text between headline and children

ID Generation

  1. CUSTOM_ID property if present
  2. Else ID property if present
  3. Else slugified title + - + short hash of position

IDs are stable unless CUSTOM_ID/ID changes or section is deleted.

Nested Hierarchy

The hierarchy mirrors org structure:

todo/
├── project-alpha/          # * TODO Project Alpha
│   ├── title               # "Project Alpha"
│   ├── keyword             # "TODO"
│   ├── design-doc/         # ** TODO Design doc
│   │   ├── title
│   │   └── review/         # *** Review
│   │       └── title
│   └── implementation/     # ** Implementation
│       └── title
└── daily-standup/          # * TODO Daily standup
    └── title

Listing todo/project-alpha/ returns children: design-doc, implementation (plus property files).

Write Semantics

Simple properties (title, keyword, priority):

  • Write replaces value
  • Server updates .org file atomically

Tags:

  • Write replaces all tags: echo "work:urgent" > tags
  • To add: read, append, write back (client responsibility)
  • Or: future ctl commands: echo "tag +urgent" > ctl

Timestamps (scheduled, deadline):

  • Write org timestamp format: <2025-01-15 Wed>
  • Or simplified: 2025-01-15 (server adds day name)
  • Write empty to remove

Body:

  • Write replaces section body content
  • Does NOT affect children (only text between headline and first child)

Properties:

  • Write replaces entire drawer
  • Format: KEY=value\n lines

EDSL Example (Rust)

Porting ollie's virtfs pattern:

fn build_namespace(state: &OrkState) -> FsNode {
    dir("/",
        file("ctl", 0o222, rdwr(|data| state.handle_ctl(data))),
        file("idx", 0o444, read(|| state.list_docs())),
        file("keywords", 0o444, read(|| state.keywords())),
        file("capture", 0o222, write(|data| state.capture(data))),
        
        dir("agenda",
            file("today", 0o444, read(|| state.agenda_today())),
            file("week", 0o444, read(|| state.agenda_week())),
            file("todos", 0o444, read(|| state.all_todos())),
        ),
        
        each("{doc}", || state.doc_names(), |doc| {
            dir(doc,
                file("raw", 0o644, 
                    read(|| state.doc_raw(doc)),
                    write(|data| state.write_doc_raw(doc, data))),
                file("meta", 0o444, read(|| state.doc_meta(doc))),
                
                each("{section}", || state.section_ids(doc), |id| {
                    section_node(state, doc, id)
                }),
            )
        }),
    )
}

fn section_node(state: &OrkState, doc: &str, id: &str) -> FsNode {
    dir(id,
        file("headline", 0o444, read(|| state.headline(doc, id))),
        file("title", 0o644,
            read(|| state.title(doc, id)),
            write(|data| state.set_title(doc, id, data))),
        file("keyword", 0o644,
            read(|| state.keyword(doc, id)),
            write(|data| state.set_keyword(doc, id, data))),
        // ... etc
        
        // Nested children
        each("{child}", || state.child_ids(doc, id), |child_id| {
            section_node(state, doc, child_id)
        }),
    )
}

Questions Resolved

  1. Hierarchy: Nested directories reflecting org structure ✓
  2. IDs: CUSTOM_ID > ID > slug+hash ✓
  3. Operations: Read/write per property, rdwr for ctl ✓
  4. Tags: Simple replace (add/remove via read-modify-write or future ctl)
  5. Body: Section content only, not children ✓

Not Needed (for v1)

  • statewait / blocking reads — GUI can poll or use inotify on .org files
  • search file — agenda views sufficient for now
  • Symlinks — just return data directly

Implementation Order

  1. Port virtfs EDSL to Rust
  2. Build 9P server using nine crate
  3. Implement read-only namespace first
  4. Add write operations
  5. File watching for external changes