Skip to content

docs: declarative UI program — design and implementation plans - #882

Closed
christianparpart wants to merge 2 commits into
masterfrom
docs/declarative-ui-plans
Closed

christianparpart wants to merge 2 commits into
masterfrom
docs/declarative-ui-plans

Conversation

@christianparpart

@christianparpart christianparpart commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

TL;DR — why

morph's example apps hand-write their UI controllers per toolkit: Qt controllers such as
examples/bank/gui/controllers/ and the ladder's *_qml_bridges each re-implement the same list/detail +
busy + lastError + refetch-after-write + drop-stale-reply logic (12 of the 19 QML-facing classes, per
docs/analysis/qml-bridge-boilerplate.md). The effort started to make that control declarative:

  • Controllers become plain, toolkit-free structs of signals, Query, Mutation and computed projections —
    no imperative fetch sequencing, no toolkit include (the build enforces it), testable with no UI at all.
  • The view is described once, as a toolkit-agnostic view tree with reactive bindings.
  • The frontend is a dependency-injected choice in main: every example app is one binary that runs as a
    terminal UI (core-cpp core::tui) or as Qt Quick, picked at runtime.
  • Forms: one C++ forms engine — buildable from schema JSON at runtime or typed from action types — replaces the
    shipped QML forms renderer.
  • Delivery: all of it lands on one branch as one PR with one commit per semantic unit (18 commits), planned in
    full before execution so the shape could be reviewed first.

What

The design specs and implementation plans for the declarative-UI program. Every example application describes its
UI once — declarative controllers plus a toolkit-free view tree — and one binary per app picks a terminal UI or
Qt Quick at runtime by dependency injection. A C++ forms engine replaces the shipped QML forms renderer.

Docs only; no code. This PR exists so the design can be reviewed on its own, ahead of the implementation.

Where to start

  1. docs/superpowers/plans/2026-10-04-declarative-ui-tui.md — the master plan: the 11 parts, their order, the
    one-commit-per-semantic-unit history the program lands as, and the conventions every part follows.
  2. docs/superpowers/specs/2026-10-04-declarative-ui-tui-design.md — spec 1, the core: morph::reactive
    (signals, Store<ViewState, Msg>, declarative control with Query/Mutation/Subscription), morph::ui
    (view tree, backend contract, frontend seam) and the terminal frontend.
  3. docs/superpowers/plans/2026-10-04-declarative-ui-tui-interfaces.md — the names every part shares.

Further specs: core-cpp mouse tracking (spec 0), forms engine (spec 2), Qt Quick frontend (spec 3), examples
migration (spec 4). One plan per part: …-0-core-cpp.md through …-10-examples-forms-and-retire.md.

Status of the implementation

Execution runs task by task (implementer, then an independent reviewer, then fix rounds), on the separate branch
feature/declarative-ui (pushed for
handover; no PR yet — Part 3 is mid-flight). Done so far:

The plans are not edited during execution — read them with this in mind

The master plan's rule: when a plan turns out wrong, the code and its docs/spec/ file are made right and the
deviation is stated in that part's squashed commit. Review has changed real things. The ones a reader of these plans
most needs to know:

  • Timed refresh (Parts 1, 6, 8, 9): a refresh tick is skipped while a call is in flight, and the period restarts
    whenever a call is issued. Superseding on every tick starved any fetch slower than its period. Part 6's Poller
    therefore needs its request deadline back, and one of its planned tests changes.
  • Backend contract (Parts 2–4): stricter than planned — a widget inside a hidden, disabled or collapsed container
    takes no input; no setter fires a handler (Select::setOptions included); a Select keeps a requested key across
    new options; a new Dialog is closed; a multiple-selection table reports only rows that exist; callbacks run
    untracked and a throwing callback is reported, not propagated; content mounts are all-or-nothing.
  • Frontends (Parts 3–4): both call ui::runApplication, which fixes the teardown order once, instead of
    hand-rolling it; the conformance probe interface grew.
  • Core (Part 3): runAndWait no longer hangs on a throwing or dropped task.

The full record — every ruling, review outcome and fix round per task, the pre-flight scans, and the obligations
each part places on later parts — is on
docs/declarative-ui-execution-notes
(start with its README.md and program-notes.md). It is a log, so it is kept off this PR: docs/superpowers/
holds present-tense reference docs only.

Review focus

  • Is the split into 11 parts, and the commit history they land as, the right shape for one PR?
  • The morph::reactive / morph::ui layering (spec 1 §3–§5b) and the backend contract.
  • Spec 4's migration of every example to one binary with a runtime-chosen frontend.

@christianparpart christianparpart added the documentation Improvements or additions to documentation label Oct 7, 2026
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

… plans

Screens become UI documents emitted by the server at runtime: JSON that
declares a screen's state, the models it uses, its queries and
mutations, the relations between its elements, and its view tree. A
generic client evaluates every expression in C++ with a reactive core
and generates QML from the document; custom QML components, delivered
in a verified bundle, carry the look and their own logic. Desktop and
web through Qt Quick are the primary targets; a terminal renderer is
best effort.

