Replace ASCII diagrams with Graphviz DOT in architecture and ticket docs.

This commit is contained in:
russell@unturf.com 2026-03-10 09:29:36 -04:00
parent b63277e4d1
commit 75bec564b2
2 changed files with 203 additions and 110 deletions

View file

@ -3,132 +3,211 @@
Document-first platform: every namespace is a book, every thread is a chapter,
every reply is a section. Pandoc renders 67 output formats.
## Data Flow
## Data Flow — Write Path
```dot
digraph write_path {
rankdir=LR
node [shape=box, style=rounded, fontname="sans-serif"]
edge [fontname="sans-serif", fontsize=10]
input [label="User Input\n(md, html, rst,\nmediawiki, latex,\ntextile, org, ...)", shape=note]
source_fmt [label="source_format\ndetection", shape=diamond]
pandoc_html [label="pandoc\nconvert to html5"]
pandoc_roundtrip [label="pandoc\nhtml → markdown\n(round-trip clean)"]
bleach [label="bleach\nsanitize"]
data [label="Node.data\n(raw source)", shape=cylinder]
data_html [label="Node.data_html\n(rendered)", shape=cylinder]
source_col [label="Node.source_format", shape=cylinder]
input -> source_fmt
source_fmt -> pandoc_html [label="rst, mediawiki,\nlatex, etc."]
source_fmt -> pandoc_roundtrip [label="html input"]
source_fmt -> data [label="markdown\n(direct)"]
pandoc_roundtrip -> data [label="cleaned md"]
pandoc_html -> bleach
bleach -> data_html
input -> source_col [style=dashed, label="store format"]
data -> data_html [label="markdown_to_html\n(for md input)", style=dashed]
}
```
┌─────────────────────────────────────────────┐
│ Write Path │
│ │
User Input │ source_format pandoc bleach │
(md/html/rst/ │ ─────────────► convert to ──► sanitize │
mediawiki/latex/ │ html5 pipeline │
textile/org/...) │ │ │ │
│ │ ▼ │
│ │ data_html │
│ │ (rendered) │
│ │ │
│ html input: │ │
│ pandoc html→md │ │
│ (round-trip clean) │ │
│ │ │ │
│ ▼ │ │
│ data (raw) │ │
│ + source_format │ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Read / Export Path │
│ │
Node tree │ tree-to-markdown pandoc │
(adjacency list) ────► │ renderer ──► convert to ────► output
│ target format │
│ │
│ Hierarchy: │
│ # Thread Title (h1) │
│ ## Author Name (h2, per reply) │
│ ### Author Name (h3, nested reply) │
│ ...depth maps to heading level │
└─────────────────────────────────────────────┘
## Data Flow — Read / Export Path
```dot
digraph read_path {
rankdir=LR
node [shape=box, style=rounded, fontname="sans-serif"]
edge [fontname="sans-serif", fontsize=10]
tree [label="Node tree\n(adjacency list)", shape=cylinder]
renderer [label="tree-to-markdown\nrenderer"]
pandoc [label="pandoc\nconvert to\ntarget format"]
output [label="Output\n(pdf, epub, docx,\nhtml, rst, ...)", shape=note]
tree -> renderer
renderer -> pandoc [label="markdown\ndocument"]
pandoc -> output
subgraph cluster_heading_map {
label="Heading depth mapping"
style=dashed
fontname="sans-serif"
h1 [label="# Thread Title (h1)", shape=plaintext]
h2 [label="## Author Name (h2, reply)", shape=plaintext]
h3 [label="### Author Name (h3, nested)", shape=plaintext]
}
}
```
## Export Hierarchy
```
Namespace (book)
GET /api/v1/export/namespace/{name}.{fmt}
├── Root Node A (chapter) ← default export
│ GET /api/v1/export/threads/{id}.{fmt}
│ │
│ ├── Reply 1 (section) ← on-demand
│ │ GET /api/v1/export/nodes/{id}.{fmt}
│ │ │
│ │ └── Reply 1.1 (subsection) ← on-demand
│ │
│ └── Reply 2 (section) ← on-demand
└── Root Node B (chapter) ← default export
GET /api/v1/export/threads/{id}.{fmt}
```dot
digraph export_hierarchy {
rankdir=TB
node [shape=box, style=rounded, fontname="sans-serif"]
edge [fontname="sans-serif", fontsize=10]
ns [label="Namespace (book)\n/export/namespace/{name}.{fmt}"]
rootA [label="Root Node A (chapter)\n/export/threads/{id}.{fmt}"]
rootB [label="Root Node B (chapter)\n/export/threads/{id}.{fmt}"]
reply1 [label="Reply 1 (section)\n/export/nodes/{id}.{fmt}"]
reply2 [label="Reply 2 (section)\n/export/nodes/{id}.{fmt}"]
reply1_1 [label="Reply 1.1 (subsection)\n/export/nodes/{id}.{fmt}"]
ns -> rootA [label="default"]
ns -> rootB [label="default"]
rootA -> reply1 [label="on-demand"]
rootA -> reply2 [label="on-demand"]
reply1 -> reply1_1 [label="on-demand"]
}
```
**67 output formats** including: markdown, html5, pdf, epub, docx, odt, rst,
67 output formats including: markdown, html5, pdf, epub, docx, odt, rst,
latex, mediawiki, man, plain, rtf, asciidoc, textile, org, json, and more.
## Wiki Mode
```
┌──────────────┐ wiki_edit() ┌──────────────┐
│ │ ──────────────────► │ rb_revision │
│ rb_node │ │ │
│ │ 1. snapshot current │ id │
│ id │ data → revision │ node_id (FK) │
│ data │ 2. increment rev# │ user_id (FK) │
│ data_html │ 3. overwrite node │ data │
│ source_fmt │ with new content │ source_format │
│ │ │ revision_number│
└──────────────┘ │ created │
│ └──────────────┘
can_wiki_edit(node, user)?
├── user.authenticated? → required
├── namespace.can_alter_node()? → owner/moderator always yes
└── namespace.wiki && node.is_root? → wiki mode for root nodes
```dot
digraph wiki_mode {
rankdir=TB
node [shape=record, fontname="sans-serif"]
edge [fontname="sans-serif", fontsize=10]
rb_node [label="{rb_node|id\ldata\ldata_html\lsource_format\l}"]
rb_revision [label="{rb_revision|id\lnode_id (FK)\luser_id (FK)\ldata\lsource_format\lrevision_number\lcreated\l}"]
wiki_edit [label="wiki_edit()", shape=ellipse]
check [label="can_wiki_edit(node, user)?", shape=diamond]
rb_node -> wiki_edit [label="called on node"]
wiki_edit -> rb_revision [label="1. snapshot\ncurrent data"]
wiki_edit -> rb_node [label="3. overwrite\nwith new content", style=dashed]
check -> wiki_edit [label="allowed"]
subgraph cluster_perms {
label="Permission checks"
style=dashed
fontname="sans-serif"
node [shape=plaintext]
p1 [label="user.authenticated → required"]
p2 [label="namespace.can_alter_node() → owner/moderator always yes"]
p3 [label="namespace.wiki && node.is_root → wiki mode for root nodes"]
}
check -> p1 [style=invis]
}
```
## Auto-Generated Themes
```
namespace_name
SHA-256 hash
├── bits[0:8] → hue (0-360)
├── bits[8:40] → 8 seed values
HSL color palette
├── Light mode (:root, .theme-light)
│ --rb-bg, --rb-text, --rb-link, --rb-accent, --rb-border, ...
└── Dark mode (@media prefers-color-scheme: dark, .theme-dark)
--rb-bg, --rb-text, --rb-link, --rb-accent, --rb-border, ...
```dot
digraph themes {
rankdir=TB
node [shape=box, style=rounded, fontname="sans-serif"]
edge [fontname="sans-serif", fontsize=10]
Deterministic: same namespace → same theme, always.
Served at: GET /api/v1/themes/{namespace}/css (1-day cache)
Preview: GET /api/v1/themes/{namespace}/preview (JSON palette)
name [label="namespace_name", shape=plaintext]
sha [label="SHA-256 hash"]
hue [label="hue (0-360)\nbits[0:8]"]
seeds [label="8 seed values\nbits[8:40]"]
palette [label="HSL color palette"]
light [label="Light mode\n:root, .theme-light\n--rb-bg, --rb-text,\n--rb-link, --rb-accent,\n--rb-border, ...", shape=note]
dark [label="Dark mode\n@media prefers-color-scheme: dark\n.theme-dark\n--rb-bg, --rb-text,\n--rb-link, --rb-accent,\n--rb-border, ...", shape=note]
css [label="GET /api/v1/themes/{ns}/css\n(1-day cache)", shape=component]
preview [label="GET /api/v1/themes/{ns}/preview\n(JSON palette)", shape=component]
name -> sha
sha -> hue
sha -> seeds
hue -> palette
seeds -> palette
palette -> light
palette -> dark
light -> css
dark -> css
palette -> preview
}
```
Deterministic: same namespace name always produces the same theme.
## Module Map
```
remarkbox/
├── lib/
│ ├── pandoc.py ← subprocess wrapper, tree renderer
│ └── theme_generator.py ← deterministic CSS from namespace name
├── api/
│ ├── export.py ← /export/ endpoints (namespace, thread, node)
│ ├── wiki.py ← /wiki-edit, /revisions endpoints
│ ├── themes.py ← /themes/ endpoints (css, preview)
│ ├── views.py ← modified: source_format on create/reply/edit
│ └── serializers.py ← modified: source_format in node JSON
├── models/
│ ├── node.py ← modified: source_format column, set_data(), wiki_edit()
│ ├── namespace.py ← modified: can_wiki_edit()
│ └── revision.py ← new: Revision model
└── scripts/alembic/versions/
├── d47dc908d2ea_... ← add source_format to rb_node
└── 8e3c406e4049_... ← create rb_revision table
```dot
digraph modules {
rankdir=TB
node [shape=box, fontname="monospace", fontsize=10]
edge [fontname="sans-serif", fontsize=9]
compound=true
subgraph cluster_lib {
label="remarkbox/lib/"
style=rounded
fontname="sans-serif"
pandoc_py [label="pandoc.py\nsubprocess wrapper\ntree renderer"]
theme_gen [label="theme_generator.py\ndeterministic CSS"]
}
subgraph cluster_api {
label="remarkbox/api/"
style=rounded
fontname="sans-serif"
export_py [label="export.py\n/export/ endpoints"]
wiki_py [label="wiki.py\n/wiki-edit, /revisions"]
themes_py [label="themes.py\n/themes/ endpoints"]
views_py [label="views.py\nsource_format on\ncreate/reply/edit"]
serial_py [label="serializers.py\nsource_format in JSON"]
}
subgraph cluster_models {
label="remarkbox/models/"
style=rounded
fontname="sans-serif"
node_py [label="node.py\nsource_format column\nset_data(), wiki_edit()"]
ns_py [label="namespace.py\ncan_wiki_edit()"]
rev_py [label="revision.py\nRevision model"]
}
subgraph cluster_migrations {
label="remarkbox/scripts/alembic/versions/"
style=rounded
fontname="sans-serif"
mig1 [label="d47dc908d2ea\nadd source_format"]
mig2 [label="8e3c406e4049\ncreate rb_revision"]
}
export_py -> pandoc_py
themes_py -> theme_gen
wiki_py -> rev_py
wiki_py -> node_py
views_py -> node_py
export_py -> node_py
export_py -> ns_py
node_py -> pandoc_py [style=dashed, label="non-md formats"]
mig1 -> node_py [style=dotted]
mig2 -> rev_py [style=dotted]
}
```

View file

@ -13,10 +13,24 @@ Wiki mode lets anyone edit root topics with revision tracking.
## Architecture
```
Namespace (book) → pandoc → all formats (default)
└── Root Node (chapter) → pandoc → all formats (default)
└── Reply (section) → pandoc → on-demand only
```dot
digraph export_hierarchy {
rankdir=LR
node [shape=box, style=rounded, fontname="sans-serif"]
namespace [label="Namespace\n(book)"]
root [label="Root Node\n(chapter)"]
reply [label="Reply\n(section)"]
pandoc [label="pandoc", shape=ellipse]
formats [label="67 output\nformats", shape=note]
namespace -> root [label="contains"]
root -> reply [label="contains"]
namespace -> pandoc [label="default"]
root -> pandoc [label="default"]
reply -> pandoc [label="on-demand"]
pandoc -> formats
}
```
### Data model changes