terraphim/terraphim-editor

Rust

0

299 commits

updated Oct 11, 2026

See the code

See what people are saying

SourceMessageScoreDate

Terraphim: carrying authored synonyms from a Rust/Wasm editor into a native Rust search CLI (r/rust)

I work on Terraphim Editor and terraphim-grep. The implementation problem I wanted to solve was preserving a wording decision across two processes: choose between “Risk” and “Concern” in a browser editor, then search for “concern” in a Markdown file that only contains “risk”. The editor is Rust…

0

Oct 12, 2026

README

Terraphim Editor

A Markdown editor for writers who keep changing their minds.

Keep several versions of every word, sentence and paragraph, cycle between them in place, fade what you are unsure of, stash what does not fit, and let the Lab show you what to cut. Rust compiled to WebAssembly, plain JavaScript, no framework.

Rust WebAssembly Trunk Licence

Write_On mode: a full-window writing surface with corner controls

Features · Run it locally · Try it in five minutes · Keyboard shortcuts · Embed it · Develop


Why

Drafting is mostly choosing. Ordinary editors make you delete the version you are not sure about; Terraphim Editor keeps every alternative attached to the text it belongs to, inside the Markdown file itself, so you can decide later. Nothing is rewritten without you asking, and every change is one undo step.

Features

Alternatives panel with Word, Sentence and Paragraph tabs

Alternatives, in place

Any word, sentence or paragraph can hold several versions. Dots under the text show how many and which is active; hover and press Up or Down to cycle, and "a" / "an" fix themselves. The panel lists every version, one editable line each: a dot for yours, a robot for AI or knowledge-graph lines.
Knowledge-graph terms with synonym dots

Knowledge-graph synonyms

Load a thesaurus and every term it knows becomes cyclable, with capitalisation kept. Matching comes from terraphim_lsp_core, the same engine as the Terraphim language server. These spans are derived each time and never written into your file.
A ghosted sentence and the selection menu

Ghost it

Not sure about a sentence? Fade it. Ghosted text stays editable, counted and saved, and is left out of exports. Right-click a selection for the menu, or press Ctrl+/.
The Overflow panel beside the page

Overflow

A scratch area for the tangent that does not belong yet. Stash a selection there with Ctrl+Shift+X (one undo step); pull it back with Ctrl+Enter or by dragging. Saved with the document, never exported.
The Lab marking the weakest sentences

The Lab

Six deterministic checks that mark and never rewrite: typos and punctuation (with fixes you can accept), the weakest sentences, sentences that run long, convoluted sentences, words off the tone, and hedges and filler.
Trim preview with the status card

Trim

Ask for a 10%, 20%, 30% or 50% trim and see the cuts faded with the word count before and after. Click a cut to keep it, walk through them one by one, then make the cuts as a single undo step.
The Blocks view: the document as cards

Blocks view

The same text as a list of cards (headings, paragraphs, lists, quotes). Edit in place, add, delete or move blocks with Alt+Up and Alt+Down; alternatives and ghosts move with them, and a half-typed draft is never lost.
Plain mode with the toolbar and live preview shown

Plain mode

A plain text editor: the text and the word counter, nothing more. The formatting shortcuts and the / command palette work as they are; Ctrl+Shift+L shows the classic formatting toolbar and live Markdown preview (pictured). Click the word counter to switch between plain and Write_On.
The restore-draft notice after a reload

Save and open real files

Ctrl+S saves the document, alternatives, ghosts and overflow included, as a .md file: in Chrome and Edge the first save asks where and later saves write to the same file; in Firefox and Safari it downloads. Ctrl+O, the folder icon or dropping a file opens one and restores everything. A dot shows unsaved changes, and an autosaved draft survives a reload.
The Markdown export dialog

Markdown export