Specs (docs/superpowers/specs):
- 0: standard mouse tracking and pointer capture in core-cpp 0.7.
- 1: morph::reactive (signals, Query, Mutation with concurrency modes,
  Subscription), morph::ui (view tree, backend contract, frontend seam)
  and the terminal renderer.
- 2: the C++ forms engine, interpreted from document nodes, with body
  parity measured against the QML renderer's output.
- 3: morph::qt_quick, the QML generator and renderer, native and
  WebAssembly.
- 4: every example's screens defined on its server, rendered by one
  generic client, migrated over several pull requests.
- 5: the UI document: structure and scopes, the expr/1 expression
  language, several models per screen or dialog, relations between
  elements, server-calculated values as the primary interaction, a
  side-effect-free evaluate envelope, offline queuing, commands, custom
  components, typed C++ authoring, handshake and versioning, permission
  filtering and per-user customization points.

The master plan, interface contract and eleven part plans were written
against the earlier toolkit-free-app design; they are adapted to these
specs once the specs are agreed.

Co-authored-by: Christian Parpart <christian@parpart.family>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A5KoSmE3aq36cDMxtQk8Xu
@Yaraslaut
Yaraslaut force-pushed the docs/declarative-ui-plans branch from 3f0ee98 to acdf1ea Compare October 7, 2026 19:59
@Yaraslaut

Copy link
Copy Markdown
Member

Direction change, and how the specs relate to open issues

The branch is now one commit. The specs changed direction. Screens are UI documents emitted by the server at runtime, in JSON, and one generic client renders them. The client evaluates every expression in C++ and generates QML from the document. Custom QML components are delivered in a verified bundle and may carry their own logic, such as charts. Qt Quick for desktop and web is the primary target, and the terminal renderer is best effort. The new spec is docs/superpowers/specs/2026-10-07-ui-document-design.md (spec 5). Specs 1–4 were revised to match.

The implementation plans have not been updated yet. They still describe the per-app, toolkit-free-binary design, and they will be adapted once the specs are agreed. That also affects feature/declarative-ui. Its Parts 0, 1 and 3 still fit the new specs, apart from the review fixes now listed in spec 1. Part 2's view tree changes: bindings become reactive values with stable ids built from a document, not C++ lambdas. Part 4 is replaced by the QML generator in spec 3.

Open issues these specs build on or answer

Decided in these specs

  • Live server recalculation is the primary interaction (spec 5 §7).
    • A row edit is sent to a declared action, and the reply updates the calculated cells.
    • The latest reply wins per row, and calculated cells stay visible, marked stale, while a reply is pending.
    • A reply never overwrites an input the user changed after the request was sent.
    • There is also a write-through variant that saves each edit in order per row and replaces the row in the list.
  • evaluate envelope. It returns an action body with its computedFields filled and its field errors, without executing the action. It is authorised as the action itself.
  • Several models in one screen or dialog, each under an alias. A sequence across models is not atomic, and composite actions spanning several models in one transaction are out of scope.
  • Relations between elements. Checks, watches with user-only cascades, dependent options and derived values are all declared in the document.
  • Offline queuing of mutations is part of ui/1.
  • Per-user customization is limited to points the document declares: columns, settings, layout, saved filters. These choices are stored per principal on the server. Documents themselves are the same for every user. Free layout editing is out of scope.

Not yet filed

morph::net::SocketBackend rejects a private bind made before its socket connects; it settles with DisconnectedError, at include/morph/net/socket_backend.hpp:626. QtWebSocketBackend queues such binds, as docs/spec/core/backend.md documents. A client that builds its handlers before the connection is up therefore fails every call over the raw-socket transport. Spec 4 §3 requires the queue.

  • Verification: inferred from reading fileControl and Bridge::registerHandler. Not reproduced.

Spec 6 (hosting an application) defines the screen lifecycle, the host
contract, the app root scope and session, the design package, the
workspace shell, application-scope preferences, offline as a backend
switch, and the host vocabulary, native panes, call and return, keyboard,
files, long jobs, settings and message catalogues an application needs.
Its reference screen is a sample-entry screen of the lims example.

Spec 7 (the table engine) defines a table declared from a model's list
action: columns from the row type, exact sort keys, normative filter
semantics, off-owner execution with a no-thread fallback, client, server
and hybrid modes with server-side limits, selection and cell editing.

The review pass corrects the earlier specs against the code: model-free
dispatch needs new bridge seams, the UI handshake moves to its own
authorized envelope kinds instead of `hello`, `refreshOn` is defined over
the interpreter's own dispatch, instances are explicitly private or
shared, and the cross-spec inconsistencies the review found are fixed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Yaraslaut

Copy link
Copy Markdown
Member

Superseded. The design specs merged in #884, and delivery is now tracked in #885, one PR per part (#886–#901), which replaces this PR's single-branch, 18-commit model. The plans here predate specs 5–7. #885 keeps them as a starting point only and references them by commit, so the docs/declarative-ui-plans branch (@ 29ca9b1f) stays. The "Not yet filed" finding above is now #902.

@Yaraslaut Yaraslaut closed this Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants