188 lines
6.9 KiB
Markdown
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
|