Skip to content

Latest commit

 

History

History
221 lines (187 loc) · 9.93 KB

README.md

File metadata and controls

221 lines (187 loc) · 9.93 KB

Design specs

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.

How an action reaches a model

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&lt;R&gt; resolves
    CB-->>GUI: .then / .onError run on cbExec
Loading

Two things this diagram is making explicit, because both are easy to get wrong:

  • cbExec is independent of everything above it. Where the model ran has no bearing on where your .then runs; the callback executor you passed decides that. See core/executor.md.
  • The model's strand is what serialises access, not a mutex in the model. See concurrency_and_lifetimes.md.

Deployment topologies

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
Loading

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.

Start here — specs by the question they answer

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

Reading order

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

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".

Keeping this file cheap

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.