Conversation
glaze emits std::optional<E> for a glz::enumerate'd E as
{"anyOf": [{"$ref": "#/$defs/E"}, {"type": "null"}]}, and $defs.E is the
oneOf of const alternatives. enumChoices() gave up on the first non-null
branch without a const, so every optional enum rendered as a free-text
field.
A non-null branch that, after resolveRef, is itself an all-const
oneOf/anyOf now contributes its alternatives. Exactly one level; a set
nested deeper still falls back. The field's kind becomes "enum", the value
encodes as the JSON string, blank is omitted.
tst_DynamicFormEnumChoice pins glaze's generated $ref shape verbatim, the
issue's inlined nested shape, and the depth bound.
Closes #839
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every rung that journals from inside execute() commits and then keeps working, and a throw from that post-commit work told the caller a durable write had failed. kanban hand-rolled the containment; no other rung had it. morph::model::runPostCommitTail (include/morph/core/model.hpp) is kanban's helper promoted as-is, with the value-returning overload's result type made a template parameter deduced from `committed`: run the tail, log a failure through morph::log::logError, never rethrow. The void overload is noexcept and rejects a value-returning tail at compile time. - kanban: local copy deleted; the ten call sites use the framework helper. - ledger: every logAction after a durable write (StoreTransaction and its rule cascade, SetCategory, CreateLedger, OpenAccount, UndoTransaction, ImportLedgerChunk, RunReportJob) now runs through it. - tests/test_post_commit_tail.cpp: framework-level coverage, including a model-shaped handler journalling to a refusing sink. - examples/ledger/tests/test_ledger_post_commit_tail.cpp: ledger shown using it; fails on master's unshielded code. - docs/spec/core/post_commit_tail.md: new spec. Closes #789 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #839
Closes #789
Two tickets, one commit each.
#839:
DynamicFormreads an optional enum's closed set one level downChanged.
src/qt/forms/qml/DynamicForm.qml:enumChoices()now delegates to a newconstBranchRows(p, nested). If a non-null branch, afterresolveRef, has noconstbut is itself an all-constoneOf/anyOf, its alternatives count as the property's set. This is read exactly one level deep, and anything deeper still falls back. This is glaze's own output forstd::optional<E>:{"anyOf": [{"$ref": "#/$defs/Grade"}, {"type": "null"}]}.docs/spec/forms/forms.md("Closed sets") now describes the optional shape and the one-level bound.Pinned in
tst_DynamicFormEnumChoice.qml:gradeoptional via$ref,reqrequired via bare$ref)The assertions check
kind == "enum"(not only that choices exist), that a ComboBox is drawn, that choosing "High" puts"grade":"High"on the wire, that a blank optional field is omitted whilereadystays true, and that"Medium"leaves the form not ready.Verified (measured, macOS, Qt 6.11.1):
ctest -j 4 -R forms_qml_logic→100% tests passed out of 1. The enum file on its own:Totals: 25 passed, 0 failed.constBranchRows(p, false), i.e. no flattening. Result:Totals: 20 passed, 5 failed. All five new optional/inlined cases fail, e.g.test_glazes_optional_enum_is_a_closed_set_of_kind_enum() ... Actual (): false Expected (): true.Totals: 24 passed, 1 failed(test_a_set_nested_two_levels_down_is_not_a_closed_set).Not verified: I did not regenerate the glaze schema myself. The test uses the triage comment's literal JSON, which was produced against the pinned glaze.
#789:
morph::model::runPostCommitTailThis follows the 2026-09-26 design decision on the issue: promote kanban's helper and do not add a dispatch-layer seam.
Changed.
include/morph/core/model.hpp: two overloads inmorph::model(non-detail), taken from kanban's helper:void runPostCommitTail(Tail&&, std::string_view what) noexcept. It requires a void-returning tail.[[nodiscard]] Result runPostCommitTail(Tail&&, Result committed, std::string_view what).Resultis deduced fromcommitted, the tail's result only has to convert to it, and the overload isnoexceptwhenResultis nothrow-move-constructible.std::exceptionand..., log throughmorph::log::logError(thenoexceptformatting overload), and never rethrow.model.hppand not a new header: a new public header would have to be added to theFILE_SET HEADERSlist in the top-levelCMakeLists.txt, which is outside this lane.::morph::model::runPostCommitTail. The labels now carry the model tag ("[kanban::BoardModel] CreateColumn"), so the log line is unchanged.logActionthat runs after a durable write now goes through the helper. That covers the two named sites (StoreTransaction together with its rule-cascade entries in one tail, and SetCategory) plus five more with the same shape after autocommit writes: CreateLedger, OpenAccount, UndoTransaction, ImportLedgerChunk and RunReportJob. Ledger catches onlyLedgerError, so a sink throw at any of these sites used to reach the caller after the write had committed.tests/test_post_commit_tail.cpp(10 cases): both overloads, std and non-std exceptions, the exact log text, conversion,noexcept, and a model-shaped handler that journals to a refusingIActionLog. It also checks that a pre-commit throw still reaches the caller.examples/ledger/tests/test_ledger_post_commit_tail.cpp: OpenAccount ×2 and StoreTransaction against a refusing sink return normally, the sink was called 3 times, and a freshGetLedgershows the balances.test_board_post_commit_tail.cppstays as it is, as the rung's integration proof.docs/spec/core/post_commit_tail.md, linked fromdocs/spec/README.md.Verified (measured):
./tests/morph_tests "[post_commit_tail]"→All tests passed (36 assertions in 10 test cases).throw;after the std::exception log line and dropnoexcept. The build fails on the twoSTATIC_REQUIRE(noexcept(...)). With those disabled, the run givestest cases: 10 | 6 passed | 4 failed: every std::exception containment case fails. The non-std case was not mutated.ladder_ledger_tests "[post_commit_tail]"→All tests passed (10 assertions in 1 test case). Against master'sledger_model.cpp:FAILED: due to unexpected exception with message: RefusingActionLog: append refused.ctest -j 4 -R "^(kanban|ledger)\."→100% tests passed out of 344.ctest -j 4 -E "^(kanban|ledger)\.": all tests that were built passed. The 14 failures are all targets this lane did not build (Not Run/*_NOT_BUILT, orforms_html_mathhitting ENOENT onmorph_forms_demo).doctarget, configured with the flags from AGENTS.md, built with no warnings.Not verified:
The design comment's revisit condition: it asked to revisit if a rung needs post-commit work in a different execution context. Neither kanban nor ledger does: every tail runs synchronously before
execute()returns. The spec's "Why a utility" section records that limit.Review notes (inline)
model.hppnow includeslogger.hpp. That adds<format>/<print>/<mutex>to every TU that includesmodel.hpp. It is small, and logger was likely already reachable transitively, but I did not measure the compile-time cost.committedby value, so kanban'sMoveTaskPositioncopies oneGetBoardResult, exactly as the old local helper did.ActionRecordingError(which covers the framework's own auto-append) and does not duplicate it. The spec states this.constBranchRowsaccepts a mix ofconstbranches and one-level nested sets as the union of their values. No generator produces that mix. I chose not to reject it, because every value in the union is still a declaredconst.🤖 Generated with Claude Code