orkmode/doc/9P_DESIGN.md

188 lines
6.9 KiB
Markdown

# 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:
```rust
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