These files are the authoritative design reference for morph. They carry
the reasoning, invariants, and rejected alternatives that the code itself does
not record. Per AGENTS.md, read a subsystem's spec before
changing that subsystem — and when a change invalidates a spec, the spec is
what gets updated, not the other way round.
This page is a map, not a summary. It exists so that "read the spec first"
names a specific file. For the narrative walkthrough, see
docs/ARCHITECTURE.md; if you have not built anything on
morph yet, start further back, with the tutorial in
docs/GETTING-STARTED.md — the three documents form a
path: tutorial → architecture narrative → these specs.
The client half is identical in both deployments. The two diverge at
IBackend::execute, and it is worth knowing which side you are looking at:
ActionDispatcher is server-side only. LocalBackend never consults it —
it posts the action's own localOp on the model's strand.
sequenceDiagram
participant GUI as GUI / call site
participant H as BridgeHandler<Model>
participant B as Bridge
participant BE as IBackend
participant M as Model
participant CB as cbExec
GUI->>H: execute(action)
H->>B: executeVia<Model, Action>(binding, action, cbExec)
B->>BE: execute(ModelId, ActionCall, cbExec)
alt LocalBackend (in-process)
BE->>M: post call.localOp on the model's strand,<br/>under ScopedContext
else RemoteServer (over the wire)
BE->>BE: serialise ActionCall, send "execute"
BE->>M: dispatchMessage looks the model up on a pool thread,<br/>then ActionDispatcher::dispatch on the model's strand,<br/>under ScopedContext
BE->>BE: reply resolves the pending callId
end
M-->>CB: Completion<R> resolves
CB-->>GUI: .then / .onError run on cbExec
Two things this diagram is making explicit, because both are easy to get wrong:
cbExecis independent of everything above it. Where the model ran has no bearing on where your.thenruns; the callback executor you passed decides that. Seecore/executor.md.- The model's strand is what serialises access, not a mutex in the model.
See
concurrency_and_lifetimes.md.
flowchart LR
subgraph local["In-process — LocalBackend"]
direction LR
A1[GUI] --> B1[Bridge] --> L[LocalBackend] --> M1[Model]
end
subgraph remote["Client/server — over a real socket"]
direction LR
A2[GUI] --> B2[Bridge] --> W[QtWebSocketBackend]
W -. WebSocket .-> WS[QtWebSocketServer]
WS --> RS[RemoteServer] --> AD[ActionDispatcher] --> M2[Model]
end
subgraph sim["Tests — no transport"]
direction LR
A3[Test] --> B3[Bridge] --> S[SimulatedRemoteBackend] --> RS2[RemoteServer] --> M3[Model]
end
SimulatedRemoteBackend exercises the same serialise/dispatch path as the
socket transport without a socket, which is why a bug that only appears
remotely is usually reproducible in a plain unit test. Per-backend API details are in core/backend.md; the
behavioural differences between the two, collected in one table, are in
core/locality.md.
How a call flows
core/bridge.md ·
core/backend.md ·
core/registry.md ·
core/completion.md ·
core/coroutines.md ·
core/wire.md ·
core/locality.md
Threading and lifetime
core/executor.md ·
concurrency_and_lifetimes.md ·
core/callback_scope.md ·
core/shared_instances.md
When things go wrong
error_handling.md ·
core/post_commit_tail.md ·
core/logger.md ·
core/observability.md ·
core/profiler.md
Working offline
offline/offline.md ·
journal/journal.md ·
core/file_io_ops.md — the injectable file-I/O seam
both the journal and the offline queue write through, and the one to read
before writing a durability test
Identity
session/session.md ·
security.md
Schema-driven UI
forms/forms.md ·
forms/views.md ·
forms/choice.md ·
forms/widget_hints.md ·
forms/workflows_navigation.md ·
forms/engine.md — the C++ forms engine: a form's model,
rules, exact values and reactive session, headless ·
table/engine.md — sorting, filtering and view
mapping for a table, on and off the owner thread, and table::apply for a
server
Reactive state and control
reactive/signals.md ·
reactive/store.md ·
reactive/control.md — signals, view state, and server
interaction a controller declares instead of sequencing
Declarative UI
ui/document.md ·
ui/expressions.md — the UI document a server serves, its
load-time validation, and the expr/1 language its properties are written in ·
ui/interpreter.md — how a loaded document mounts: its scopes, queries and the
session's cache, mutations, relations and commands ·
ui/author.md — the server-side builders that write it, and the
registration that validates it once, with the accessibility rules ·
ui/service.md — the UI service: ui-hello, ui-catalog and ui-fetch, the catalog
filtered for the principal, and content-addressed bundles verified into the client's cache ·
ui/preferences.md — customization choices stored per person by the server's
preferences model, keyed by the verified session, and loaded back into a mounted screen ·
ui/host_vocabulary.md — the commands and values a client application registers
(host/1): checked arguments, the trust gate, can and the handshake ·
ui/catalogue.md — a bundle's message catalogues, the provider t resolves through, locale
switching and search in every locale ·
ui/theme.md — theme tokens: DTCG 2025.10 files, aliases and layering, the checks at registration,
the closed ui-theme/1 set and its defaults, and applying it on Qt Quick ·
ui/host.md — the host contract: ScreenHost, ScreenHandle, the close protocol, call and return,
and DocumentScreenHost mounting screens as roots or into a host's container ·
ui/workspace.md — the workspace shell: tabs over screens, focus by identity, singleton and
placeholder tabs, closing tab by tab until a veto, and the most-recently-used walk ·
ui/view_tree.md ·
ui/backend_contract.md ·
ui/frontend.md ·
tui/frontend.md — the view tree a screen mounts into,
the widgets every renderer implements, the seam that picks a renderer at
runtime, and the terminal renderer
Qt Quick renderer
qt_quick/backend.md ·
qt_quick/frontend.md — the view tree's widgets as
Qt Quick items, the application's own components and restyling, and the
window a client runs in
Exact values on the wire
util/rational.md ·
util/quantity_type.md ·
util/datetime.md ·
util/tagged.md — the newtype wrapper that stops two
unrelated protocol scalars sharing one underlying type from being
interchangeable
Process
VERSIONING.md ·
testing_strategy.md
Adding a model and its actions. core/bridge.md for how
a handler binds and dispatches → core/registry.md for
what registration macros actually do →
core/completion.md for the result type you return →
core/wire.md if the action must cross a socket, which
constrains the field types you may use. Then
ARCHITECTURE.md's "Adding a new model and actions"
for the concrete steps.
Debugging a dispatch that misbehaves. Establish which side ran the action
(core/locality.md tabulates the behaviour differences
that produce most of these bugs; core/backend.md has the
per-backend detail) → then
core/executor.md and
concurrency_and_lifetimes.md if the symptom
is a race, a use-after-free, or a callback on an unexpected thread →
error_handling.md if an error arrived in the wrong shape
or not at all.
pinned_facts.toml is not prose. It is the spec↔code
drift guard: each entry pins a mechanical fact some spec states in words (a
buffer size, a limit, a default) to the real symbol, and
tests/test_pinned_facts.cpp fails if they diverge, while
a gate removed on 2026-09-23 fails if the citing spec stops mentioning
it. Change a pinned value only in the same commit that changes both the code
and the prose. See CONTRIBUTING.md under "Quality
gates".
A new spec adds one line to the map above. The diagrams change only when the call path itself changes — if you find yourself restating what a spec says, that belongs in the spec.