Files
shard-wiki/research/260614-tiddlywiki-deep-dive/findings.md
tegwick fee47514a2 research: TiddlyWiki deep dive (single-file wiki, write-granularity extreme); UC-78
SHARD-WP-0003 T3. Whole-file write-granularity anchor: an entire wiki (content
+ app engine) in one self-contained HTML file -> save rewrites the whole file,
no per-page atomicity. Node .tid file-per-tiddler substrate is git-diffable/
fine-grained, so the same engine spans the granularity spectrum by substrate
(cf. Logseq file/DB UC-62, backend-swap UC-43). Tiddler = flexible-field
record (UC-34); filter expressions = native-query tier (UC-52). UC-78
(single-file container-format attach). Enriched UC-35/40/34/52/43. Marks T3
done. Feeds SHARD-WP-0002 T11/T14.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:46:37 +02:00

9.3 KiB

TiddlyWiki — deep dive (findings)

Date: 2026-06-14 · Source: SHARD-WP-0003 T3 · Subject: TiddlyWiki / TiddlyWiki5 (TW5), Jeremy Ruston's self-contained personal wiki.

Why this dive

The synthesis matrix names whole-file write granularity as one extreme of the write-granularity spectrum, anchored by TiddlyWiki. This dive confirms the anchor and the portability extreme it implies — a wiki that is a single HTML file you can email — and finds the twist: TiddlyWiki also has a Node.js file-per-tiddler substrate, so it spans the granularity spectrum the way Logseq spans file/DB (UC-62). The question: how does shard-wiki attach a backend whose entire content is one file?

1. The single-file model

Classic TiddlyWiki ships as one .html file that contains both:

  1. the TiddlyWiki core (the JavaScript engine, parser, renderer, UI), and
  2. every tiddler (all content), serialized into the file.

Open it in a browser and the file is the running application. There is no server and no build step — the app reconstitutes itself from the file it was loaded from. This is the portability extreme: a complete, self-hosting wiki in a single, emailable, USB-stick-able artifact that runs offline anywhere.

Saving is the catch: a browser page cannot normally overwrite the file it came from, so TiddlyWiki uses "savers" — TiddlyFox/browser extension, the File System Access API, a Node.js server, TiddlySpot/put-savers, or "download a new copy." Crucially, a save rewrites the entire HTML file (core + all tiddlers re-serialized). Hence whole-file write granularity: there is no concept of writing one page in isolation in the single-file mode — every save touches the whole artifact.

2. The tiddler data model

