generated from coulomb/repo-seed
SQLite-local PKB whose standout is note cloning — a single note can sit in multiple tree locations at once via the note (identity) vs branch (placement) split, so the hierarchy is a DAG, not a tree, with no single canonical path. The identity-not-equal-placement model is the clean way to represent a page in multiple locations/shards and the namespace-level form of the clone/reference primitive. Also: attributes (labels + typed relations) are inherited + templated, so metadata is computed (own + inherited + template), not a flat bag; content opacity is per-item (per-note encryption / protected notes), refining the proposed 12th spectrum; HTML-native (CKEditor, lossy to Markdown); dual extension surface (scripting code notes + ETAPI token REST). TriliumNext is the active community fork of zadam's Trilium (TWiki->Foswiki pattern). Added UC-66 (DAG hierarchy / note cloning), UC-67 (inherited/templated attributes, effective vs own); enriched UC-15/22/34/38/42/61. Catalog now 67 UCs. Architecture for SHARD-WP-0002 T11/T12/T14/T15/T16: DAG namespace + identity/placement split, computed/inherited metadata, per-item content opacity, HTML source model, scripting + ETAPI host surfaces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
275 lines
16 KiB
Markdown
275 lines
16 KiB
Markdown
# Findings — Trilium (TriliumNext): note cloning, attribute inheritance, HTML-native
|
|
|
|
Date: 2026-06-14
|
|
Source kind: **modern shipped product** — an open-source hierarchical PKB; a *candidate
|
|
shard* whose distinctive traits are **note cloning (a DAG hierarchy)**, an
|
|
**attribute/relation system with inheritance + templates**, and **HTML-native** content
|
|
Lens: shard-wiki — the namespace model (identity vs placement), inherited/computed
|
|
metadata, HTML translation, per-note encryption, and the scripting/ETAPI surfaces
|
|
|
|
> Why Trilium earns a dive. It is another SQLite-local note app, but it brings a
|
|
> structural feature **none of the prior twelve systems had: note cloning** — a single
|
|
> note can sit in **multiple places in the tree at once**, so the hierarchy is a **DAG,
|
|
> not a tree**, and **note identity is cleanly separated from placement** (a note has
|
|
> many "branches"). That directly challenges shard-wiki's namespace model (UC-22 assumes
|
|
> a path) and is the clone/reference primitive made concrete at the *namespace* level.
|
|
> Trilium also has an **attribute system with inheritance and templates** (effective
|
|
> metadata is computed, not just stored), and it is one of the few **HTML-native** (not
|
|
> Markdown) tools — a useful stress on "Markdown-first must degrade."
|
|
|
|
Lineage note (like TWiki→Foswiki): **TriliumNext** is the community fork of the original
|
|
**Trilium** (zadam) after it went maintenance-only; the active project is TriliumNext.
|
|
|
|
Contrast set: Joplin (SQLite-local, files-on-sync, page-level), Logseq (block-graph on
|
|
files), Notion (hosted DB, schema+relations). Trilium = **SQLite-local, DAG hierarchy,
|
|
inherited attributes, HTML content, self-host sync + ETAPI**.
|
|
|
|
---
|
|
|
|
## 1. Core architecture — one SQLite file, server-syncable
|
|
|
|
- **Storage:** a single **SQLite** file (`document.db`, via better-sqlite3) holding
|
|
notes, attachments, history, and settings. Local DB store (like Joplin; not files).
|
|
- **Clients/sync:** desktop (Electron) or a self-hosted **server**; multi-instance
|
|
**sync protocol** with **conflict resolution + entity change tracking**, plus
|
|
**WebSocket** realtime updates. Attach via the server, the ETAPI (§5), or the DB.
|
|
- **IDs:** 12-char IDs — **`noteId`, `branchId`, `attributeId`, `attachmentId`**. The
|
|
**note vs branch** split is the key idea (§2).
|
|
- **Export:** to Markdown/HTML (lossy — content is HTML, §4).
|
|
|
|
---
|
|
|
|
## 2. Note cloning — a DAG hierarchy, identity separated from placement
|
|
|
|
Notes form an arbitrarily deep tree, **but a single note can be placed in multiple
|
|
locations** — "cloning," implemented via the **branches** model:
|
|
|
|
- A **note** (`noteId`) is the content + identity. A **branch** (`branchId`) is *one
|
|
placement* of that note under a parent (with an optional **branch prefix** giving
|
|
per-location context). A note with several branches is **cloned** — it appears in
|
|
several places at once; editing it anywhere edits the one note.
|
|
- So the hierarchy is a **DAG**, not a tree, and **identity (note) is separated from
|
|
location (branch)**. There is no single canonical path for a cloned note.
|
|
|
|
This is the most shard-wiki-relevant feature in the dive:
|
|
|
|
- It breaks the assumption behind **UC-22** (resolve a page by *a* path). shard-wiki's
|
|
namespace model must allow **a page in multiple namespace locations** with no single
|
|
canonical path (UC-66).
|
|
- The **note/branch split** is a clean model shard-wiki should borrow: **page identity ≠
|
|
page placement**. A page is one entity; its locations are separate placement records —
|
|
exactly how shard-wiki should treat a page that appears under multiple paths/shards.
|
|
- It is the **clone/reference-not-copy primitive** (Xanadu clone, ZigZag clone, UC-44/45,
|
|
T16) realized at the *namespace* level: same note, many positions, one source of truth.
|
|
|
|
---
|
|
|
|
## 3. Attributes — labels + relations, with inheritance and templates
|
|
|
|
Trilium's metadata system is **attributes**:
|
|
|
|
- **Labels** (`#tag`, optionally `#key=value`) — typed metadata on a note.
|
|
- **Relations** (`~relation`) — **typed links to other notes** (a knowledge graph).
|
|
- **Inheritance** — attributes can be **inherited down the subtree** (inheritable
|
|
attributes), and **templates** (a note whose attributes/structure are applied to
|
|
instances via a `~template` relation) inject attribute sets. So a note's **effective
|
|
metadata = its own attributes + inherited + templated** — computed, not just stored.
|
|
- Promoted attributes give a form-like UI; attributes drive **search/queries** and
|
|
**scripting**.
|
|
|
|
shard-wiki read: this is structured data (UC-34) and typed relations (UC-58), but with a
|
|
**new wrinkle — inheritance/templates make metadata *computed***. Projecting such a shard
|
|
must distinguish **effective vs own** attributes (and record their provenance: own /
|
|
inherited-from / template), not flatten them (UC-67). Templates also reinforce blueprint
|
|
pages (UC-15).
|
|
|
|
---
|
|
|
|
## 4. Content model — HTML-native, many note types
|
|
|
|
- **Text notes are HTML** (WYSIWYG via **CKEditor5**), **not Markdown**. Trilium is one
|
|
of the few HTML-first tools studied. Markdown participation therefore needs **HTML↔
|
|
Markdown translation** — more tractable than Notion's block model but still **lossy**
|
|
for some constructs (CKEditor features, includes) → a fidelity-aware case bridging
|
|
UC-42 (lossless ideal) and UC-59 (lossy-with-report).
|
|
- **Note types:** text (HTML), code (CodeMirror), **canvas (Excalidraw)**, relation
|
|
maps, mind maps (Mind Elixir), spreadsheets (Univer), geo maps (Leaflet), **render
|
|
notes**, file/image, book — i.e. lots of **non-Markdown content** (UC-55) and
|
|
**script/render-generated dynamic notes** (UC-54).
|
|
|
|
---
|
|
|
|
## 5. Extension surfaces — scripting (in-app) + ETAPI (external)
|
|
|
|
Two surfaces, like Joplin:
|
|
|
|
- **Scripting (in-engine host).** **Code notes** in JS run as **frontend** (UI widgets,
|
|
buttons) or **backend** (server-side automation) scripts against a **Script API**
|
|
(create/query notes, attributes, etc.). Executable, in-app — the adapter-host path
|
|
(UC-38). "Render notes" + scripts produce **dynamic, generated content** (UC-54).
|
|
- **ETAPI (external REST API).** Trilium's public REST API (since v0.50), **token auth**
|
|
(`Bearer ETAPITOKEN`), with client libs (`trilium-py`): CRUD over notes/branches/
|
|
attributes/attachments, search, import/export — an external attach surface (UC-57)
|
|
that, unlike Joplin's localhost-only Data API, is the **designed external integration
|
|
API** for a (often self-hosted) server.
|
|
|
|
---
|
|
|
|
## 6. Security — per-note (partial) encryption
|
|
|
|
Trilium offers **strong per-note encryption** ("protected notes") unlocked in a
|
|
**protected session** (password; TOTP/OpenID for login). Crucially this is **per-note**:
|
|
a shard can hold **some encrypted and some plaintext notes at once**. This refines the
|
|
**content-opacity** dimension (UC-61, proposed twelfth spectrum from Joplin/Anytype):
|
|
opacity is **per-item, not only whole-shard** — the adapter must handle a shard where
|
|
*part* of the content is opaque without a key (protected notes projectable only as
|
|
structure-shell; unprotected notes fully).
|
|
|
|
---
|
|
|
|
## 7. Trilium as a shard — capability profile
|
|
|
|
| Capability | Trilium | Notes for the adapter contract |
|
|
|------------|---------|--------------------------------|
|
|
| Read | yes (ETAPI / sync / DB) | ETAPI REST (server) is the clean surface |
|
|
| Write | yes (ETAPI / script) | per-note; via ETAPI or backend code notes |
|
|
| Write granularity | per-note (page) | — |
|
|
| Identity / addressing | **`noteId` + `branchId`** (12-char) | **identity ≠ placement**; cloned notes have many branches (UC-51, UC-66) |
|
|
| Hierarchy | **DAG (cloning)** | a note in multiple locations; no single canonical path (UC-66) |
|
|
| Structure | **labels + relations, inherited + templated** | effective vs own metadata (UC-34, UC-58, UC-67) |
|
|
| Content | **HTML (CKEditor)** + many types | HTML↔MD lossy translation (UC-42/59); non-MD types (UC-55) |
|
|
| History | internal revisions in the DB | not portable git → supplement (UC-36) |
|
|
| Native query | attribute search / script queries | delegate or build index (UC-52/63) |
|
|
| Subscribe | WebSocket + sync protocol | push; conflict resolution built in (UC-31) |
|
|
| Content opacity | **per-note encryption (partial)** | per-item opacity (UC-61 refined) |
|
|
| Extension | **scripting (in-app) + ETAPI (external)** | dual surface (UC-38, UC-57) |
|
|
| Templates | yes (`~template`) | blueprint pages (UC-15) |
|
|
|
|
Verdict: a capable **SQLite-local, server-syncable** shard best attached via **ETAPI**.
|
|
Standout demands: the **DAG hierarchy / note-branch identity model** (UC-66) and
|
|
**inherited/templated attributes** (UC-67); plus HTML-native translation, per-note
|
|
opacity, and the scripting host.
|
|
|
|
---
|
|
|
|
## 8. Mapping to shard-wiki INTENT (compare, do not equate)
|
|
|
|
### 8.1 Reinforcements
|
|
|
|
- **Identity ≠ placement** (note/branch) is a model shard-wiki should adopt for pages
|
|
that appear under multiple paths or in multiple shards — provenance and union without
|
|
erasure depend on separating "what a page is" from "where it sits."
|
|
- **Typed relations + templates** validate the structured/relation demand (UC-58) and
|
|
blueprints (UC-15) on a local-first, self-hostable backend.
|
|
- **ETAPI** is a clean example of a **designed external REST surface** for a self-hosted
|
|
server (cf. Notion external API, but self-hosted/trust-boundary friendly).
|
|
|
|
### 8.2 Deliberate divergences (design bugs if conflated)
|
|
|
|
1. **Don't force a single canonical path.** A cloned note has many placements; modeling
|
|
it with one path loses information (UC-66). Use the note/branch separation.
|
|
2. **Don't flatten computed metadata.** Effective attributes include inherited/templated
|
|
values; record provenance (own vs inherited vs template), don't collapse (UC-67).
|
|
3. **HTML-native, not Markdown.** Translate HTML↔Markdown with a fidelity report; degrade
|
|
to read-only where lossy (UC-42/59/03). Markdown-first must degrade gracefully.
|
|
4. **Per-note opacity.** Some notes are encrypted; never surface protected-note
|
|
ciphertext; project them as structure-shell (UC-61).
|
|
5. **One Trilium instance = one shard**, not the federation layer; its sync protocol is
|
|
its own — attach via ETAPI/replica, don't re-drive it (not-a-sync-daemon).
|
|
|
|
### 8.3 What Trilium teaches that shard-wiki should keep
|
|
|
|
- **Separate page identity from placement** (note vs branch) — the cleanest model for
|
|
multi-location / multi-shard pages and for the clone/reference primitive (T16).
|
|
- **Model metadata as computed** (own + inherited + templated) with per-attribute
|
|
provenance (UC-67) — not a flat key/value bag.
|
|
- **Content opacity is per-item**, not only whole-shard (UC-61 refinement).
|
|
|
|
---
|
|
|
|
## 9. Use-case seeds → catalog (promoted 2026-06-14)
|
|
|
|
Last existing UC is **UC-65**. New UCs **UC-66, UC-67** added; existing UCs enriched.
|
|
|
|
| Seed | Catalog action |
|
|
|------|----------------|
|
|
| **Attach a shard with a DAG hierarchy / note cloning** — a page may occupy multiple namespace locations at once (note identity separated from placement via branches); no single canonical path | **UC-66 (new)** |
|
|
| **Preserve inherited / templated attributes** — project a structured shard whose metadata is computed (own + inherited + template), distinguishing effective vs own with per-attribute provenance | **UC-67 (new)** |
|
|
| DAG/multi-parent vs tree; note/branch (identity vs placement) | **enriches UC-22** |
|
|
| Labels (`#tag`) + typed relations (`~relation`); HTML-native | **enriches UC-34** |
|
|
| Templates (`~template`) inject attribute sets | **enriches UC-15** |
|
|
| HTML (CKEditor) content → HTML↔Markdown lossy translation | **enriches UC-42** (links UC-59) |
|
|
| Per-note encryption (protected notes) = **partial** content opacity | **enriches UC-61** |
|
|
| Scripting (frontend/backend code notes, Script API) = in-app host | **enriches UC-38** |
|
|
| ETAPI (token REST) = designed external surface for a self-host server | links UC-57 |
|
|
| Render/script notes + attribute queries = dynamic content | links UC-54; typed relations link UC-58 |
|
|
| `noteId`/`branchId` 12-char IDs | links UC-51 |
|
|
|
|
---
|
|
|
|
## 10. Architecture notes for SHARD-WP-0002 (no UC)
|
|
|
|
- **Namespace model must support a DAG** and **separate page identity from placement**
|
|
(note/branch). A page is one entity with N placements (paths/shards). Feeds the page/
|
|
namespace model and the clone/reference primitive. (T12, T16.)
|
|
- **Computed/inherited metadata** (UC-67): the page model's structured-data
|
|
representation must carry **effective vs own** with per-attribute provenance
|
|
(own/inherited/template), not a flat bag. (T12.)
|
|
- **Content opacity is per-item** (UC-61 refinement): the content-opacity capability
|
|
(proposed twelfth spectrum) should be **granular** (per-note), not only whole-shard.
|
|
(T11.)
|
|
- **HTML as a source content model** joins TML/Notion-blocks in the translation
|
|
capability (HTML↔Markdown, lossy-aware). (T15.)
|
|
- **Scripting as an in-engine host** + **ETAPI external REST** are two more adapter-host
|
|
exemplars (with Roam/Obsidian/Joplin). (T14.)
|
|
|
|
---
|
|
|
|
## 11. Open questions (for spec / workplans)
|
|
|
|
1. How does shard-wiki represent a **cloned note** in the union — one page with multiple
|
|
path placements, or a page transcluded into multiple locations? (UC-66 vs UC-44/45.)
|
|
2. When projecting **inherited attributes** (UC-67), does shard-wiki materialize
|
|
effective values (snapshot) or compute them live from the shard's tree/templates?
|
|
3. Is **HTML↔Markdown** round-trip lossless enough for write-back overlays, or are
|
|
Trilium overlays read-only/native-HTML (cf. UC-42 Q2)?
|
|
4. For **per-note encryption** (UC-61), is a partially-opaque shard projected with
|
|
protected notes as visible-but-opaque placeholders, or hidden entirely?
|
|
|
|
---
|
|
|
|
## 12. Sources
|
|
|
|
| Source | Used for |
|
|
|--------|----------|
|
|
| TriliumNext/Trilium — DeepWiki (https://deepwiki.com/TriliumNext/Trilium) | SQLite `document.db`/better-sqlite3; tree + cloning via branches; note types; sync |
|
|
| Cloning notes — TriliumNext wiki (https://github.com/TriliumNext/Trilium/wiki/Cloning-notes) | Note in multiple locations; branches; branch prefixes |
|
|
| TriliumNext — DeepWiki API & Synchronization (https://deepwiki.com/TriliumNext/Trilium/4.3-api-and-synchronization) | WebSocket + sync protocol w/ conflict resolution; 12-char IDs (note/branch/attribute/attachment) |
|
|
| ETAPI (REST API) — Trilium docs (https://docs.triliumnotes.org/User%20Guide/User%20Guide/Advanced%20Usage/ETAPI%20(REST%20API)/) | Public REST API since v0.50; token/Bearer auth; trilium-py |
|
|
| Script API — TriliumNext wiki (https://github.com/TriliumNext/Trilium/wiki/Script-API) | Frontend/backend code notes; Script API |
|
|
| BrightCoding — TriliumNext overview (https://www.blog.brightcoding.dev/2025/09/20/triliumnext-notes...) | Attributes (labels/relations), inheritance, templates, per-note encryption, note types |
|
|
|
|
Cross-references: `research/260614-joplin-deep-dive/findings.md` (SQLite-local, dual
|
|
surface, content opacity), `research/260614-notion-deep-dive/findings.md` (typed
|
|
relations, external API), `research/260614-zigzag-deep-dive/findings.md` (clone /
|
|
dimensions), `research/260614-shard-spectrum-synthesis/findings.md` (spectra this
|
|
refines), `spec/UseCaseCatalog.md` (UC-15, UC-22, UC-34, UC-38, UC-42, UC-51, UC-54,
|
|
UC-57, UC-58, UC-61), `workplans/SHARD-WP-0002-federation-architecture.md` (T11, T12,
|
|
T14, T15, T16).
|
|
|
|
---
|
|
|
|
## 13. Traceability
|
|
|
|
- New UCs: **UC-66, UC-67** → `spec/UseCaseCatalog.md`.
|
|
- Enriched UCs: **UC-15, UC-22, UC-34, UC-38, UC-42, UC-61** (links UC-51, UC-54, UC-57,
|
|
UC-58, UC-59).
|
|
- Architecture (no UC): DAG namespace + identity/placement (note/branch) split;
|
|
computed/inherited metadata; per-item content opacity; HTML source model; scripting +
|
|
ETAPI host surfaces → `SHARD-WP-0002` (T11, T12, T14, T15, T16).
|
|
- Boundary recorded: Trilium (TriliumNext) is **one SQLite-local candidate shard** with a
|
|
DAG hierarchy and computed metadata, best attached via **ETAPI**; HTML-native (lossy to
|
|
Markdown), per-note opacity; not a substrate, not the federation layer (INTENT
|
|
graceful-degradation, no-silent-mutation, not-a-sync-daemon).
|
|
</content>
|