Skip to content

Adding a host

github-actions[bot] edited this page Oct 18, 2026 · 5 revisions

Adding a host

A host is an app the editor is shown in: Obsidian, the browser editor from Claude Code and, later, the standalone app and VS Code. The editor draws everything itself with the web page. A host gives only the few things that it alone decides, through one interface, DisplayHost.

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.

How it fits

flowchart LR
  H["Your app<br/><i>:Host</i>"] -->|installHost, once| I{{"DisplayHost<br/><i>:Interface</i>"}}
  I -->|icons, notices, menus, language| D["Display layer<br/><i>:Layer</i>"]
  D --> C["Core<br/><i>:Layer</i>"]
  T["Host contract<br/><i>:Contract</i>"] -->|checks| H
Loading
  1. Write an adapter: an object with the members below, made from your app's own icons, notices and menus.
  2. Call installHost(yourAdapter) once, before anything is drawn.
  3. Run the host contract against your adapter in your tests.

The interface is at contract version 3. The version changes with every change to DisplayHost.

What a host gives

Read from packages/core/src/display/host.ts:

Member What it is for
icon Required Draws the icon named name into el, replacing what it held.
isPhone Required Whether the screen is a phone's, asked each time it matters, as it can change with the window.
notice Required Tells the person something briefly, as a notice does.
menu Required Shows a menu of entries with its top left at at, in the window of doc.
noInlineStyle Optional Whether the host's content security policy forbids inline styles (style-src without 'unsafe-inline'), as the browser editor's page does (editor spec §26, cc_csp). The core writes the colours of a drawing as style attributes. Where they are forbidden the layer sets the same declarations through the CSSOM, which the policy allows. No style attribute is ever parsed from markup. Not set: styles stay attributes, as Obsidian has them.
language Optional The language the host shows itself in, as a language tag: Obsidian's own, the display language of VS Code, the system's for the app and the browser page (editor spec §42, language_follows_host). The words are said in it when a translation for it is added, else in UK English. Not set: UK English.
direction Optional The direction the host reads in, when it sets one of its own (editor spec §42, rtl_mirrors). Not set: the direction of its language, so an Arabic or Hebrew host mirrors the interface with no word from it.

Obsidian's adapter is the shortest example: packages/obsidian/src/display-host.ts. The browser editor's is packages/claude-code/page/browser-host.ts.

Check your host

hostContract(make) gives every check. make returns a fresh adapter and a probe, which lets the checks see the notices and menus your app showed.

import { hostContract } from '@thoughtknot/core/display/host-contract';

for (const check of hostContract(makeMyHost)) await check.run();
  • ☑ an icon is drawn into its element, once, whether the host knows its name or not
  • ☑ the phone test is a true or false, asked each time
  • ☑ a notice shows its message as text, as given, markup and all
  • ☑ a menu shows its rows in order, with their lines, ticks and warnings
  • ☑ choosing a menu row runs its action once, and no other row’s
  • ☑ the style policy is said as true or false, or left unsaid
  • ☑ the language is a language tag, or left unsaid
  • ☑ the direction is ltr or rtl, or left unsaid

Rules a host follows

  • Colours. Map your app's theme to the design tokens in one place. Your theme always wins. The tokens are listed on Writing a plug-in.
  • Language and direction. Give language(), and direction() if your app sets its own. The words and the mirroring follow with no other work.
  • Security. If your app forbids inline styles, set noInlineStyle. The layer then sets styles a safe way.

The requirements for hosts

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.

Clone this wiki locally