The atomic unit is the tiddler — a named record with fields:

  • Core fields: title (the identity), text (the body), tags, created, modified, type (content type, e.g. text/vnd.tiddlywiki, text/markdown), plus arbitrary custom fields (any key→value). A tiddler is effectively a flexible flat record — closer to a typed-field record (UC-34) than to prose-with-frontmatter.
  • Everything is a tiddler: not just pages, but tags, macros, templates, themes, plugins, and the wiki's own configuration are all tiddlers. A plugin is a bundle of tiddlers.
  • Content markup is WikiText (TW5's own), though type can mark a tiddler as Markdown, JSON, image, etc. Transclusion is native: {{SomeTiddler}} embeds another tiddler; {{SomeTiddler!!field}} embeds a field.

3. The dual substrate — single-file vs file-per-tiddler

TiddlyWiki on Node.js stores each tiddler as a separate .tid file on disk: a small text file with a header of field: value lines, a blank line, then the body. The Node server assembles these into the same wiki at serve time. This substrate is:

  • git-diffable and fine-grained — one file per tiddler, line-level diffs, per-tiddler history — the opposite end of the granularity spectrum from the single HTML file.
  • the natural attach surface for a versioned, multi-author TiddlyWiki.

So TiddlyWiki spans the write-granularity spectrum by substrate (single-file = whole-file write; Node = file-per-tiddler write), exactly as Logseq spans file/DB (UC-62) and as the backend-swap question (UC-43) anticipates.

4. Native query — filter expressions

TiddlyWiki's query language is filter expressions over tiddler fields, e.g. [tag[todo]!tag[done]sort[modified]] — a compact DSL that selects/orders tiddlers by field and tag. Lists, tables, and dynamic views are built from filters. This is a native-query capability (UC-52 tier) — less expressive than SPARQL/datalog but real, and computed over the tiddler store.

5. Capability profile

Dimension (synthesis spectrum) TiddlyWiki (single-file) TiddlyWiki (Node .tid)
Attachment mode file-store: one HTML file file-store: dir of .tid files
Addressing granularity tiddler (title) within the file tiddler = one file
Content identity title (placement-bound) title ↔ filename
Structure flat record store w/ arbitrary fields + tags same
History none in-file (whole-file save) per-file git history
Merge model whole-file replace (no merge) git 3-way per tiddler
Native query filter expressions filter expressions
Translation WikiText (or per-tiddler type: markdown/json/…) same
Write granularity whole file (the anchor) file per tiddler
Operational envelope trivial — a browser; no server a Node server
Access grant file access = full access server/file perms
Content opacity transparent (parse the HTML store) transparent text
Provenance created/modified fields git + fields

6. INTENT mapping

Reinforcements

  • Graceful degradation: a single-file TiddlyWiki is a trivial read-only / projection / backup shard — parse the tiddlers out of the HTML, project pages; no server needed. The limited-backend-still-usable principle at its simplest.
  • Markdown-first but backend-neutral: tiddlers carry a type, so Markdown tiddlers coexist with WikiText — the page model's content-type field maps directly.
  • Typed fields (UC-34): arbitrary tiddler fields are a flexible record model the page model already accommodates.
  • Backend-swap under stable identity (UC-43): single-file ↔ Node .tid is the same logical wiki on two substrates — the migration UC-43 anticipates, within one engine.

Divergences (boundaries / notes)

  • Whole-file write granularity is a real constraint: in single-file mode shard-wiki cannot write one page atomically — an overlay applied "to one page" still rewrites the whole file (T11). This is the coarsest write tier; model it explicitly so overlays/locks account for it (a write to any page conflicts with any concurrent write).
  • Identity = title, file-local; cross-shard identity (T16) layered above.
  • The app is in the file: when parsing a single-file TiddlyWiki, shard-wiki must extract the tiddler store and ignore the embedded engine — i.e. treat the HTML as a container format, not as page content (don't mistake the app for content).

What to keep

  1. Single-file self-contained wiki as a first-class file-store shard — container-format parse, whole-file write granularity (UC-78); the portability/granularity extreme.
  2. Whole-file write granularity as a named tier (T11) with overlay/lock implications.
  3. Dual-substrate binding (single-file vs .tid dir) as another instance of substrate-choice under one identity (UC-43/UC-62).

7. UC seed

# Seed Disposition
UC-78 Attach a single-file self-contained wiki (TiddlyWiki HTML) as one shard — parse tiddlers out of the container, project pages; write = rewrite the whole file (whole-file write granularity, the coarsest tier) new
whole-file write granularity anchor + overlay/lock implications enrich UC-35
single HTML file as a file-store shard (container format) enrich UC-40
tiddler arbitrary fields = flexible record enrich UC-34
filter expressions as a native-query tier enrich UC-52
single-file ↔ Node .tid substrate swap enrich UC-43

8. Architecture notes for SHARD-WP-0002

  • T11 (capability / write granularity): confirm whole-file as the coarsest named write tier (anchored by single-file TiddlyWiki), with the implication that an overlay to any page conflicts with concurrent writes (no per-page atomicity). File-per-tiddler is the fine tier on the same engine.
  • T14 (attach binding): a single-file wiki binds as a container-format file-store (parse tiddler store, ignore embedded engine); a Node TiddlyWiki binds as a dir of .tid files (git-diffable). One engine, two bindings — parameterize like UC-43.
  • Native query: filter expressions are a low-mid native-query tier between "none" and datalog/SPARQL — delegate where present (UC-52).

9. Open questions

  1. In single-file mode, how does shard-wiki represent per-page overlays when writes are whole-file — buffer overlays and re-serialize, or require the Node .tid substrate for write-through and treat single-file as read/projection/backup only?
  2. Is a single-file TiddlyWiki's embedded plugins/config ever relevant to the union, or strictly ignored as app-internals (parse only content tiddlers)?
  3. Does shard-wiki expose tiddler filter expressions as a delegated query, or only its own union query over projected tiddlers?

10. Sources

  • TiddlyWiki.com — Tiddlers, TiddlerFields, Filters, Saving, Node.js docs
  • TiddlyWiki5 GitHub (Jermolene/TiddlyWiki5) — .tid file format, store structure
  • prior: research/260614-logseq-deep-dive/ (file/DB dual substrate, UC-62)

11. Traceability

New UC UC-78 carries the marker in the wikiengines column of spec/UseCaseCatalog.md. Enriched: UC-35, UC-40, UC-34, UC-52, UC-43. Architecture cross-refs: SHARD-WP-0002 T11 (whole-file tier), T14 (dual binding).