Skip to content

Architecture

github-actions[bot] edited this page Oct 19, 2026 · 10 revisions

Architecture

ThoughtKnot is built in layers. Each layer only uses the ones below it, so a new app can be added on top without changing what is underneath.

This page describes main, which the nightly build is made from. The latest release is 0.7.0: ✅ is in it, 🌙 is only in the nightly build, 🟡 is being built and ⏳ is planned.

The layers

The drawing is made from requirements/architecture.gql, a graph note kept in the repository. Each box is a part, and each six-sided box is an interface between layers. A dashed box is planned.

flowchart TB
  subgraph plugins["Plug-ins"]
    built_in_exports["Built-in data exports<br/><i>:Part</i>"]
  end
  subgraph apps["Apps"]
    obsidian["Obsidian plugin<br/><i>:Part</i>"]
    claude_code["Browser editor, from Claude Code<br/><i>:Part</i>"]
    standalone["Standalone app (Tauri)<br/><i>:Part</i>"]
    vscode["VS Code plugin<br/><i>:Part</i>"]
  end
  subgraph display["Display layer"]
    display_part["Display layer<br/><i>:Part</i>"]
  end
  subgraph core["Core"]
    core_part["Core<br/><i>:Part</i>"]
  end
  subgraph library["Graph format library"]
    graph_spec_js["Graph format library (JavaScript)<br/><i>:Part</i>"]
    graph_spec_py["Graph format library (Python)<br/><i>:Part</i>"]
  end
  host_interface{{"Host interface (DisplayHost)<br/><i>:Interface</i>"}}
  plugin_interface{{"Plug-in interface<br/><i>:Interface</i>"}}
  storage_interface{{"Storage interface<br/><i>:Interface</i>"}}
  problem_shapes{{"Problems and fixes (Language Server Protocol shapes)<br/><i>:Interface</i>"}}
  plugins -->|uses| display
  apps -->|uses| display
  display -->|uses| core
  core -->|uses| library
  graph_spec_js -.-|offers| problem_shapes
  core_part -.-|offers| plugin_interface
  core_part -.-|offers| storage_interface
  display_part -.-|offers| host_interface
  obsidian -->|implements| host_interface
  claude_code -->|implements| host_interface
  standalone -->|implements| host_interface
  vscode -->|implements| host_interface
  built_in_exports -->|implements| plugin_interface
  classDef planned stroke-dasharray: 5 5,opacity:0.7
  class standalone,vscode planned
Loading
Layer What it does Parts
Plug-ins Add exports, theme colours and, later, diagram types, steps, storage and AI providers, through one checked interface. Built-in data exports
Apps Put the editor where people work, each through a small adapter of its own. Obsidian plugin
Browser editor, from Claude Code
Standalone app (Tauri) (⏳ Planned, standalone_app)
VS Code plugin (⏳ Planned, vscode_plugin)
Display layer Draws the card, the details and the forms with the web page alone, the same in every app. Display layer
Core Works out what to draw, the problems and their fixes, and every edit, with no app inside. Core
Graph format library Reads a graph written in a note and checks it against the rules, in JavaScript and Python. Graph format library (JavaScript)
Graph format library (Python)

The interfaces

Every interface between layers has a contract: a set of checks that any app or plug-in runs against itself. If it passes, it fits.

Interface Where it is defined Requirements
Host interface (DisplayHost) packages/core/src/display/host.ts
checks: packages/core/src/display/host-contract.ts
🟡 host_contract_published
🟡 hosts_pass_contract
Plug-in interface packages/core/src/plugins.ts
checks: packages/core/src/plugins-contract.ts
🟡 plugin_interface
🟡 plugin_manifest_limits
🟡 plugin_contract_suites
🟡 plugin_theme_tokens
Storage interface Not written yet ⏳ storage_contract
Problems and fixes (Language Server Protocol shapes) packages/graph-spec/src/reader/problem-codes.json ✅ lsp_shapes
🟡 problem_codes

How a note becomes a drawing

flowchart LR
  N["A note<br/><i>:Note</i>"] -->|read by| L["Graph format library<br/><i>:Layer</i>"]
  L -->|graph and problems| C["Core<br/><i>:Layer</i>"]
  C -->|layout source| G["Graphviz, in a worker<br/><i>:Engine</i>"]
  G -->|picture| D["Display layer<br/><i>:Layer</i>"]
  D -->|card, details, forms| A["The app<br/><i>:Host</i>"]
  A -->|an edit| C
  C -->|new text| N
