Repository navigation
Enhancement backlog: candidates waiting on a decision or a measurement #860
Description
Enhancement candidates that are real but not ready to implement, collected in one place so the next decision is easy to see. Each entry says what it would add, what is blocking it (a decision or an experiment, not engineering time), and what makes it ready. The full evidence stays in the source issue, which is linked and closed as superseded. Re-open a source issue, or file a fresh one, once its blocker is answered. That issue then goes through triage on its own.
Checked against master @ 85a5280e (2026-10-03), by reading. Nothing below was re-built or re-run for this collection unless it says so.
Needs a design decision
Per-model storage a model reads synchronously on its own strand (from #854, area: journal)
- Adds: one framework seam replacing four hand-rolled self-journals:
SelfJournalinexamples/limsandexamples/crm, pluslogActionwith a synchronous read inexamples/ledgerandexamples/kanban. It also stops kanban journalling each mutating action twice, which was observed once and inferred to still happen. - Blocked on: the seam as filed hands the model its own strand, which breaks "a model does not know the framework exists". The direction that fits is for the holder or registry to build the log with the model's strand as its owner and inject it, and for a holder that injected a log to skip its own auto-append. Open questions: is the injected type a
journal::type, and how does a model declare that it wants one without naming an executor? - Ready when: both questions are answered in
docs/spec/journal(or the spec for the holder).
Profiling: a Tracy sink for morph::observe, and a statistics collector (Part 2 of #843, area: core)
- Adds: each
Metricsent to a fixed-nameTracyPlot, and theTraceSinkmapped toTracyMessage. Optionally, a Lightweight-styleSnapshot()with latency histograms. - Blocked on: (a)
morph::observeholds one sink per kind, so a Tracy sink would evict a host's own sink. Either add a tee/fan-out helper, or document "profiling builds only". (b) A histogram collector ininclude/morphcontradictsobservability.md: "No sampling/aggregation/histograms inmorph". The spec owner has to choose between shipping it as an optional host-installed sink or keeping it intests/bench. - Ready when: (a) and (b) are decided in
docs/spec/core/observability.md. Part 1 of observability: Tracy zones behind MORPH_ENABLE_TRACY (prefixed MORPH_ZONE macros) and a capture job that proves they fire #843 (compile-time zones) does not depend on this and stays open as its own ticket.
Nested aggregates in DynamicForm (from #759, part (b), area: forms)
- Adds: a sub-form for nested object members, lazily for recursive ones. Today a nested member is flattened to one text field.
- Already done on master: the defect half. A member the form cannot represent is now marked
unrepresentableand keeps the form short ofready(src/qt/forms/qml/DynamicForm.qml:30;docs/spec/forms/forms.md, "Whatreadyclaims"). - Blocked on: choosing between drawing nested aggregates, declining the schema loudly, or keeping today's documented behaviour. No shipped action has a nested-aggregate member yet.
- Ready when: that choice is made, or a shipped action needs a nested member.
Models over existing (legacy) objects (from #838, area: core)
- Adds: a
MaybeOwned<T>-style holder so that morph can run actions against an object it does not own. - Blocked on: which reading is meant. Reading A, a model that wraps the object, already works with no framework change:
ModelHolderforwards constructor arguments, andregisterModel<Model>(id, factory)is the dependency-injection seam. Reading B, the legacy object is the model, needsModelHolderto stop holding the model by value, under the conditions in Models for the existing objects #838's triage comment (lifetime, one strand perModelId). - Ready when: someone names the concrete type that needs Reading B and why Reading A does not fit it.
Client-side model and action discovery: one generic client for every rung (from #829, area: core, area: qt) — moved to #885
area: core, area: qt)- Superseded (2026-10-08): the declarative UI specs (docs: declarative UI program — design specs 0 to 7 #884) replace the Generic client: design note and ticket split (answers #860's blocker for #829) #877 design note. Delivery is tracked in Declarative UI program: tracking issue (specs 0–7) #885 as 16 parts; core: Bridge::executeJson — string-keyed dispatch without a Model type #878–forms/views: measure how much of one rung's QML the declarative vocabulary already covers #880 are closed in favour of core: model-free dispatch seams — bindByType, executeRaw, string-keyed attach #886 (bridge seams) and net/ui: the UI service — ui-hello, ui-catalog, ui-fetch, ui-evaluate, ui-file and content-addressed bundles #894 (
ui-*envelopes, catalog filtered for the principal).
What still overlaps with core-cpp (from #807, area: core)
- Adds: fewer parallel implementations of things core-cpp also provides.
- Blocked on, per row: leaf executors need a closure
submitin core-cpp (async: let IExecutor run a posted callable, not only a coroutine handle contour-terminal/core-cpp#56).FileIoOpsneeds a file-handle abstraction with sync and truncate in core-cpp (platform: FileSystem has no durability primitives (file sync, directory sync, truncate) contour-terminal/core-cpp#57).morph::logis proposed to stay, pending a decision on mapping its severity threshold onto core-cpp's categories. The wall-clock row was largely handled by core, offline: stamp action-log and offline-queue times from an injected wall clock #842. - Ready when: the core-cpp issue for a row lands. File that row as its own ticket then.
Parked performance work (re-entry is a measurement)
InMemoryReplayLedger::doLookup materialises its key inside the lock (from #728, area: offline)
- Ready when: a profile shows
doLookupon a hot path, or a heterogeneous lookup becomes available that removes the materialisation for free.
ModelRegistryFactory::create builds its lookup key (from #709, area: core)
- Measured at one allocation per model instantiation.
- Ready when: model instantiation shows up in a profile, or
bench.alloc_budgetstarts counting it against a budget it exceeds.
How to use this issue: when a blocker above is answered, comment here, re-open the source issue (or file a fresh one) with the answer folded in, and tick the entry off. Do not implement directly from this list.
Activity
Yaraslaut commentedon Oct 3, 2026
Added: the four building blocks from #793 (
area: core,area: forms)#793 measured where the ladder rungs' application code goes. The figures below are code-only, after its own correction comment. None of the four blocks is ready to build: each waits on a measurement or a contract decision.
Required-ness in
allRequiredEngaged: the flat walk andstd::stringvalidate()that more actions get for free. 39 of the 153 hand-writtenvalidate()bodies are exact drop-ins for the helper today (measured in examples: where the 42k lines of application code go, and the four building blocks that would remove most of it #793's third comment). 26 more rest onstd::string::empty(). Two hand-write a delegation into a nestedoptional<Sub>.include/morph/forms/forms.hpp:146-164documents both the flat walk and thestd::stringexclusion as intended behaviour. Does an empty string count as "not filled in" for a required member? And should the walk descend intoSub,optional<Sub>andvector<Sub>?docs/spec/forms/forms.md.A generic QML bridge (the largest block)
*_qml_bridgefiles and 26 presenters, plussubmitIfValid(const QString&, const QString&)hand-written seven times.QVariantconversion, change signals). How much of the remaining 1,995 would survive generalization is unmeasured. Note that qt/forms: multi-model bridge routing core, retrofitting bookmarks' FormsBridge #835 already landed a multi-model bridge routing core.A shared error vocabulary
The handler opening (auth, validate, throw)
runPostCommitTail).#793's overall close condition still applies: a new rung of comparable scope written in materially fewer lines, measured the same way.
Yaraslaut commentedon Oct 4, 2026
Added: should a dropped connection stop its own running Task handlers? (
area: core)RemoteServer::closeConnectionis G0 for an execute in flight, as documented and measured in the Cancellation policy (docs/spec/concurrency_and_lifetimes.md,RemoteServer::closeConnectionrow). The running execute still answers into a connection that is gone, and only new lookups are refused. This is the specified behaviour, not a defect.closeConnection(cid), request stop on every cancellable Task run thatcidadmitted. Since core, wire, net, qt: a hello-negotiated cancel envelope, honoured by RemoteServer and sent by both client backends (#864, #865) #867 the server keeps exactly that per-connection table, the one acancelis looked up in, so the mechanism is a loop over existing state.backend.md"Connection scopes" decides it and writes the rule there. The row in the Cancellation policy then moves from G0.Yaraslaut commentedon Oct 7, 2026
The "generic client" entry (from #829) is answered and ticked off above. The design note is #877. Ready tickets: #878 (
Bridge::executeJson), #879 (thehellohandshake), #880 (measuring how much of one rung's QML the declarative vocabulary covers).Two things changed from #829's framing:
IBackend(Generic client: design note and ticket split (answers #860's blocker for #829) #877, F1, measured on944620c). What is missing is aBridge-level entry point that keeps session, timeout and cancellation (core: Bridge::executeJson — string-keyed dispatch without a Model type #878), and discovery.Still open, and blocking the
catalogticket: whether discovery is filtered by the caller's session.Generated by Claude Code
Yaraslaut commentedon Oct 15, 2026
Triage: parked
This issue is a collection of candidates, each with an explicit blocker and a "ready when". That is what
triage: parkedmeans. Nothing here is part of the declarative UI milestone (#885): its one declarative-UI entry was already moved to #885 on 2026-10-08. Re-checked 2026-10-15: no entry's re-entry condition has been met. Re-enter an entry when its "ready when" holds, per the instructions at the bottom of the issue.