Fold a board's last column into a deck #245
No reviewers
Labels
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente!245
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "ui-v2-deck"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The v2 language's Done deck, signed off: a board's last column folds into a deck.
What changed
BoardPage.ResolveDropIndexanswers 0 before the filter's mapping). That holds for a deck of one, so a card dropped under a lone card stays in sight once the two fold. While a card is over the deck, its ghost is drawn first, where it will land. Only the top card can be dragged out, being the only one rendered.::before/::after(flex items ordered after the card and pulled up under it), so SortableJS sees no extra children and they follow the card's height. Tokens only (--bg-1,--shadow-sm,--radius-sm,--brand-ink,--fill-1). Laid out, the cards beneath come out from under the top one on a@starting-style, with no motion underprefers-reduced-motion.docs/ui.mdhas a new The deck section,docs/frontend.mdthe drag-and-drop note and the v2 status line.Not done: the optional "Deck" chip in Settings › Columns. It would add a word members never see on the board, plus a new chip style, so it did not meet "only if trivial".
How it was checked
tools/verify.sh: build clean, 584/584 unit tests (no new xunit: the drop rule is a one-line answer that e2e reaches).board.spec.js(fold, count, Show all and Show less, no Add card while folded, a drop well below the deck landing on top, a filter on the deck, not remembered across a reload; on a board of its own) andmobile.spec.js(the deck on a 375px phone's page, footnote centred under the card and clear of the edges, thumb-sized toggle, tap to lay out).search.spec.js(a dialog opened from a search hit was still open after Escape when the test narrowed to 960px, on a card in the deployment board's first column, so not the deck); it passed 3/3 on rerun.Also fixed here: a board that would not drag (
ea9a359). It is older than the deck and was found by the deck's drop test failing about one fresh load in six. The spinner's render also reachesBoardPage's interop once a load has set_columnsbut before the columns render.sync()then bound nothing, but the column signature was remembered anyway, so the render that did show the columns skipped wiring them, and nothing dragged until a reload.sync()now answers whether every column was bound, and the signature is kept only then. With the fix, the deck test passed 25 of 25 fresh runs, and the full suite passed 161/161.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
The change looks good to merge. I read the diff only and did not run the build or the tests.
ResolveDropIndexshort-circuits to 0 before the filter mapping, as documented. The pager tile goes through the same path.board.deck.moreand.one, the changelog line is in both locales, anddocs/ui.mdanddocs/frontend.mdare updated.::before/::afterkeeps SortableJS from seeing extra children. Tokens are used throughout.Minor, not blocking:
IsFoldedDeckinBoardPageis true for a deck of one, whileColumnView.IsFoldedis false there. The docs say this is deliberate, andResolveDropIndexreturns 0 in that case. It is easy to misread later, so a one-line comment onIsFoldedDeckwould help.🧠 claude-sonnet-5-5 · ⏱ 0m 11s · 2 turns · tokens: 23.0k in / 1.1k out (+12.2k cache-read)
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
The fold logic is correct and well-factored.
CanFold/IsFoldedinColumnViewderive purely from the filtered view, so filter and deck compose without either knowing the other, and the fold decision is applied uniformly (drop indexing viaResolveDropIndex's early return, Add-card hiding, and the auto-spread on save). The SaveCard spread flag is only committed after the save succeeds, so failed creates don't open the deck. The intra-list::before/::after edges cleverly avoid giving SortableJS extra droppables, and the injectedcolumn-deck-morebutton is exempt from dragging by the existingdraggable: '.card'selector, withnewIndexcounting cards only — so the "every drop on top" rule holds for either drop position.UGINS: no correctness issues found. Two minor notes, both non-blocking:board.deck.more.oneis a duplicate of the plural form in both locales; harmless sinceTCountrequires thekey.oneslot and the folded count is always ≥ 1, but if the deck ever shows a different singular elsewhere, it's already in place.IsFoldedDeck(used for the SaveCard auto-spread) doesn't checkCanFold, so the flag can be set when the deck has one visible card (e.g., a filter reducing it to one). That matches the test's flow and produces a sensible result (column shows both cards spread), but the state is slightly wider than "a folded deck was opened" — worth a line indocs/ui.mdif it ever matters.Coverage in
board.spec.jsandmobile.spec.jsis thorough: fold, count, drop-below-the-deck landing on top, filter interplay, reload persistence, and the phone geometry checks (footnote centering, edge clearance, touch target height).⏱ 1m 1s · 8 steps · $0.0158 · tokens: 36.1k in / 1.2k out (+170.5k cache-read) · Go: 5h 4% · wk 9% · mo 87%
📜 Full session transcript
🤖 Resolving the merge conflicts — the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the fix runs.
The merge is complete and verified. Final state: merge commit
7897bfdonui-v2-deck, working tree clean, both parents' changes fully present.What conflicted and how it was resolved (main's #242 "save as you leave each field + Undo" vs this PR's "fold the last column into a deck"):
BoardPage.razor— one hunk, the tail ofSaveCard. The branch re-declaredvar isNewand closed the dialog after every save (old flow); main closes only for a new card so an existing card's inspector stays open. Kept main'sif (isNew) CloseModal();and kept the branch'sif (spread) _spreadDecks.Add(card.BoardColumnId);(a card created into a folded deck lays the deck out), dropping the branch's duplicate declaration and unconditional close.Changelog.cs— both added an Unreleased bullet (en + de). Kept both; 7 bullets is withinChangelogTests' cap.docs/frontend.md— both rewrote the same "Two things lead the app" bullet: main declared the v2 region the adopted direction (sidebar, inspector, save-as-you-leave, Undo built); the branch's edit moved the Done deck out of the "waits on sign-off" list as built. Took main's framing and folded the PR's fact in: foundations, sidebar, inspector, save-as-you-leave, Undo and the Done deck are built; drag physics and phone navigation are still being built.tests/e2e/board.spec.js— import header only: unioned both (createBoard/deleteBoard/boardName/addTag+devLoginfor the deck tests,psqlfor main's Undo test); every helper verified exported.Unmarked clashes checked: no remaining references to keys/rules #242 removed (
board.card.saved,modal.save,*.delete.confirm,.delete-confirm-label,.comment-delete-confirm); deck CSS and toast/modal-saved CSS are disjoint;CardService.UpdateCardAsync→UpdateTextAsyncrename has no stale callers; main's reworkeddeleteBoardhelper fits the new deck tests.Verified:
dotnet build Ponente.slnclean (0 warnings),dotnet test584/584 green (includes Changelog, locale and DocumentationTests),node --checkon every merged e2e spec, and no conflict markers anywhere.→ Merge resolved and pushed; a re-review follows.
⏱ 9m 2s · 44 steps · $0.1148 · tokens: 313.1k in / 4.2k out (+1888.5k cache-read) · Go: 5h 6% · wk 9% · mo 87%
📜 Full session transcript
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
My earlier pass raised no blocking issues, so there was nothing to resolve. I re-read the diff only. I did not run the build or the tests.
_spreadDecksis circuit-local and cleared on a board switch.ResolveDropIndexreturns 0 for a folded deck before the filter mapping, and the pager tile goes through the same path..onekey) and the changelog line, and the docs are updated.Minor, not blocking, and unchanged from my last pass:
IsFoldedDeckis true for a deck of one, whileColumnView.IsFoldedis false there. The docs say this is deliberate, but a one-line comment onIsFoldedDeckwould help later readers.🧠 claude-sonnet-5-5 · ⏱ 0m 6s · 2 turns · tokens: 24.0k in / 622 out (+12.2k cache-read)
📜 Full session transcript
7897bfd424ff39593b92Rebased onto main after #242 and #243 merged (
ff39593). This replaces opencode-bot's merge of main, which covered only #242. Changes in the rebase:IsDeckneeds two columns or more (_columns is [_, .., var last]). A board's only column holds everything, not just what is finished.docs/ui.mdsays so.IsFoldedDeckhas the comment claude-reviewer asked for: it is true for a deck of one too, whichColumnViewshows unfolded, so a drop there still goes on top.docs/ui.mdandBoardPage.psql) and #243's.The full e2e suite is running on this exact tree.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
Good to merge. I re-read the diff only and did not run the build or the tests.
_spreadDecksis circuit-local and cleared on a board switch.ResolveDropIndexreturns 0 for a folded deck before the filter mapping, and the pager tile goes through the same path.Minor, not blocking, and unchanged from my earlier passes:
IsFoldedDeckis true for a deck of one, whileColumnView.IsFoldedis false there. A one-line comment onIsFoldedDeckwould help later readers.Changelog.cs, the "A fresh look" lines in both locales have stray extra leading whitespace.🧠 claude-sonnet-5-5 · ⏱ 0m 8s · 2 turns · tokens: 25.4k in / 1.2k out (+12.2k cache-read)
📜 Full session transcript
💬 Verdict: comment — the reviewer did not submit a structured review; the raw output follows.
(empty)
⏱ 30m 2s · 2 steps · $0.0027 · tokens: 14.7k in / 26 out (+0 cache-read) · Go: 5h 6% · wk 9% · mo 87%
📜 Full session transcript
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
✅ Verdict: approve — no blocking issues found.
Good to merge. This is a re-review: the minor points from earlier passes are addressed —
IsFoldedDeckinBoardPage.razornow carries the one-line comment explaining that it is true for a deck of one (so a drop there still lands on top and stays visible once it folds), and both locales define theboard.deck.more.oneslot thatTCountrequires.Verified in the checkout beyond the diff:
SaveCardcomputes the flag before the save and only commitsif (spread)after the successful path; thePermissionDeniedExceptionand generic failure pathsreturnearly, so a failed create never opens the deck.L.TCountuseskey.oneat exactly one and fills{0}with the count, matching the"1 more"/"1 weitere"copy and the e2e assertions.sortable-interop.jsthe Sortable is bound withdraggable: '.card', so the injectedcolumn-deck-morebutton and the::before/::afteredges are neither draggable nor counted inevt.newIndex, and the ghost'scard-ghostclass is what the deck'sorder: -1rule targets.CanFold/IsFoldedinColumnViewderive purely from the filtered view, so filter and deck compose without either knowing about the other; the toggle'saria-expanded/aria-controlsare consistent.Minor, not blocking:
Changelog.cs: the "A fresh look" / "Ein frischer Look" lines in both locales have stray extra leading indentation (about 20 unwanted spaces) left over from editing a line the PR otherwise rewrote. Purely cosmetic in a string array.⏱ 1m 0s · 5 steps · $0.0093 · tokens: 40.1k in / 897 out (+57.7k cache-read) · Go: 5h 6% · wk 9% · mo 87%
📜 Full session transcript
@ -43,3 +42,3 @@"**A fresh look.** Softer surfaces, a new typeface, and each board wears its own colour.","**A sidebar on wide screens.** Your boards, search, Home and Calendar sit on the left; a board\u2019s Board, Calendar and Settings sit under its name.","**A sidebar and a card panel on wide screens.** Your boards, search, Home and Calendar sit on the left. A card opens in a panel on the right while the board stays usable: click another card to open it there.","**No Save, no \u201care you sure?\u201d.** A card saves each change as you make it; press Done when you\u2019re finished. Deleting a card, comment or board happens at once, with Undo for a few seconds.",Stray extra leading indentation on this line (and the "Ein frischer Look" line in the de locale) — about 20 unwanted spaces left when the neighbouring lines were rewritten. Cosmetic; worth collapsing.
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
Good to merge. This re-review confirms the one note from my last pass is resolved:
Changelog.csno longer has stray leading indentation on the "A fresh look" (en) and "Ein frischer Look" (de) lines — both locales now sit as plain, evenly indented array entries, and the new deck lines land cleanly in both.The core behaviour is unchanged from the earlier approvals and still holds:
_spreadDecksis circuit-local and cleared on board switch;ResolveDropIndexshort-circuits folded decks to 0 before the filter mapping; the auto-spread on save only fires for a new card with the deck already showing a card, and the flag commits only after a successful save; the deck's::before/::afteredges stay invisible to SortableJS;IsFoldedDeckcarries the explanatory comment about decks of one; and both locales define theboard.deck.more.oneslot.Remaining non-blocking notes, unchanged from earlier passes:
IsFoldedDeckis slightly wider than "a folded deck was opened" (it is true for a deck reduced to one card by the filter), which is deliberate and matches the test flow; a line indocs/ui.mdwould help if it ever matters.No new issues found.
⏱ 0m 11s · 3 steps · $0.0050 · tokens: 24.5k in / 533 out (+31.4k cache-read) · Go: 5h 6% · wk 9% · mo 87%
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
Good to merge. I re-read the diff only and did not run the build or the tests. My earlier passes raised no blocking issues.
_spreadDecksis circuit-local and cleared on a board switch.ResolveDropIndexreturns 0 for a folded deck before the filter mapping.sync()now returns whether every column was bound, and the signature is stored only then. This fixes the bug where a board would not drag after the spinner's render.Minor, not blocking, and unchanged from earlier passes:
IsFoldedDeckis true for a deck of one, whileColumnView.IsFoldedis false there. The comment now present onIsFoldedDeckcovers this.🧠 claude-sonnet-5-5 · ⏱ 0m 5s · 2 turns · tokens: 27.0k in / 512 out (+12.2k cache-read)
📜 Full session transcript