6.9 KiB
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
CUSTOM_IDproperty if present- Else
IDproperty if present - 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
ctlcommands: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\nlines
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
- Hierarchy: Nested directories reflecting org structure ✓
- IDs: CUSTOM_ID > ID > slug+hash ✓
- Operations: Read/write per property, rdwr for ctl ✓
- Tags: Simple replace (add/remove via read-modify-write or future ctl)
- Body: Section content only, not children ✓
Not Needed (for v1)
statewait/ blocking reads — GUI can poll or use inotify on .org filessearchfile — agenda views sufficient for now- Symlinks — just return data directly
Implementation Order
- Port virtfs EDSL to Rust
- Build 9P server using
ninecrate - Implement read-only namespace first
- Add write operations
- File watching for external changes