Skip to content

Application ladder: rungs 0-3 (pastebin, bookmarks, polls) + shared infrastructure - #41

Open
Yaraslaut wants to merge 5 commits into
masterfrom
application-ladder
Open

Application ladder: rungs 0-3 (pastebin, bookmarks, polls) + shared infrastructure#41
Yaraslaut wants to merge 5 commits into
masterfrom
application-ladder

Conversation

@Yaraslaut

@Yaraslaut Yaraslaut commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

The application ladder — a planned sequence of stateful example apps of gradually increasing
complexity, each stressing a distinct set of morph subsystems. This PR now carries rung 0
(shared infrastructure) through rung 3 (polls)
, not just rung 0 as originally scoped:

  • Rung 0 — shared infrastructure: MORPH_BUILD_LADDER build wiring, morph_add_rung()
    per-rung scaffolding, the shared testkit (pump.hpp, db_fixture.hpp/db_fault_fixture.hpp,
    backend_rig.hpp, fault_proxy, strand_interleaver.hpp), and the shared presenter
    architecture (Presenter, AppContext, the WASM-remote spike proving QtWebSocketBackend
    works from Emscripten).
  • Rung 1 — pastebin: animal-name-keyed pastes with burn-after-read, expiry, and edit
    conflict detection; the first rung exercised end-to-end (server, desktop GUI, WASM client).
  • Rung 2 — bookmarks: multi-user bookmarks with tags, bulk edit, Netscape-format import,
    a cross-user shared feed, and session-based auth.
  • Rung 3 — polls: multi-participant polling with vote/comment history, undo, finalization,
    and a Zulip-pattern event log (GetEventsSince) — the first rung to exercise the framework's
    async keyed/shared-model attach path, which drove core framework additions (see below).