Loading
  1. The graph format library reads each gql block of the note and checks it against the rules.
  2. The core works out what to draw, the problems and their fixes. It writes a layout source that is the same, byte for byte, in JavaScript and Python.
  3. Graphviz lays the drawing out in a worker, so the page never freezes.
  4. The display layer shows the card, the details and the forms, using only the web page.
  5. The app gives the few things only it knows: icons, notices, menus, its language.
  6. An edit goes back through the core, which writes the new text into the note. The note is always the one store.

The rules between layers

flowchart LR
  s10(["§10 Hosts — one core, Obsidian first, VS Code later<br/><i>:Section</i>"])
  subgraph g_released["✅ Released"]
    host_free_core["host_free_core<br/><i>:Requirement</i>"]
    format_as_library["format_as_library<br/><i>:Requirement</i>"]
    lsp_shapes["lsp_shapes<br/><i>:Requirement</i>"]
    one_colour_map["one_colour_map<br/><i>:Requirement</i>"]
  end
  s10 --- g_released
Loading
Requirement Where it stands Level Rule
host_free_core ✅ Released must The core must not import obsidian or vscode.
format_as_library ✅ Released must The reader, the rules, completeness and the layout source must live in their own package, which the plugin uses, and the vault must carry only a generated, read-only copy of each release.
lsp_shapes ✅ Released must Problems, fixes and suggestions must use the shapes of the Language Server Protocol
one_colour_map ✅ Released should Theme colours should reach each host through one mapping table.

The plug-ins, layers and contracts

flowchart LR
  s43(["§43 Plug-ins, layers and their contracts<br/><i>:Section</i>"])
  subgraph g_released["✅ Released"]
    affected_tests_by_imports["affected_tests_by_imports<br/><i>:Requirement</i>"]
  end
  s43 --- g_released
  subgraph g_part["🟡 Being built"]
    plugin_interface["plugin_interface<br/><i>:Requirement</i>"]
    plugin_manifest_limits["plugin_manifest_limits<br/><i>:Requirement</i>"]
    plugin_contract_suites["plugin_contract_suites<br/><i>:Requirement</i>"]
    hosts_pass_contract["hosts_pass_contract<br/><i>:Requirement</i>"]
  end
  s43 --- g_part
  subgraph g_open["⏳ Planned"]
    layers_checked["layers_checked<br/><i>:Requirement</i>"]
    plugin_rtl["plugin_rtl<br/><i>:Requirement</i>"]
  end
  s43 --- g_open
Loading
Requirement Where it stands Level Rule
plugin_interface 🟡 Being built must Diagram types, data exports, graph steps and sources, storage backends, AI providers and the commands and panels a plug-in adds must be reached through one typed, versioned plug-in interface, published with the library, and the built-in ones must register through it too.
plugin_manifest_limits 🟡 Being built must A plug-in must declare in a manifest the extension points and host abilities it uses and get nothing else, and a plug-in that throws or runs too long must be stopped and named, never leave a drawing blank.
plugin_contract_suites 🟡 Being built must Each extension point must ship a contract suite that any plug-in can run against itself, and every built-in must pass it in CI.
hosts_pass_contract 🟡 Being built must Every host's adapter, Obsidian's, the browser page's, the standalone app's and VS Code's, must pass the one shared host contract suite in CI, run against the real adapter.
layers_checked ⏳ Planned must Each layer must import only the layers below it, from the graph format library through the core, the display layer and the plug-ins to the hosts, checked in CI on every change.
affected_tests_by_imports ✅ Released must The quick test run must choose, besides the tests named after a changed module, every test that reaches it through the import graph, by importing it directly or through others or by naming it to build, and run the whole suite only for a change no test reaches or one every test shares.
plugin_rtl ⏳ Planned must Every plug-in must work in a right-to-left language: what it adds reads in the host's direction, keeps the person's names in their own direction and uses logical sides only, checked by its extension point's contract suite (Roger, 18 Oct 2026).

Designs

Clone this wiki locally