M↓ (or the toolbar's Markdown button, Ctrl+Shift+L in plain mode) shows the clean Markdown: the active alternatives, without ghosted text, the overflow or the annotation block, ready to copy or download.

Your alternatives, ghosts and overflow live in a small trailing block at the end of the Markdown file:

Every team runs on a handful of trade-offs.

```terraphim-alternatives
{"version":1,"spans":[{"id":"s1","kind":"word","anchor":{"start":32,"end":42,"text":"trade-offs"},
  "active":0,"alts":[{"text":"trade-offs","source":"original"},{"text":"choices","source":"human"}]}]}
```

Any Markdown viewer still shows the text; Terraphim Editor shows the alternatives.

Run it locally

You need Rust, the WebAssembly target, Trunk, and two small Node tools that build the embeddable bundle.

1. Toolchain

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh   # Rust (skip if installed)
rustup target add wasm32-unknown-unknown
cargo install trunk wasm-pack
npm install -g terser clean-css-cli                               # used by Trunk's build hook

2. Access to the Terraphim crate registry

Some dependencies (terraphim_automata, terraphim_types, terraphim_lsp_core and the editor's own crates) come from the private Terraphim registry on git.terraphim.cloud. The repository already declares it in .cargo/config.toml; you only need a Gitea token with package read access:

export CARGO_REGISTRIES_TERRAPHIM_TOKEN="Bearer <your-gitea-token>"

3. Clone and serve

git clone https://github.com/terraphim/terraphim-editor.git
cd terraphim-editor
trunk serve

Open http://127.0.0.1:8080. Trunk compiles the Rust to WebAssembly, serves the page and rebuilds on every change. The first build takes a few minutes; later ones take seconds.

4. Check everything works (optional)

cargo test --workspace          # Rust tests
scripts/browser-tests.sh        # browser tests in headless Chrome (needs Chrome)

Try it in five minutes

  1. Switch to Write_On. Click the word counter at the top left.
  2. Add alternatives. Select a word, press Ctrl+Shift+A, type another version in the panel and press Enter. Dots appear under the word.
  3. Cycle. Close the panel (Escape), hover the word and press Up or Down.
  4. Ghost a sentence. Select it and press Ctrl+/. Press it again with the caret inside to revive it.
  5. Stash a tangent. Select a paragraph and press Ctrl+Shift+X; open XYZ at the bottom right to see it.
  6. Ask the Lab. Click LAB, choose "Mark hedges and filler", then try Tighten more and Make the cuts. Ctrl+Z undoes the whole trim.
  7. Rearrange. In plain mode press Ctrl+Shift+L for the toolbar, switch to Blocks and move a paragraph with Alt+Down.
  8. Save. Press Ctrl+S (or click the floppy). In Chrome and Edge the first save asks where to put the .md file and later saves write to it; in Firefox and Safari it downloads. Opening it again (Ctrl+O, the folder icon, or dropping the file on the editor) restores every alternative, ghost and stash, and unsaved work survives a reload as a draft the editor offers to restore.

Keyboard shortcuts

Formatting and selection shortcuts use Ctrl on every platform (not Cmd); undo, redo, save, open and Ctrl+Enter also accept Cmd. The keyboard glyph in Write_On mode shows the same reference.

Writing
KeysAction
Ctrl+B / Ctrl+IBold / italic
Ctrl+K / Ctrl+LInline code / link
Ctrl+HHeading
/Command palette
Ctrl+Shift+LShow or hide the toolbar and preview (plain mode)
Ctrl+Z / Ctrl+Y (or Cmd)Undo / redo
Ctrl+S / Ctrl+O (or Cmd)Save / open a file (replaces the browser's own)
Selection (Write_On)
KeysAction
Right-click, Menu key or Shift+F10Selection menu
Ctrl+Shift+AAlternatives for selection
Ctrl+Shift+GAI alternatives for selection (knowledge-graph synonyms)
Ctrl+/Ghost it / Revive
Ctrl+Shift+XStash this in Overflow
Alternatives
KeysAction
Hover a span, Up / DownCycle in place (wraps round)
Alt+Up / Alt+Down, caret in a spanCycle without the mouse
Click a dotJump to that alternative
Panel: Up / DownMake a line active
Panel: Alt+Up / Alt+DownReorder
Panel: Enter on the last lineAdd an alternative
EscapeClose the panel
Overflow, Lab and trim
KeysAction
XYZ (bottom right)Open or close Overflow
Ctrl+Enter in OverflowInsert the selection (or current line) at the caret
LAB (bottom centre)Open the Lab; Up / Down, Home / End move between actions
Walk through: Left / Right, K, EscapePrevious / next cut, keep it, stop
Blocks view
KeysAction
Up / Down, Home / EndMove between cards
Enter or F2Edit the block
Shift+EnterAdd a paragraph below
DeleteDelete the block
Alt+Up / Alt+DownMove the block
Ctrl+Enter / Escape (editing)Commit / close (typed text is kept in the drafts notice)

Embed it in a page

trunk build --release writes an embeddable bundle next to the standalone page in target/trunk-dist: terraphim-editor.min.js, terraphim-editor.min.css, the WebAssembly module (terraphim_editor.js and terraphim_editor_bg.wasm) and a working example.html. Serve those files from one directory and a page with nothing but a container gets the whole editor, Write_On chrome, alternatives, Lab, Blocks, Overflow and save/open included:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
<link rel="stylesheet" href="terraphim-editor.min.css">
<div id="editor" style="height: 80vh"></div>
<script src="terraphim-editor.min.js"></script>
<script type="module">
  const editor = await TeraphimEditor.create(document.getElementById('editor'), {
    value: '# Hello',
    // wasmUrl / glueUrl: only if the module is not next to the script
    // standalone: true: Ctrl+S / Ctrl+O anywhere on the page
  });
  editor.setValue('# Hello again');
  editor.on('te:saved', (event) => console.log(event.detail.text));
</script>

The editor object has getValue(), setValue(), openDocument(text, name), saveDocument() (Markdown plus the annotation block), exportDocument() (clean Markdown), isWriteOn() / setWriteOn(), on() / off() for its te:* events, persistence and destroy(). Shoelace is loaded from its CDN; link FontAwesome yourself, as above. One editor per page: the document model lives in the WebAssembly module as a single instance, so a second create() is refused until the first is destroyed. Write_On themes the editor's own container and never restyles the host page; its corner controls sit in the viewport corners, so give the editor most of the page. Design notes: docs/design/embedding.md.

The bundle's example.html: an embedded editor in a host page

The standalone page (index.html) creates its editor on load and exposes it as window.terraphimEditor; with the embed, use the object create() returns. Either way a host script can take over storage:

<script>
  document.addEventListener('te:save', (event) => {
    event.preventDefault(); // store documents yourself instead of a file
    const text = window.terraphimEditor.saveDocument(); // Markdown plus annotation block
    fetch('/documents/draft.md', { method: 'PUT', body: text })
      .then(() => window.terraphimEditor.persistence.markClean());
  });

  document.addEventListener('te:open', (event) => {
    event.preventDefault();
    fetch('/documents/draft.md')
      .then((response) => response.text())
      .then((text) => window.terraphimEditor.openDocument(text, 'draft.md'));
  });
</script>

te:save, te:open and te:markdown (the M↓ control) bubble to document and are cancelable; without preventDefault() the editor saves to a file or download, opens a file, and shows the export dialog. te:saved carries the serialised document (detail.text) whenever the editor saves; te:written, te:opened, te:dirty-change, te:mode-change and te:blocks-drafts report what happened. exportDocument() returns the clean Markdown. Set fileSystemAccess: false in the editor config to always download instead of using the browser's file picker. In an embedding page Ctrl+S and Ctrl+O act only while focus is in the editor, so the host's own shortcuts keep working; standalone: true (what the standalone page sets) makes them act anywhere. Design notes: docs/design/persistence.md.

How it is built

flowchart LR
  subgraph Browser
    UI["public/js<br/>surface, chrome, panels,<br/>Lab, trim, Blocks"]
  end
  subgraph WebAssembly
    Bridge["src/<br/>document, lab, kg bridges"]
    Alt["terraphim_alternatives<br/>spans, ghosts, overflow,<br/>annotation block"]
    Lab["terraphim_lab<br/>marks and trim"]
    Core["terraphim_lsp_core<br/>KG matching, synonyms"]
  end
  UI <--> Bridge
  Bridge --> Alt
  Bridge --> Lab
  Bridge --> Core
  Core --> Alt
  Lab --> Alt

The Rust model is the source of truth: every edit goes to it first, and the surface follows, so undo and redo restore text and annotations together.

PathContents
src/WebAssembly entry point, Markdown rendering, the document, Lab and knowledge-graph bridges
crates/terraphim_alternativesThe span model, annotation block, re-anchoring, moves and the shared word count
crates/terraphim_labThe Lab engine: mark actions and trim
public/js/, public/css/The editor surface and features (including save, open and export); design tokens
docs/requirements/, docs/design/Specification and per-feature design notes
tests/Native tests and the browser test binaries (web_*.rs)

Develop and test

CommandWhat it does
trunk serveDevelopment server with live rebuild at http://127.0.0.1:8080
cargo test --workspaceRust unit and integration tests
scripts/browser-tests.shEvery browser test binary in headless Chrome, one at a time, with a time and load table. Before each binary it waits (saying what for) for the load to drop below a limit (20 on Linux, three quarters of the CPUs on macOS); it skips the benchmarks under heavy load (exit code 3) and, locally, stops without running the rest if the load stays high for 5 minutes (exit code 4; on CI it only warns). Use it for every local browser run (see CLAUDE.md, "Browser tests")
scripts/browser-tests.sh web_kgA single browser test binary, load-guarded
cargo clippy --workspace --all-targets -- -D warningsLints (also run with --target wasm32-unknown-unknown --lib --tests)
cargo benchCriterion benchmarks
scripts/release-dist.shRegenerate the committed release copy in dist/

Browser tests drive the real DOM and the real WebAssembly bindings; there are no mocks. Development builds write to target/trunk-dist; dist/ changes only through scripts/release-dist.sh.

Contributing

Work is tracked in Gitea issues at git.terraphim.cloud. Create a branch per issue (task/<number>-<short-title>), keep each change one undo step for the writer, and open a pull request against main.

Licence

MIT. See LICENSE.

terraphim/terraphim-editor

Rust

0

299 commits

updated Oct 11, 2026

See the code

See what people are saying

SourceMessageScoreDate

Terraphim: carrying authored synonyms from a Rust/Wasm editor into a native Rust search CLI (r/rust)

I work on Terraphim Editor and terraphim-grep. The implementation problem I wanted to solve was preserving a wording decision across two processes: choose between “Risk” and “Concern” in a browser editor, then search for “concern” in a Markdown file that only contains “risk”. The editor is Rust…

0

Oct 12, 2026

README

Terraphim Editor

A Markdown editor for writers who keep changing their minds.

Keep several versions of every word, sentence and paragraph, cycle between them in place, fade what you are unsure of, stash what does not fit, and let the Lab show you what to cut. Rust compiled to WebAssembly, plain JavaScript, no framework.

Rust WebAssembly Trunk Licence

Write_On mode: a full-window writing surface with corner controls

Features · Run it locally · Try it in five minutes · Keyboard shortcuts · Embed it · Develop


Why

Drafting is mostly choosing. Ordinary editors make you delete the version you are not sure about; Terraphim Editor keeps every alternative attached to the text it belongs to, inside the Markdown file itself, so you can decide later. Nothing is rewritten without you asking, and every change is one undo step.

Features

Alternatives panel with Word, Sentence and Paragraph tabs

Alternatives, in place

Any word, sentence or paragraph can hold several versions. Dots under the text show how many and which is active; hover and press Up or Down to cycle, and "a" / "an" fix themselves. The panel lists every version, one editable line each: a dot for yours, a robot for AI or knowledge-graph lines.
Knowledge-graph terms with synonym dots

Knowledge-graph synonyms

Load a thesaurus and every term it knows becomes cyclable, with capitalisation kept. Matching comes from terraphim_lsp_core, the same engine as the Terraphim language server. These spans are derived each time and never written into your file.
A ghosted sentence and the selection menu

Ghost it

Not sure about a sentence? Fade it. Ghosted text stays editable, counted and saved, and is left out of exports. Right-click a selection for the menu, or press Ctrl+/.
The Overflow panel beside the page

Overflow

A scratch area for the tangent that does not belong yet. Stash a selection there with Ctrl+Shift+X (one undo step); pull it back with Ctrl+Enter or by dragging. Saved with the document, never exported.
The Lab marking the weakest sentences

The Lab

Six deterministic checks that mark and never rewrite: typos and punctuation (with fixes you can accept), the weakest sentences, sentences that run long, convoluted sentences, words off the tone, and hedges and filler.
Trim preview with the status card

Trim

Ask for a 10%, 20%, 30% or 50% trim and see the cuts faded with the word count before and after. Click a cut to keep it, walk through them one by one, then make the cuts as a single undo step.
The Blocks view: the document as cards

Blocks view

The same text as a list of cards (headings, paragraphs, lists, quotes). Edit in place, add, delete or move blocks with Alt+Up and Alt+Down; alternatives and ghosts move with them, and a half-typed draft is never lost.
Plain mode with the toolbar and live preview shown

Plain mode

A plain text editor: the text and the word counter, nothing more. The formatting shortcuts and the / command palette work as they are; Ctrl+Shift+L shows the classic formatting toolbar and live Markdown preview (pictured). Click the word counter to switch between plain and Write_On.
The restore-draft notice after a reload

Save and open real files

Ctrl+S saves the document, alternatives, ghosts and overflow included, as a .md file: in Chrome and Edge the first save asks where and later saves write to the same file; in Firefox and Safari it downloads. Ctrl+O, the folder icon or dropping a file opens one and restores everything. A dot shows unsaved changes, and an autosaved draft survives a reload.
The Markdown export dialog

Markdown export

M↓ (or the toolbar's Markdown button, Ctrl+Shift+L in plain mode) shows the clean Markdown: the active alternatives, without ghosted text, the overflow or the annotation block, ready to copy or download.

Your alternatives, ghosts and overflow live in a small trailing block at the end of the Markdown file:

Every team runs on a handful of trade-offs.

```terraphim-alternatives
{"version":1,"spans":[{"id":"s1","kind":"word","anchor":{"start":32,"end":42,"text":"trade-offs"},
  "active":0,"alts":[{"text":"trade-offs","source":"original"},{"text":"choices","source":"human"}]}]}
```

Any Markdown viewer still shows the text; Terraphim Editor shows the alternatives.

Run it locally

You need Rust, the WebAssembly target, Trunk, and two small Node tools that build the embeddable bundle.

1. Toolchain

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh   # Rust (skip if installed)
rustup target add wasm32-unknown-unknown
cargo install trunk wasm-pack
npm install -g terser clean-css-cli                               # used by Trunk's build hook

2. Access to the Terraphim crate registry

Some dependencies (terraphim_automata, terraphim_types, terraphim_lsp_core and the editor's own crates) come from the private Terraphim registry on git.terraphim.cloud. The repository already declares it in .cargo/config.toml; you only need a Gitea token with package read access:

export CARGO_REGISTRIES_TERRAPHIM_TOKEN="Bearer <your-gitea-token>"

3. Clone and serve

git clone https://github.com/terraphim/terraphim-editor.git
cd terraphim-editor
trunk serve

Open http://127.0.0.1:8080. Trunk compiles the Rust to WebAssembly, serves the page and rebuilds on every change. The first build takes a few minutes; later ones take seconds.

4. Check everything works (optional)

cargo test --workspace          # Rust tests
scripts/browser-tests.sh        # browser tests in headless Chrome (needs Chrome)

Try it in five minutes

  1. Switch to Write_On. Click the word counter at the top left.
  2. Add alternatives. Select a word, press Ctrl+Shift+A, type another version in the panel and press Enter. Dots appear under the word.
  3. Cycle. Close the panel (Escape), hover the word and press Up or Down.
  4. Ghost a sentence. Select it and press Ctrl+/. Press it again with the caret inside to revive it.
  5. Stash a tangent. Select a paragraph and press Ctrl+Shift+X; open XYZ at the bottom right to see it.
  6. Ask the Lab. Click LAB, choose "Mark hedges and filler", then try Tighten more and Make the cuts. Ctrl+Z undoes the whole trim.
  7. Rearrange. In plain mode press Ctrl+Shift+L for the toolbar, switch to Blocks and move a paragraph with Alt+Down.
  8. Save. Press Ctrl+S (or click the floppy). In Chrome and Edge the first save asks where to put the .md file and later saves write to it; in Firefox and Safari it downloads. Opening it again (Ctrl+O, the folder icon, or dropping the file on the editor) restores every alternative, ghost and stash, and unsaved work survives a reload as a draft the editor offers to restore.

Keyboard shortcuts

Formatting and selection shortcuts use Ctrl on every platform (not Cmd); undo, redo, save, open and Ctrl+Enter also accept Cmd. The keyboard glyph in Write_On mode shows the same reference.

Writing
KeysAction
Ctrl+B / Ctrl+IBold / italic
Ctrl+K / Ctrl+LInline code / link
Ctrl+HHeading
/Command palette
Ctrl+Shift+LShow or hide the toolbar and preview (plain mode)
Ctrl+Z / Ctrl+Y (or Cmd)Undo / redo
Ctrl+S / Ctrl+O (or Cmd)Save / open a file (replaces the browser's own)
Selection (Write_On)
KeysAction
Right-click, Menu key or Shift+F10Selection menu
Ctrl+Shift+AAlternatives for selection
Ctrl+Shift+GAI alternatives for selection (knowledge-graph synonyms)
Ctrl+/Ghost it / Revive
Ctrl+Shift+XStash this in Overflow
Alternatives
KeysAction
Hover a span, Up / DownCycle in place (wraps round)
Alt+Up / Alt+Down, caret in a spanCycle without the mouse
Click a dotJump to that alternative
Panel: Up / DownMake a line active
Panel: Alt+Up / Alt+DownReorder
Panel: Enter on the last lineAdd an alternative
EscapeClose the panel
Overflow, Lab and trim
KeysAction
XYZ (bottom right)Open or close Overflow
Ctrl+Enter in OverflowInsert the selection (or current line) at the caret
LAB (bottom centre)Open the Lab; Up / Down, Home / End move between actions
Walk through: Left / Right, K, EscapePrevious / next cut, keep it, stop
Blocks view
KeysAction
Up / Down, Home / EndMove between cards
Enter or F2Edit the block
Shift+EnterAdd a paragraph below
DeleteDelete the block
Alt+Up / Alt+DownMove the block
Ctrl+Enter / Escape (editing)Commit / close (typed text is kept in the drafts notice)

Embed it in a page

trunk build --release writes an embeddable bundle next to the standalone page in target/trunk-dist: terraphim-editor.min.js, terraphim-editor.min.css, the WebAssembly module (terraphim_editor.js and terraphim_editor_bg.wasm) and a working example.html. Serve those files from one directory and a page with nothing but a container gets the whole editor, Write_On chrome, alternatives, Lab, Blocks, Overflow and save/open included:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
<link rel="stylesheet" href="terraphim-editor.min.css">
<div id="editor" style="height: 80vh"></div>
<script src="terraphim-editor.min.js"></script>
<script type="module">
  const editor = await TeraphimEditor.create(document.getElementById('editor'), {
    value: '# Hello',
    // wasmUrl / glueUrl: only if the module is not next to the script
    // standalone: true: Ctrl+S / Ctrl+O anywhere on the page
  });
  editor.setValue('# Hello again');
  editor.on('te:saved', (event) => console.log(event.detail.text));
</script>

The editor object has getValue(), setValue(), openDocument(text, name), saveDocument() (Markdown plus the annotation block), exportDocument() (clean Markdown), isWriteOn() / setWriteOn(), on() / off() for its te:* events, persistence and destroy(). Shoelace is loaded from its CDN; link FontAwesome yourself, as above. One editor per page: the document model lives in the WebAssembly module as a single instance, so a second create() is refused until the first is destroyed. Write_On themes the editor's own container and never restyles the host page; its corner controls sit in the viewport corners, so give the editor most of the page. Design notes: docs/design/embedding.md.

The bundle's example.html: an embedded editor in a host page

The standalone page (index.html) creates its editor on load and exposes it as window.terraphimEditor; with the embed, use the object create() returns. Either way a host script can take over storage:

<script>
  document.addEventListener('te:save', (event) => {
    event.preventDefault(); // store documents yourself instead of a file
    const text = window.terraphimEditor.saveDocument(); // Markdown plus annotation block
    fetch('/documents/draft.md', { method: 'PUT', body: text })
      .then(() => window.terraphimEditor.persistence.markClean());
  });

  document.addEventListener('te:open', (event) => {
    event.preventDefault();
    fetch('/documents/draft.md')
      .then((response) => response.text())
      .then((text) => window.terraphimEditor.openDocument(text, 'draft.md'));
  });
</script>

te:save, te:open and te:markdown (the M↓ control) bubble to document and are cancelable; without preventDefault() the editor saves to a file or download, opens a file, and shows the export dialog. te:saved carries the serialised document (detail.text) whenever the editor saves; te:written, te:opened, te:dirty-change, te:mode-change and te:blocks-drafts report what happened. exportDocument() returns the clean Markdown. Set fileSystemAccess: false in the editor config to always download instead of using the browser's file picker. In an embedding page Ctrl+S and Ctrl+O act only while focus is in the editor, so the host's own shortcuts keep working; standalone: true (what the standalone page sets) makes them act anywhere. Design notes: docs/design/persistence.md.

How it is built

flowchart LR
  subgraph Browser
    UI["public/js<br/>surface, chrome, panels,<br/>Lab, trim, Blocks"]
  end
  subgraph WebAssembly
    Bridge["src/<br/>document, lab, kg bridges"]
    Alt["terraphim_alternatives<br/>spans, ghosts, overflow,<br/>annotation block"]
    Lab["terraphim_lab<br/>marks and trim"]
    Core["terraphim_lsp_core<br/>KG matching, synonyms"]
  end
  UI <--> Bridge
  Bridge --> Alt
  Bridge --> Lab
  Bridge --> Core
  Core --> Alt
  Lab --> Alt

The Rust model is the source of truth: every edit goes to it first, and the surface follows, so undo and redo restore text and annotations together.

PathContents
src/WebAssembly entry point, Markdown rendering, the document, Lab and knowledge-graph bridges
crates/terraphim_alternativesThe span model, annotation block, re-anchoring, moves and the shared word count
crates/terraphim_labThe Lab engine: mark actions and trim
public/js/, public/css/The editor surface and features (including save, open and export); design tokens
docs/requirements/, docs/design/Specification and per-feature design notes
tests/Native tests and the browser test binaries (web_*.rs)

Develop and test

CommandWhat it does
trunk serveDevelopment server with live rebuild at http://127.0.0.1:8080
cargo test --workspaceRust unit and integration tests
scripts/browser-tests.shEvery browser test binary in headless Chrome, one at a time, with a time and load table. Before each binary it waits (saying what for) for the load to drop below a limit (20 on Linux, three quarters of the CPUs on macOS); it skips the benchmarks under heavy load (exit code 3) and, locally, stops without running the rest if the load stays high for 5 minutes (exit code 4; on CI it only warns). Use it for every local browser run (see CLAUDE.md, "Browser tests")
scripts/browser-tests.sh web_kgA single browser test binary, load-guarded
cargo clippy --workspace --all-targets -- -D warningsLints (also run with --target wasm32-unknown-unknown --lib --tests)
cargo benchCriterion benchmarks
scripts/release-dist.shRegenerate the committed release copy in dist/

Browser tests drive the real DOM and the real WebAssembly bindings; there are no mocks. Development builds write to target/trunk-dist; dist/ changes only through scripts/release-dist.sh.

Contributing

Work is tracked in Gitea issues at git.terraphim.cloud. Create a branch per issue (task/<number>-<short-title>), keep each change one undo step for the writer, and open a pull request against main.

Licence

MIT. See LICENSE.