documents
Documents — real things that know their structure, viewable as any format
Doc of record: [../roadmap/documents-universe/THESIS.md](../roadmap/documents-universe/THESIS.md). This page describes the built document spine; the THESIS
carries the universal law + the phase-by-phase status. Status: the spine is complete — real
owned instances (__dataroot/<id>/{object.data,blocks.json,fields.json}), kinds-as-data
(document/registry.jsonl + document/kinds/<k>/…, 99 kinds incl. the legal-lifecycle family),
symmetric codecs (extract.js/render.js, 6/6 content-faithful round-trip), scanned-PDF vision
OCR, per-instance ownership + lensing + sharing, browser download, file-import, cross-node twin
convergence, and one /document/home. The one deferred boundary is layout-faithful (Acrobat-grade) PDF.
The one idea
A document is a real thing — a doc, a sheet, a presentation, a report, a webpage.
It is genuinely that thing, not a "view of" one. What every document has in common is that it
knows its own structure: a tree of semantic content (heading, paragraph, list, table, image,
code…). Because the structure is known, two things fall out for free:
• you can create one — *"ask a doc to create a doc and it happily creates"*;
• you can view it as anything — a native scene you traverse, content-only markdown the LLM
reads, or a real .docx / .pptx / .xlsx / .pdf file.
The "view it as X" is just a view / projection — the same idea as the view FACE on a thing,
nothing more. It is not a separate concept with its own name; a document is the real thing,
and a format is one of its faces.
A website is the clearest case: an external website is not itself one of our things. We *read*
it into a document's known structure and then see it in our thing-shape — the consistent
structure you already navigate, or clean markdown with the skin stripped off.
The structure — a block list
Every document's structure is a JSON array of blocks, walked by functions/json_list /
functions/json_get exactly as openapi walks its operations manifest. One block per content
unit:
[ {"t":"h1","v":"Title"},
{"t":"p","v":"A paragraph."},
{"t":"li","v":"a bullet"},
{"t":"a","v":"label","h":"https://…"},
{"t":"pre","v":"code text"},
{"t":"img","v":"alt","src":"https://…"},
{"t":"quote","v":"…"},
{"t":"hr"},
{"t":"tr","cells":"a|b|c"} ]
Block types map 1:1 onto the scene vocabulary (h1/h2/h3→heading, p→text, li→bullet,
a→link, pre→code, img→image, quote→quote, hr→divider, tr→table row), so the
scene face is a straight walk of the structure. A presentation reads an h1 as a new slide; a
sheet reads tr rows as cells.
The class hierarchy (real things, one shared base)
thing
└── document a content thing that KNOWS its structure: create it, view it as anything
├── doc a word-processing document → default file: .docx
├── sheet a spreadsheet (tr rows/cells) → default file: .xlsx
├── presentation slides (h1 opens a slide) → default file: .pptx (deck reparented here)
├── report a composed report over data → default file: .pdf
└── webpage a web page read into structure → default face: markdown / scene
Each kind is a real class — it *is* that thing. It overrides only what makes it specific (how
it reads its source, its default file format); the structure, create, and the view faces all
come from document.
The verbs (shared by every kind)
verb
role
create
bring the document into being: author its structure (?brief via oracle, ?text inline, else the shipped sample), SAVE it, present it in the default format
write ($1/?content=)
set the content (create / overwrite) from Markdown, blocks, or text — one call delivers the doc
append ($1/?content=)
add content to the end (update) — grow a doc a piece at a time
get
read the content as clean Markdown, returned (no emit) — feed a model the content, not the markup
clear
empty the content (delete)
source (data)
where a *read* document gets its content — a URL (webpage), a file (doc/sheet); empty for an authored one
read
force a fresh read of source into the structure (refresh)
content
the FACE — the structure as a native scene (thing.render draws it per viewer)
md / text / html
the content-only faces (the LLM lens), via the render organ
docx / pptx / xlsx / pdf
the real file faces — the render organ on need
present (?format=)
view the structure in the asked format (dispatch to the leaf named by it)
run
resolve the structure (author or read) and present it — the one-call invoke
make (?brief=)
author the structure from a brief via oracle, cached by content hash
The two organs (carried node scripts, dependency-free)
• document/extract.js <mode> <file> — the structure READER (deterministic, no LLM). Modes:
html (strip a page's skin), md, text, csv, tsv, ipynb, docx, xlsx, pptx, pdf (born-digital
text layer), images (dump a scanned PDF's page images), roundtrip (the P4 self-test). A
scanned / image-only PDF (no text layer) falls back to a live vision-model OCR (document/_ocr),
content-faithful.
• document/render.js <blocksfile> <format> [outpath] — the structure → a face. md/text/html/csv/tsv to
stdout; .docx / .pptx / .xlsx are OOXML (a hand-built ZIP of XML parts, node zlib, no
deps); .pdf is assembled by hand (content-faithful, not layout-faithful). The DSL conducts; the
organ encodes.
CRUD by chat / the LLM / any other thing
A document is a thing, so its actions are already callable by chat, the model, or any other
action — the value here is not "make it callable" (it always was) but that the model works
intelligently instead of fumbling generic file tools:
• Create / Update — one call with the content the model already produced (Markdown):
functions/call "write" $md (overwrite) or functions/call "append" $md (grow it). No
file handles, no format library, no OOXML — the thing owns its own structure.
• Read — functions/call "get" -> $content returns the document as clean Markdown: the
model is fed the content, never tokens spent on tags, layout, or parsing. (md/text
do the same for a human/HTTP view.)
• Deliver — functions/call "docx" -> $path (or pptx/xlsx/pdf) emits the real file
from that same known structure. Author in Markdown, hand someone a Word doc.
So a chat turn that "writes the report and sends the deck" is two action calls on two things,
each operating on its own content — the structure is the asset; the format is a leaf. Markdown
passed to write/append via functions/call is taken verbatim; over HTTP it is URL-encoded
(?content=), as usual.
Relationship to neighbouring classes (no duplication)
• website (author *our own* site chrome — 14 subclasses) is untouched. webpage is its
mirror: *read someone else's* page into structure. Two clear, non-colliding names — and exactly
the point that "a website is not our thing; we see it in our thing-shape."
• mdsite (render a folder of .md as a scene) stays; doc can read .md and adds export
• authoring.
• deck keeps its design system and slide engine; it is reparented parent =
presentation so the taxonomy is honest (a deck *is a* presentation *is a* document) and it
inherits create + the file faces for free.
• openapi is the *active* twin of this *passive* reading: openapi turns an API spec into
callable functions; a document turns a page/file into a readable structure. APIs as things +
pages as things → the whole internet reached by one uniform tool.
What this buys
• Read the web in your own structure. /webpage/content?url=… is any site as the native
thing-view you already navigate; /webpage/md?url=… is the page without skin — the model reads
meaning, not markup.
• Create once, deliver any format. doc.create authors a document and writes a real .docx;
the same structure is then a .pdf, a scene, or markdown by changing ?format=.
• A new kind is a new subclass. PDF-in, image-in (OCR/caption), .ipynb, .epub — each is
one more parent = document folder with its own reader and (if needed) one file face. The
architecture is settled; growth is additive.
The file class — any local file, natively viewed and edited
document reads a *source* (a URL or an authored brief) into structured blocks. Its sibling
file (parent = thing, /file?p=<path>) is the other half: a native viewer / editor /
manager for any file already on disk. No download — you SEE and WORK the file here, the way each
type renders itself:
• code/text → highlighted code; Markdown → formatted (reuses the chat _rich renderer);
CSV/TSV → a real table; JSON → pretty-printed; image → shown inline (base64 data:
URI); PDF / HTML → embedded in a webview; Word / Excel / PowerPoint (.docx/.xlsx/
.pptx, incl. macro-enabled) → read natively (OOXML unzipped by file/office.js): a doc as
formatted text, a workbook as real tables (one per sheet), a deck as titled slides; a folder →
a browsable list (the manager); anything else → a friendly "no viewer yet" note.
• Text kinds get ✎ Edit → a textarea that saves back to the file (file.save).
• Any viewable file gets 📄 Edit as document (file.import) → the file's bytes are read through
the document codecs into a real, owned document instance you can edit and re-export in any
format (content-faithful round-trip). This folds file onto the one document spine — no parallel world.
• One classifier (file/kind.js, the extension→kind map) + one _file_<kind> leaf per format.
Adding a type = one line in kind.js + one leaf — the same additive growth as a document kind.
convos link any file a transcript referenced to /file, so a coding-agent conversation's file
mentions are clickable and open in their own rendering. Owner-only (reads/writes the owner's machine).
Status / follow-ons
• [x] The full document spine (P1–P6): real owned instances, one store+index, kinds-as-data +
generic fallback, symmetric codecs, per-instance ownership/lensing/sharing, one /document/home.
See the THESIS for the phase-by-phase proof.
• [x] deck reparented under presentation; acme-deck re-skin intact.
• [x] Binary view by inlining; office read via file/office.js.
• [x] Real downloads: functions/servefile + the __content_disposition knob; a document
instance streams a real .pdf/.docx/… to the browser via /<id>/download?fmt=.
• [x] Scanned-PDF OCR: image-only PDFs recovered to text via a live vision model
(document/_ocr, content-faithful).
• [x] File → spine: file.import mints a document instance from any file on disk.
• [x] Legal lifecycle on the spine: legal kinds carry sign/stamp/handoff/revise/history on a
per-instance sha256-pinned ledger (document/{sign,stamp,…,history}, reusing legaldoc's engine);
legaldoc is now doorways onto the spine. See [forms.md](./forms.md).
• [x] Cross-node convergence: a document follows the owner across their fleet via twin-DATA.
• [x] In-place round-trip EDIT (roadmap/office-editing/): open a real .docx/.xlsx/.pptx/.pdf from a
storage place, edit it, and save it BACK to the same place in the same format via the C3 write door
storage.put. Docs and Markdown use one continuous document canvas with one Save; a whole-document
base hash refuses stale saves. Sheets, Slides and agent operations keep independently addressed edits,
so concurrent changes to different cells/blocks survive and conflicting changes surface honestly. The
editing primitives live on document (edit, open, saveback, versions, restore, _edit_write)
so every kind inherits them. Walkable version history uses the sha256-pinned ledger, and a 5,000-row
sheet pages via thing/_list.
Everything is FILE-TO-FILE (the block payload never passes through a DSL local, which caps at 128 KB).
Fidelity is content-faithful, not byte-faithful — stated on screen before save (_fidelity).
• [ ] Layout-faithful (Acrobat-grade) PDF — the one deliberately deferred boundary (byte-surgical
layout; we ship content-faithful). Plus nice-to-haves: .ipynb/.epub kinds, headless extraction
for JS-rendered pages.
Zero Office — the product layer (the suite as a range)
The spine is the engine; Zero Office is the product a person lives in. It is a *range* of named
apps, each a face on a document kind (never a new storage model):
App
Class
Front door
New
Export
Zero Docs
doc
/doc/front
/doc/new
.docx
Zero Sheets
sheet
/sheet/front
/sheet/new
.xlsx
Zero Slides
presentation
/presentation/front
/presentation/new
.pptx
Zero Forms
form
/form/front
/document/kinds
.pdf (typed)
Zero Notes
notes
/notes
/notes/add
—
Zero Drive
mydrive
/mydrive
—
—
Zero Mail
—
Gmail/IMAP bridge (untouched)
• One front door for all apps: document/front lives on document at height, so a named app is
just app = 1 + app_start = front + packaging (brand/icon/blurb/noun) + an optional
office_scan (field:value, default class:<self>) / office_new spec. No per-app fork.
• One suite home: /office (class office) — a launcher/storefront that owns no content and
links every app, the kind catalogue, "all my documents", "shared with me", and a bounded projection
of real document files already reachable on disk or in connected Drive.
• One native visual editor, all doors: /<id>/edit emits the shared editable scene primitive—a single
continuous canvas for Docs/Markdown, individual cells for Sheets, and title/body controls inside slide cards. Browser HTML is
generated from the scene; macOS/Windows/GTK/Android/iOS use native text widgets. There is no Office HTML
application and no editor webview. Its state is still blocks.json; blur autosave, add/move/delete, conflict hashes,
versions and save-back all call the existing _edit_write/storage._put path. ?data=meta|blocks
exposes the same bounded structure to an agent; ?ai=1 converts a request to one addressed,
_cap_guard saveact-governed proposal after an owner check performed before inference.
• Existing files are first-class: office/_file_src reuses storage._doc_recent (node, disk, Drive)
and office/_file_row sends every symmetric-codec format—DOCX/XLSX/PPTX/PDF/Markdown/text/HTML/CSV/TSV—through document.open. The native files.pick
capability and the storage disk/Drive detail views converge on that same route. No provider scanner,
file writer, or platform editor is duplicated in Office.
• A format is an Office face, not storage metadata: prose files render in the continuous document
canvas, delimited files in the sheet grid, and each saves back through the same guarded block model. The shared
storage._office_route owns this adoption policy for Office rows, disk details and Drive details.
• Google-native is still Zero-native editing: Google Docs, Sheets and Slides are detected by MIME and
fetched through Drive files.export as DOCX/XLSX/PPTX before entering document.open. The Google UI is
never the editor. Zero persists its canonical blocks and saves an OOXML sibling in the source Drive
folder, leaving the proprietary source untouched.
• No blank first canvas: new Docs/Sheets/Slides get minimal data starters; a legacy truly empty file
draws one first editable block/row/slide control. A web.serve browser must prove its Zero ID before
owner content appears, and the signed-out scene provides that sign-in action directly.
• Infinite kinds, browsable: /document/kinds windows a __dataroot mirror of registry.jsonl
through thing/_list — bounded, ?q=-filterable, category chips, honest count == wc -l. Adding
kind #119 is one registry row + one document/kinds/<kind>/ folder, no code.
• Unlimited, listed, bounded: every "your things" list is thing/_list. document/_office_src
is the render helper; its "all my documents" branch delegates to document/_docidx (count-on-write,
O(1)), with an owner-scoped docscan.sh fallback. document.new stamps typed = 1 for template
kinds so Zero Forms (office_scan = typed:1) windows exactly the typed papers.
• Guarded by roadmap/office-suite/verify-office-suite.mjs (stranded links, catalogue bounded,
packaging).
[native-rendering.md]: ./native-rendering.md
[lenses.md]: ./lenses.md (access posture — a different axis from a document's format faces)