Framework changes surfaced by building the rungs (not rung-scoped, land in include/morph/core/):

  • A client-side execute deadline (Bridge::setExecuteDeadline).
  • An async register-or-attach/attach path for shared/keyed models, closing a staleness race and
    two exception-safety gaps in switchBackend (found while building polls' shared poll state).
  • A browser-timer build of TimeoutScheduler for single-threaded WASM.

Findings: docs/findings/001-033 — each a minimal failing test or a spec-cited
impossibility, opened and closed as the rungs' own construction surfaced or resolved them
(e.g. 017's async-registration-before-connect bug, 030's attachModel staleness, 031's
DynamicForm array-field gap, 033's poisoned-attach reproduction).

CI: a path-filtered ladder-tests job, coverage measurement extended to the ladder's
hand-written code, the GUI/WASM build+gate legs, and (this session) a full pass fixing every
standing CI failure on the branch — a bridge.hpp unused-capture warning, an MSVC /bigobj
gap, a morph_add_rung() check that unconditionally required a testkit target Emscripten
correctly never builds, and half a dozen Clang -Weverything diagnostics the WASM leg's
older bundled Clang surfaces that the Linux/Windows Clang never did — plus a design doc
(docs/superpowers/specs/2026-08-11-strong-storage-types-design.md) scoping a follow-up pass
(DataMapperPool adoption, GUID ids, Lightweight strong string/timestamp types) from PR review
comments, not yet implemented.

Testing

  • Every rung's model layer is 100% unit tested against a real on-disk SQLite database via the
    shared testkit fixtures — no mocked persistence anywhere.
  • Each rung's GUI has the one offscreen engine-load smoke test examples/TESTING.md requires,
    plus full presenter/bridge coverage in C++.
  • Verified locally this session: ladder_pastebin_tests, ladder_bookmarks_tests,
    ladder_polls_tests, ladder_common_tests all rebuild clean and pass against a real SQLite
    ODBC backend (one known pre-existing Windows temp-file-lock flake in bookmarks' test_app.cpp,
    unrelated to any change in this PR).
  • CI fixes in this PR are being verified against a live run; WASM ladder gate has never passed
    on this branch and is being brought up incrementally, commit by commit, as each new layer of
    breakage surfaces.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

@Yaraslaut Yaraslaut changed the title Document the application ladder: eight stress-test example apps Application ladder: design docs + rung 0 shared infrastructure Aug 6, 2026
Comment thread examples/pastebin/include/pastebin/db/db_model.hpp
Comment thread examples/pastebin/include/pastebin/db/paste_entity.hpp
Comment thread examples/pastebin/include/pastebin/db/paste_entity.hpp
Comment thread examples/pastebin/include/pastebin/db/paste_entity.hpp
Yaraslaut pushed a commit that referenced this pull request Aug 11, 2026
…r-wide

Scopes DataMapperPool adoption, pastebin's animal-name-id -> SqlGuid
conversion, plain std::string -> Lightweight strong string types, and
int64 epoch-ms -> SqlDateTime across bank/bookmarks/pastebin/polls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Yaraslaut Yaraslaut changed the title Application ladder: design docs + rung 0 shared infrastructure Application ladder: rungs 0-3 (pastebin, bookmarks, polls) + shared infrastructure Aug 11, 2026
Yaraslaut pushed a commit that referenced this pull request Aug 13, 2026
…r-wide

Scopes DataMapperPool adoption, pastebin's animal-name-id -> SqlGuid
conversion, plain std::string -> Lightweight strong string types, and
int64 epoch-ms -> SqlDateTime across bank/bookmarks/pastebin/polls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Yaraslau Tamashevich and others added 4 commits August 13, 2026 14:26
The application ladder -- a planned sequence of stateful example apps of
gradually increasing complexity, each stressing a distinct set of morph
subsystems (examples/LADDER.md). This commit carries rung 0: everything
the rungs themselves share, not any one rung's own model/DTO/GUI code.

- MORPH_BUILD_LADDER build wiring, morph_add_rung() per-rung scaffolding
  (examples/CMakeLists.txt, cmake/morph_add_rung.cmake).
- Shared testkit (examples/common/testkit/): pump.hpp, db_fixture.hpp/
  db_fault_fixture.hpp/db_busy_fixture.hpp, backend_rig.hpp, fault_proxy,
  strand_interleaver.hpp -- the fixtures every rung's own tests build on.
- Shared presenter architecture (examples/common/gui/): Presenter,
  AppContext, event_poller.hpp/.cpp, plus the WASM-remote spike
  (examples/common/wasm_spike/) proving QtWebSocketBackend works from
  Emscripten.
- Framework changes the rungs surfaced, landing in include/morph/core/
  rather than any rung's own tree: RemoteServer's same-model execute
  reordering, fixed with a per-model ticket gate
  (tests/test_remote_execute_ordering.cpp).
- CI: a path-filtered ladder-tests job, coverage measurement extended to
  the ladder's hand-written code, the WASM build+gate leg
  (.github/workflows/wasm-ladder.yml), and every standing CI fix found
  building the rungs (an MSVC /bigobj gap, a morph_add_rung() Emscripten
  check, several Clang -Weverything diagnostics the WASM leg's older
  bundled Clang surfaces that Linux/Windows Clang never did).
- docs/findings/001-036: each a minimal failing test or a spec-cited
  impossibility, opened and closed as the rungs' own construction
  surfaced or resolved them.
- Planning docs (docs/superpowers/plans/) and a design doc scoping a
  follow-up storage-types pass from PR review comments, not yet
  implemented.
- README-only stubs for five not-yet-built future rungs (crm, forge,
  kanban, ledger, lims).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Animal-name-keyed pastes with burn-after-read, expiry, and edit conflict
detection -- the first rung exercised end-to-end (server, desktop GUI,
WASM client).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Multi-user bookmarks with tags, bulk edit, Netscape-format import, a
cross-user shared feed, and session-based auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Multi-participant polling with vote/comment history, undo, finalization,
and a Zulip-pattern event log (GetEventsSince) -- the first rung to
exercise the framework's async keyed/shared-model attach path, which
drove the core framework additions in the shared-infrastructure commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ts, remove settled findings

Goes through every finding filed against rung 0-3 (36 files total) and
either migrates the ladder's own code onto the public seam that closes
it, corrects every stale in-code comment/README passage that still
described a closed gap as open, or removes the finding file once its
content is durably captured elsewhere (a spec section, a GitHub issue,
or the fix's own code comments) -- never leaving a bare historical
record behind.

Code migrations (findings 019, 024):
- Testkit reach-ins onto public seams: Completion<T>::makeSettleable()/
  Promise replaces direct CompletionState<T> construction
  (test_pump.cpp); BridgeHandler::isBound()/whenBound() replaces manual
  HandlerBinding construction + registerHandler + currentId polling
  (test_wasm_registration_path_native.cpp, wasm_spike/main_wasm.cpp);
  QtWebSocketBackend(url, tls, cfg) replaces the
  defaultDispatcher()/defaultRegistry() padding five call sites no
  longer need (app_context.cpp, test_fault_proxy.cpp, and the above).
  StrandExecutor/ModelId reach-in (strand_interleaver.hpp) is kept
  on purpose, documented as testkit-layer-by-design: those tests prove
  StrandExecutor's own ordering guarantee and a stand-in would prove
  nothing.
- Presenter::bound()/trackBound() (examples/common/gui/presenter.hpp),
  backed by Bridge::whenBound(), gates every rung's bootstrap dispatch
  (PastePresenter/BookmarkPresenter/TagPresenter/SharedFeedPresenter and
  their QML bridges/Main.qml/BookmarkListView.qml) instead of a 150ms
  polling Timer. Guarded with QPointer<Presenter>, not a bare `this`
  capture -- whenBound()'s Completion resolves through the executor
  asynchronously even in Local mode, so a short-lived presenter torn
  down before that post runs would otherwise be dereferenced after
  destruction (caught via a genuine SIGSEGV repro under cdb during this
  work, in a presenter destroyed between test cases). PollPresenter is
  untouched: nothing in polls' QML dispatches on Component.onCompleted,
  so that rung never hits the window this closes.
- Along the way, verified and fixed bookmarks' own tags field
  (CreateBookmark::tags/EditBookmark::tags, a JSON array of strings) now
  renders and submits correctly through DynamicForm's array-field
  control -- the README's "tagging is not reachable from the GUI at
  all" claim was stale; neither Main.qml nor BookmarkListView.qml
  special-cases the field.

Findings removed after independent re-verification against current
source (not their own disposition: labels, which are known to drift):
003, 005-009, 011-014, 019-021, 023-026, 028-034 (framework fixes
confirmed landed, each with its own commit/regression test), 001, 002,
004 (rung-0 framework prerequisites, all confirmed shipped), 017 (its
queueing behavior was already fully documented in
docs/spec/core/backend.md -- the finding file was pure redundant
history), 018 (folded directly into examples/TESTING.md's testkit
section and examples/IMPLEMENTATION.md rule 5, since it's a genuine,
still-current limitation that belongs in the governing docs, not a
separate finding file), 035 (RemoteServer execute-ordering gate; its
full design history, including the reverted first attempt, is now
condensed into remote.hpp's own comment rather than a separate file).

Two genuine, still-open technical gaps were filed as GitHub issues
instead of finding files, since they're real work items someone should
eventually pick up:
- 022 (sqliteodbc's UPDATE...RETURNING reports success but SQLFetch
  throws SQLSTATE 24000) -- a third-party driver bug, filed as
  LASTRADA-Software/Lightweight#545 with the full reproducer;
  cross-linked with morph's own tracking issue (#58, already existed).
- 036 (BookmarkModel::execute(GetChangesSince)'s millisecond-resolution
  cursor can miss a same-millisecond write) -- already tracked as
  morph#43; added the GetEventsSince id-cursor precedent and remaining
  design-work detail from the finding file as a follow-up comment
  before removing it.

Left untouched, correctly: 010 (forms sum-type gap), 015
(reconcileDeclaredPrecision spec/code agreement, already verified
matching), 016 (FileOfflineQueue linear-scan dedup) -- all three
`documented-limitation`, independently re-checked against
docs/spec/forms/forms.md and docs/spec/offline/offline.md, both specs
already stating the identical content verbatim. And 022/036's
replacement issues are new work items, not closed findings -- left
open on GitHub for whoever picks them up next.

Verified throughout: full core suite (morph_tests: 9774 assertions,
morph_qt_tests: 496 assertions) plus every ladder rung
(ladder_common/pastebin/bookmarks/polls_tests: 2423 assertions total),
all green. The only non-comment production-code changes are the
Presenter::bound()/whenBound() migration and its QPointer lifetime fix;
everything else is documentation, test-comment, or finding-file churn.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant