kanban(gui): the board was 3px wide and its right-hand columns unreachable — measured, fixed, and the fix mutation-tested - #794
Merged
Conversation
…hable At the shipped 1280x860 window the board area measured 3 pixels and the Activity panel 1237. Three things compounded: The Activity panel is a ColumnLayout inside a RowLayout, and a layout nested in a layout fills width by default -- `Layout.preferredWidth: 280` does not stop it. Its implicit width is the widest unwrapped activity summary, so it took the whole row. It is now `fillWidth: false` with a 200..320 band, which puts it at 280 and leaves the board 960. `boardRoot` is a bare Item, implicit width 0, with `fillWidth: true` and no floor, so there was nothing to stop that. It now carries a `Layout.minimumWidth` of one column plus the gaps either side -- below that the first column is cut in half and no window size rescues it. The columns were laid out in a bare Row inside an Item with `clip: true` and no scroll container anywhere in the file, so a strip wider than the viewport was not merely cut off but unreachable: a four-column board already needs 984 against the 960 it gets. The strip now lives in a Flickable with an always-on horizontal ScrollBar, sized from the column count rather than from the Row's implicit width, since a Row is a positioner and stretching it moves nothing inside it. The two numbers the strip is built from -- 240 wide, 8 apart -- are now named once on `boardRoot` and read by the delegate, the Row's spacing, the floor and the content width. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW
A layout that "looks right" is the weakest evidence there is, and an offscreen run cannot look at anything -- so this measures instead. It loads Main.qml with both bridges attached, so the widths under test are the ones the real shell produces (the 1280x860 window, its margins, the StackView, BoardView's own margins), and reaches the board by evaluating `stack.push(boardPage)` in Main's own context -- verbatim the statement its `onProjectOpened` handler runs, so no input event is synthesized. Five columns are created through the bridge's typed entry point, which is 1232px of strip against a 960px board area. Asserted: the Activity panel stays within a sidebar's width; the board area holds at least one whole column; the strip is genuinely wider than the area (otherwise the reachability check would prove nothing); a horizontal scrollbar is on screen; and the last column, which starts outside the viewport, is wholly inside it once scrolled to the end. The floor is then checked again at a 500px window, the only size at which it is what decides the split rather than the sidebar's own bound. Each of the three fixes was removed in turn against this test: dropping the Flickable leaves it unfound; dropping the floor puts the board at 180 of the 256 it needs at 500px; dropping the sidebar's bound puts Activity at 648. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW
The layout test landed without the clang-tidy gate having been run over it, so
three findings on changed lines reached CI:
readability-identifier-length `id` is shorter than the three-character floor
misc-no-recursion `findItem` calls itself
(precedence) `a + 2 * b` wants the multiplication bracketed
The first and third are renames and a pair of brackets. The second is not: the
function walks a QQuickItem tree of unbounded depth, and an iterative version
would hand-roll the same stack with none of the clarity, so it carries a
suppression that says why rather than a rewrite that pretends the shape is
different.
No assertion, bound or measured value changes. The test still fails against
each of the three mutations it was written to catch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW
… builds its bounds from
Three further `readability-math-missing-parentheses` findings on changed lines,
in the two expressions that compute the strip extent and the minimum usable
board width. The earlier pass fixed one occurrence and did not look for the
rest, which is why this is a second commit rather than an amendment.
(kColumnCount * kColumnWidth) + ((kColumnCount - 1) * kColumnSpacing)
kColumnWidth + (2 * kColumnSpacing)
Brackets only -- the operator precedence C++ already applies is what is now
written down, so every bound the test asserts keeps its value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW
Yaraslaut
force-pushed
the
fix/kanban-board-unreachable
branch
from
September 24, 2026 00:42
7771063 to
57a5c3f
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
The repository owner reported that the kanban board is not visible and that
Activity overlaps it. It reproduces, and it is worse than "too narrow": at the
shipped 1280×860 window the board area measured 3 px and the Activity panel
1237 px.
examples/is a detector, not a product, so this is fixed in place and nothingis filed. The cause is entirely the example's own QML — no morph forms/qt layer
behaviour and no framework sizing hint is involved, so there is no framework gap
to file against
include/morpheither.The three causes, each confirmed
grep -nE "ScrollView|Flickable|contentWidth|ScrollBar" BoardView.qmlreturned nothing; the test'sfindItem(boardArea, "boardFlickable")wasnullptr.clip: true, with nothing to reach themFlickablewith an always-on horizontalScrollBarboardRoot.grep -nE "Layout\.(minimumWidth|maximumWidth)"returned nothing across every kanban QML file.Layout.minimumWidth= one column + gaps (256)ColumnLayoutnested in aRowLayoutfills width by default —Layout.preferredWidth: 280does not stop it — and its implicit width is the widest unwrapped activity summary.fillWidth: false, 200…320 band → 280Measured widths at the default window size, from the test's own
INFO:960 is four columns short of five (1232) — which is why reachability, not just
width, is the property that matters.
The test, and the mutations that turn it red
examples/kanban/tests/test_board_layout.cppruns offscreen(
QT_QPA_PLATFORM=offscreen). It loads Main.qml with both bridges attachedrather than
BoardViewas a bare root, so the widths it measures are the onesthe real shell produces, and reaches the board by evaluating
stack.push(boardPage)in Main's own context — verbatim the statementonProjectOpenedruns, so no input event is synthesized (presenter rule 6). Fivecolumns are created through
BoardBridge::createColumn.It asserts the sidebar stays a sidebar; the board holds at least one whole
column; the strip is genuinely wider than the area it is drawn in (so the
reachability check cannot pass vacuously); the scrollbar is on screen; and that
the last column — which starts outside the viewport — is wholly inside it after
scrolling to the end. The floor is then re-checked at a 500 px window, the only
size at which it, and not the sidebar's bound, is what decides the split.
Each of the three fixes was removed in turn and the test re-run:
Other views
Checked
ProjectListView.qml,MembersView.qml,RulesView.qml,TaskDetailPopup.qml,LoginView.qml. None has this defect, and none ischanged here.
ProjectListView.qml's two-pane row has the same declaration shape — pane 1is a nested
ColumnLayoutwithLayout.preferredWidth: 420and nofillWidth: false, so both panes fill and split the row roughly evenly insteadof honouring the 420. It is not the same defect: neither pane holds
fixed-width content, nothing is clipped, and nothing becomes unreachable.
Pinning pane 1 to 420 would also leave dead space whenever the members pane is
hidden (it is
visible: falseuntil a project is selected), so it is leftalone deliberately rather than by omission. Everything else on these screens is
elastic — labels elide, list delegates take their view's width, forms wrap — and
the vertical
ListViews are already flickable.Gates
clang-format --dry-run -Werror(22.1.8) over the one changed C++ file — clean.ctest -N -L ladder-kanbanselects 182 tests (181 before, +1 here), so thefilter is not matching nothing.
ctest -L ladder-kanban -j6→ 100% tests passed out of 182, including bothpre-existing QML engine-load smoke cases (no new QML warnings — the suite fails
on any).
ladder_kanban_qml_qmllint: 19 warnings before, 19 after, none of them inthe changed block.
Build: Clang 22.1.8, Release,
MORPH_BUILD_LADDER=ON MORPH_LADDER_RUNGS=kanban MORPH_BUILD_QT=ON MORPH_BUILD_FORMS_QML=ON.🤖 Generated with Claude Code
https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW