Repository navigation
Four remaining declarative-UI issues: Ghost emphasis, overlay width, shell head, dateTimeInput mode - #1038
Conversation
06068e6 to
a8ad908
Compare
dateTimeInput() was a positional function with no way to set mode, even though the document schema and interpreter already fully support it. Add DateTimeInputOptions, matching TextInputOptions/ FilePickerOptions's own pattern, alongside the existing positional overload. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A ghost button has no border or fill, minimal (space.1) padding, and a faint neutral tint on hover/press, for an action embedded in other content (a table row's remove, a disclosure chevron) where full button chrome reads as a second control beside it rather than part of it. Touches the enum, both document-schema emphasis arrays, both interpreter string-to-enum chains, both author.hpp enum-to-string chains, the Qt Quick enum-to-QML-int mapping (the one site that actually renders the feature), MorphButton.qml/ MorphButtonBackground.qml (including the implicitWidth floor, which would otherwise keep every ghost button at the same width as a filled one), and the TUI fallback-to-plain-style path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ar inference (#1026) MorphDrawer.qml and MorphDialog.qml both infer their width from their own content's implicit size, which reports back roughly the width the content was just given rather than what it actually needs -- a drawer settles at its 30-unit floor, a dialog settles wherever the first measurement pass happened to land. A document's own `sizing` now wins outright when given: Fixed on the drawer binds Drawer.width directly; on the dialog, both the Popup's own width and contentColumn's tracking of popup.availableWidth must change together, since contentItem's width is only re-asserted by Qt's own resizeContent() from specific trigger points, not from a plain change of its implicit size. Both overlays' root is only a zero-sized placeholder for the real on-screen overlay; without an explicit Layout.preferredWidth/Height override, the inherited MorphWidget binding would make that invisible placeholder claim real space in whatever layout holds the node. Stretch sizing is refused on both node kinds -- not silently ignored -- since a stretching overlay placeholder would change how every sibling in its own parent layout is justified. Covers the document's own literal check and, in one place, both the document's computed-expression path and a C++ author calling ui::dialog()/ ui::drawer() directly, since both converge at the same mountKind overload before either backend sees the result. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e body (#1027) A tab's title + primary-actions head sits above its screen today, but that row is inside the page the workspace's tabs sit on, which cannot reach the window's own top bar -- the issue's repro. ShellFrame gains a dedicated _headSlot beside the title, built in the constructor like every other top-bar child, with its own independent stretch so it keeps claiming width even while the mounted row is invisible (a screen with no head). Workspace::headRowForActive() exposes the same reactive head Node headRow() already builds for the in-body rendering, read directly off the model's own active tab id -- splitting headRow()'s construction into a Row-returning helper both methods share, since a Node, once built, cannot be mutated further. shell.cpp mounts it, re-mounting only on a genuine active-tab change (compared by tab id, not Workspace::active()'s own index, which is a bar position and not stable across a close) -- the mounted Node is itself reactive, so in-tab status/action changes update without a re-mount. The mount and its comparison key reset together, ahead of _workspace, on every path that tears the workspace down directly rather than through its own model callback: the destructor and buildFrame()'s own rebuild (where a fresh Workspace instance restarts its tab-id numbering from 1, which would otherwise collide with the pre-rebuild key and silently drop the head on the very screen a sign-in just opened). Trailing-edge action alignment inside the row is deliberately not attempted here: pushing the row's own spacer to stretch would need a change to ui::spacer() that also affects the existing in-body rendering, out of scope for a change that only adds a new mount point. Part (b) of the issue -- an optional second builder for arbitrary document-authored chrome content -- is explicitly out of scope; this covers only the ScreenHead-shaped repro the issue actually reports. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
103eb96 to
8fd3e4e
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
CI status: every check is green except Linux / gcc-release, which fails on a pre-existing Also note: this branch originally conflicted with #1037 in |
…d() test GCC 15.2 at -O3 loses the variant's index once a list's make_shared is inlined into the test, and reports engaged()'s string branch as reading an uninitialized length on a value that holds a list. Neither naming the values nor rewriting engaged() as a switch on kind() changes that, so the suppression is scoped to the one test case. This fails the gcc-release leg on master too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BmVLNhUvbWZjv85Hx2saxn
|
The gcc-release failure is now fixed in this PR (b7890fc), so the earlier comment about it is out of date. The Reproduced locally with GCC 15.2 at 🤖 Generated with Claude Code |
Summary
Closes #1026, closes #1028. Partially addresses #1024 and #1027 (both stay open — see below).
Four changes from the #1035 batch, each independently reviewable:
dateTimeInput()gains aDateTimeInputOptionsoverload, exposingmode(already fully supported by the document schema and interpreter, just never surfaced by the positional author wrapper). The issue's core ask — a calendar/date-picker in the Qt Quick backend — is not implemented here;MorphDateTimeInput.qmlis unchanged.ButtonEmphasis::Ghost: no border/fill, minimal padding, a faint neutral tint on hover/press — for an action embedded in other content (a table row's remove, a disclosure chevron) where full button chrome reads as a second control. Touches the enum, both document-schema emphasis arrays, both interpreter mapping chains, both author.hpp serializers, the Qt Quickleaves.cppenum-to-int mapping (the one site that actually renders it),MorphButton.qml/MorphButtonBackground.qml(including theimplicitWidthfloor, which would otherwise keep a ghost button at the same width as a filled one), and the TUI fallback-to-plain-style path.sizinglever, breaking their circular width inference (a drawer always settles at its 30-unit floor; a dialog settles wherever the first measurement pass happened to land). The dialog fix needs two coordinated changes: thePopup's ownwidthandcontentColumn's tracking ofpopup.availableWidth, sincecontentItem's width is only re-asserted by Qt'sresizeContent()from specific trigger points, not from a plain implicit-size change.Stretchsizing is refused on both node kinds at the two points that actually matter (a literal document value, and the sharedmountKindconvergence point for a computed expression or a direct C++ui::dialog()/ui::drawer()call) — a stretching overlay placeholder would silently change how every sibling in its own parent layout is justified.ShellFrame's own top bar, not just inside the page body. A new_headSlotsits beside the title with its own independent stretch (so it keeps claiming width even while the mounted row is invisible).Workspace::headRowForActive()exposes the same reactive rowheadRow()already builds for the in-body rendering —headRow's construction is split into aRow-returning helper both methods share, since aNode, once built, cannot be mutated further.shell.cppre-mounts only on a genuine active-tab change, compared by the model's own tab id (notWorkspace::active()'s index, which is a bar position, not an identity, and unsafe to compare across a close) — and resets the mount together with its comparison key ahead of_workspace, on every path that tears the workspace down directly rather than through its own model callback (the destructor, andbuildFrame()'s rebuild — where a freshWorkspaceinstance restarts its own tab-id numbering from 1, which would otherwise collide with the pre-rebuild key and silently drop the head on the very screen a sign-in just opened).Part (b) of #1027 (an optional second builder for arbitrary document-authored chrome content) is explicitly out of scope — this covers only the
ScreenHead-shaped repro the issue reports. Trailing-edge action alignment inside the mounted head row is also deliberately not attempted: it would need a change toui::spacer()that also affects the existing in-body rendering.Design process
This batch went through six rounds of adversarial review (a second model instance independently re-verifying every claim against the live source — including executing
qmllintagainst the proposed QML and tracing Qt's own C++ internals for the overlay-width circularity) before implementation started. The design document is atdocs/superpowers/specs/2026-10-22-remaining-ui-issues-design.md(not part of this PR's diff — tracked separately).Test plan
morph_tests(core, 3133/3134 — the one "failure" isdefault-sink-coverage, an intentional negative-path assertion)morph_tui_tests(TUI, 338/339 — the one failure,test_tui_fields.cpp's year-10000 date formatting, pre-dates this branch and is unrelated to any file touched here)morph_qt_quick_tests(headless Qt Quick, 139/141 — the two failures,test_qt_quick_theme.cpp/test_qt_quick_containers.cppfont-family detection, are offscreen-platform font-availability issues pre-dating this branch)morph_client_tests(30/30 relevant — one failure intest_sign_in.cppis an unescaped-Windows-backslash-in-JSON bug in a test fixture, pre-dating this branch, unrelated to anything touched here)clang-formatclean on every changed fileWorkspace/ShellFramehead-row-in-two-places assertion.🤖 Generated with Claude Code