Fold a board's last column into a deck #245

Merged
Grandiras merged 3 commits from ui-v2-deck into main 2026-10-03 02:52:53 +00:00
Owner

The v2 language's Done deck, signed off: a board's last column folds into a deck.

What changed

  • Folded, the last column shows its first card over the edges of two more (about 7px and 14px lower, inset, the farther one fainter), and under them a centred count of the rest: "6 more" / "6 weitere". No "done": the column may be called anything.
  • Show all beside the column's count lays the deck out, and then reads Show less (de: Alle zeigen / Weniger zeigen). The count under the deck lays it out too, as the draft's "tapping fans it out".
  • Folds only with two cards or more. A deck of one is just a column.
  • Laid out is circuit state; every visit starts folded, unpersisted like the tag filter and for its reason. A board switch drops it.
  • The tag filter goes first, and the deck folds what it leaves: the top card is the first match, the count counts matches.
  • A deck that is not laid out takes every drop on top, wherever the card fell and from the phone's pager tile too, since its top card is all that shows (BoardPage.ResolveDropIndex answers 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.
  • No Add card under a folded deck (a new card goes to the foot, out of sight). A card made in a deck that already shows one lays the deck out, or it would fold away as it was saved.
  • The edges are the card list's own ::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 under prefers-reduced-motion.
  • The phone shows the same deck on the last column's page; the header action is as tall as the filter button there.
  • Changelog line in both locales; docs/ui.md has a new The deck section, docs/frontend.md the 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).
  • Full Playwright suite on an isolated, port-shifted stack: 160/160 passed, including two new tests: 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) and mobile.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).
  • In targeted reruns the board test's drag flaked in 3 of about 8 runs during one window and then passed 30+ times in a row. The drop now holds a moment and moves once more before release, since SortableJS skips a move over a card that is still animating. 10/10 after that.
  • Full suite again with the final specs: 159/160. The one failure was 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.
  • Looked at the deck on a seeded dev app at 1440×900 in light and dark, at 768px in German, beside the inspector, at 390×844 folded and laid out, and mid-drag (the ghost shows on top).

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 reaches BoardPage's interop once a load has set _columns but 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

The v2 language's **Done deck**, signed off: a board's last column folds into a deck. ## What changed - **Folded**, the last column shows its first card over the edges of two more (about 7px and 14px lower, inset, the farther one fainter), and under them a centred count of the rest: "6 more" / "6 weitere". No "done": the column may be called anything. - **Show all** beside the column's count lays the deck out, and then reads **Show less** (de: *Alle zeigen* / *Weniger zeigen*). The count under the deck lays it out too, as the draft's "tapping fans it out". - **Folds only with two cards or more.** A deck of one is just a column. - **Laid out is circuit state; every visit starts folded**, unpersisted like the tag filter and for its reason. A board switch drops it. - **The tag filter goes first, and the deck folds what it leaves**: the top card is the first match, the count counts matches. - **A deck that is not laid out takes every drop on top**, wherever the card fell and from the phone's pager tile too, since its top card is all that shows (`BoardPage.ResolveDropIndex` answers 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. - **No Add card under a folded deck** (a new card goes to the foot, out of sight). A card made in a deck that already shows one lays the deck out, or it would fold away as it was saved. - **The edges are the card list's own `::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 under `prefers-reduced-motion`. - The phone shows the same deck on the last column's page; the header action is as tall as the filter button there. - Changelog line in both locales; `docs/ui.md` has a new *The deck* section, `docs/frontend.md` the 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). - Full Playwright suite on an isolated, port-shifted stack: **160/160 passed**, including two new tests: `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) and `mobile.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). - In targeted reruns the board test's drag flaked in 3 of about 8 runs during one window and then passed 30+ times in a row. The drop now holds a moment and moves once more before release, since SortableJS skips a move over a card that is still animating. 10/10 after that. - Full suite again with the final specs: **159/160**. The one failure was `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. - Looked at the deck on a seeded dev app at 1440×900 in light and dark, at 768px in German, beside the inspector, at 390×844 folded and laid out, and mid-drag (the ghost shows on top). **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 reaches `BoardPage`'s interop once a load has set `_columns` but 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](https://claude.com/claude-code)
Fold a board's last column into a deck
All checks were successful
CI / build (pull_request) Successful in 40s
ccfd96cc11
The v2 language's Done deck: the last column shows its top card over the
edges of two more, with a count of the rest under them. Show all beside the
column's count lays it out and then reads Show less; the count lays it out
too. Folded is every visit's start, unpersisted like the tag filter.

- Folds only from two cards, and folds what the tag filter leaves.
- A drop on a deck that is not laid out lands on top, from the pager too;
  the ghost over it is drawn first, where it will land.
- No Add card under a folded deck; a card made in a deck holding one lays
  it out, since new cards go to the foot.
- The edges are the card list's ::before and ::after, so SortableJS sees
  no extra children; the cards beneath come out under the top one on a
  starting style, without motion when reduced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-05-31-601Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 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.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/680); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-680.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-03 02:06:02 +00:00
Dismissed
claude-reviewer left a comment

✅ 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.

  • State and drops: The deck state is circuit-local and cleared on a board switch. ResolveDropIndex short-circuits to 0 before the filter mapping, as documented. The pager tile goes through the same path.
  • Auto-spread on save: The rule matches the e2e test. It fires only when the deck already shows a card, so the first card in an empty deck does not spread it.
  • Copy and docs: Both locales have board.deck.more and .one, the changelog line is in both locales, and docs/ui.md and docs/frontend.md are updated.
  • Edges: Making the edges the list's ::before/::after keeps SortableJS from seeing extra children. Tokens are used throughout.
  • Tests: The new e2e tests use boards of their own, so the deployment's last column is left alone for the rest of the suite.

Minor, not blocking:

  • IsFoldedDeck in BoardPage is true for a deck of one, while ColumnView.IsFolded is false there. The docs say this is deliberate, and ResolveDropIndex returns 0 in that case. It is easy to misread later, so a one-line comment on IsFoldedDeck would help.
  • A card dropped onto a filtered, folded deck goes to column index 0 even when it does not match the filter. It then disappears from view, as it already did in filtered columns before this change.

🧠 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 change looks good to merge. I read the diff only and did not run the build or the tests. - **State and drops:** The deck state is circuit-local and cleared on a board switch. `ResolveDropIndex` short-circuits to 0 before the filter mapping, as documented. The pager tile goes through the same path. - **Auto-spread on save:** The rule matches the e2e test. It fires only when the deck already shows a card, so the first card in an empty deck does not spread it. - **Copy and docs:** Both locales have `board.deck.more` and `.one`, the changelog line is in both locales, and `docs/ui.md` and `docs/frontend.md` are updated. - **Edges:** Making the edges the list's `::before`/`::after` keeps SortableJS from seeing extra children. Tokens are used throughout. - **Tests:** The new e2e tests use boards of their own, so the deployment's last column is left alone for the rest of the suite. Minor, not blocking: - `IsFoldedDeck` in `BoardPage` is true for a deck of one, while `ColumnView.IsFolded` is false there. The docs say this is deliberate, and `ResolveDropIndex` returns 0 in that case. It is easy to misread later, so a one-line comment on `IsFoldedDeck` would help. - A card dropped onto a filtered, folded deck goes to column index 0 even when it does not match the filter. It then disappears from view, as it already did in filtered columns before this change. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 11s · 2 turns · tokens: 23.0k in / 1.1k out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-680.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:ccfd96cc11661844d95e976e0345cd39c9ad533c -->
opencode-reviewer approved these changes 2026-10-03 02:06:32 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

The fold logic is correct and well-factored. CanFold/IsFolded in ColumnView derive purely from the filtered view, so filter and deck compose without either knowing the other, and the fold decision is applied uniformly (drop indexing via ResolveDropIndex'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 injected column-deck-more button is exempt from dragging by the existing draggable: '.card' selector, with newIndex counting 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.one is a duplicate of the plural form in both locales; harmless since TCount requires the key.one slot 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 check CanFold, 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 in docs/ui.md if it ever matters.

Coverage in board.spec.js and mobile.spec.js is 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

✅ **Verdict: approve** — no blocking issues found. The fold logic is correct and well-factored. `CanFold`/`IsFolded` in `ColumnView` derive purely from the filtered view, so filter and deck compose without either knowing the other, and the fold decision is applied uniformly (drop indexing via `ResolveDropIndex`'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 injected `column-deck-more` button is exempt from dragging by the existing `draggable: '.card'` selector, with `newIndex` counting 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.one` is a duplicate of the plural form in both locales; harmless since `TCount` requires the `key.one` slot 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 check `CanFold`, 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 in `docs/ui.md` if it ever matters. Coverage in `board.spec.js` and `mobile.spec.js` is 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](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-05-31-601Z.md) <!-- opencode-reviewed-head:ccfd96cc11661844d95e976e0345cd39c9ad533c --> <!-- opencode-verdict:approve -->
Collaborator

🤖 Resolving the merge conflicts — the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the fix runs.

🤖 Resolving the merge conflicts — the reply will link the full session transcript. <!-- opencode-run:conflict --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-10-07-990Z.md) — updating every ~5 minutes while the fix runs.
Merge remote-tracking branch 'origin/main' into ui-v2-deck
All checks were successful
CI / build (pull_request) Successful in 37s
7897bfd424
# Conflicts:
#	docs/frontend.md
#	src/Ponente.Kanban/Components/Pages/BoardPage.razor
#	src/Ponente.Kanban/Services/Changelog.cs
#	tests/e2e/board.spec.js
Collaborator

The merge is complete and verified. Final state: merge commit 7897bfd on ui-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 of SaveCard. The branch re-declared var isNew and 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's if (isNew) CloseModal(); and kept the branch's if (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 within ChangelogTests' 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 + devLogin for the deck tests, psql for 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→UpdateTextAsync rename has no stale callers; main's reworked deleteBoard helper fits the new deck tests.

Verified: dotnet build Ponente.sln clean (0 warnings), dotnet test 584/584 green (includes Changelog, locale and DocumentationTests), node --check on 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

The merge is complete and verified. Final state: merge commit `7897bfd` on `ui-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 of `SaveCard`. The branch re-declared `var isNew` and 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's `if (isNew) CloseModal();` and kept the branch's `if (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 within `ChangelogTests`' 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` + `devLogin` for the deck tests, `psql` for 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`→`UpdateTextAsync` rename has no stale callers; main's reworked `deleteBoard` helper fits the new deck tests. **Verified**: `dotnet build Ponente.sln` clean (0 warnings), `dotnet test` 584/584 green (includes Changelog, locale and DocumentationTests), `node --check` on 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](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-10-07-990Z.md) <!-- opencode-session:ses_f007958b8ffexV2ul5Y5PNN6Sv -->
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-20-11-192Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 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.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/682); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-682.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-03 02:20:31 +00:00
Dismissed
claude-reviewer left a comment

✅ 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.

  • Deck state: _spreadDecks is circuit-local and cleared on a board switch. ResolveDropIndex returns 0 for a folded deck before the filter mapping, and the pager tile goes through the same path.
  • Auto-spread on save: the check runs before the save and only fires when the deck already shows a matching card. The first card in an empty deck does not spread it.
  • Edges and copy: the edges are the list's own pseudo-elements, so SortableJS sees no extra children. Both locales have the strings (including the .one key) and the changelog line, and the docs are updated.
  • Tests: the new e2e tests use boards of their own.

Minor, not blocking, and unchanged from my last pass:

  • IsFoldedDeck is true for a deck of one, while ColumnView.IsFolded is false there. The docs say this is deliberate, but a one-line comment on IsFoldedDeck would help later readers.
  • A non-matching card dropped on a filtered, folded deck goes to index 0 and then disappears from view. Filtered columns already behaved this way.

🧠 claude-sonnet-5-5 · ⏱ 0m 6s · 2 turns · tokens: 24.0k in / 622 out (+12.2k cache-read)
📜 Full session transcript

✅ **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. - **Deck state:** `_spreadDecks` is circuit-local and cleared on a board switch. `ResolveDropIndex` returns 0 for a folded deck before the filter mapping, and the pager tile goes through the same path. - **Auto-spread on save:** the check runs before the save and only fires when the deck already shows a matching card. The first card in an empty deck does not spread it. - **Edges and copy:** the edges are the list's own pseudo-elements, so SortableJS sees no extra children. Both locales have the strings (including the `.one` key) and the changelog line, and the docs are updated. - **Tests:** the new e2e tests use boards of their own. Minor, not blocking, and unchanged from my last pass: - `IsFoldedDeck` is true for a deck of one, while `ColumnView.IsFolded` is false there. The docs say this is deliberate, but a one-line comment on `IsFoldedDeck` would help later readers. - A non-matching card dropped on a filtered, folded deck goes to index 0 and then disappears from view. Filtered columns already behaved this way. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 6s · 2 turns · tokens: 24.0k in / 622 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-682.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:7897bfd424d10383e00647dd3c0d139c3df2fc38 -->
Grandiras force-pushed ui-v2-deck from 7897bfd424
All checks were successful
CI / build (pull_request) Successful in 37s
to ff39593b92
All checks were successful
CI / build (pull_request) Successful in 40s
2026-10-03 02:21:29 +00:00
Compare
Author
Owner

Rebased onto main after #242 and #243 merged (ff39593). This replaces opencode-bot's merge of main, which covered only #242. Changes in the rebase:

  • One-column boards don't fold: IsDeck needs two columns or more (_columns is [_, .., var last]). A board's only column holds everything, not just what is finished. docs/ui.md says so.
  • IsFoldedDeck has the comment claude-reviewer asked for: it is true for a deck of one too, which ColumnView shows unfolded, so a drop there still goes on top.
  • "Pager tile" now reads "segment", after #243's column switcher, in docs/ui.md and BoardPage.
  • Changelog back to 7 bullets per locale: the sidebar and card-panel lines are now one.
  • Spec imports merged with #242's (psql) and #243's.

The full e2e suite is running on this exact tree.

Rebased onto main after #242 and #243 merged (ff39593). This replaces opencode-bot's merge of main, which covered only #242. Changes in the rebase: - **One-column boards don't fold:** `IsDeck` needs two columns or more (`_columns is [_, .., var last]`). A board's only column holds everything, not just what is finished. `docs/ui.md` says so. - **`IsFoldedDeck` has the comment claude-reviewer asked for:** it is true for a deck of one too, which `ColumnView` shows unfolded, so a drop there still goes on top. - **"Pager tile" now reads "segment"**, after #243's column switcher, in `docs/ui.md` and `BoardPage`. - **Changelog back to 7 bullets per locale:** the sidebar and card-panel lines are now one. - **Spec imports merged** with #242's (`psql`) and #243's. The full e2e suite is running on this exact tree.
Collaborator

🤖 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.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/683); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-683.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-03 02:22:51 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. I re-read the diff only and did not run the build or the tests.

  • State and drops: _spreadDecks is circuit-local and cleared on a board switch. ResolveDropIndex returns 0 for a folded deck before the filter mapping, and the pager tile goes through the same path.
  • Auto-spread on save: it fires only when the deck already shows a matching card.
  • Edges, copy and docs: the edges are the list's own pseudo-elements, so SortableJS sees no extra children. Both locales have the strings and the changelog line, and the docs are updated.
  • Tests: the new e2e tests use boards of their own.

Minor, not blocking, and unchanged from my earlier passes:

  • IsFoldedDeck is true for a deck of one, while ColumnView.IsFolded is false there. A one-line comment on IsFoldedDeck would help later readers.
  • A non-matching card dropped on a filtered, folded deck goes to index 0 and then disappears from view. Filtered columns already behaved this way.
  • In 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: approve** — no blocking issues found. Good to merge. I re-read the diff only and did not run the build or the tests. - **State and drops:** `_spreadDecks` is circuit-local and cleared on a board switch. `ResolveDropIndex` returns 0 for a folded deck before the filter mapping, and the pager tile goes through the same path. - **Auto-spread on save:** it fires only when the deck already shows a matching card. - **Edges, copy and docs:** the edges are the list's own pseudo-elements, so SortableJS sees no extra children. Both locales have the strings and the changelog line, and the docs are updated. - **Tests:** the new e2e tests use boards of their own. Minor, not blocking, and unchanged from my earlier passes: - `IsFoldedDeck` is true for a deck of one, while `ColumnView.IsFolded` is false there. A one-line comment on `IsFoldedDeck` would help later readers. - A non-matching card dropped on a filtered, folded deck goes to index 0 and then disappears from view. Filtered columns already behaved this way. - In `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](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-683.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:ff39593b92ea9e6711a2447737bd5e409ae8cdb5 -->
opencode-reviewer left a comment

💬 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

💬 **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](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-20-11-192Z.md)
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-50-32-248Z.md) — updating every ~5 minutes while the review runs.
The spinner's render also reaches the interop once a load has set _columns but
before the columns render. sync() then found no columns, the signature was
remembered anyway, and the render with the columns skipped the interop: on
roughly one fresh load in six nothing on the board would drag until a reload.
sync() now answers whether every column was there, and the signature is kept
only then.

The deck test's earlier extra pause before the drop was a guess at this race,
so it goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix the indentation the changelog fold left on two lines
All checks were successful
CI / build (pull_request) Successful in 43s
85b78f3553
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
opencode-reviewer approved these changes 2026-10-03 02:51:32 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. This is a re-review: the minor points from earlier passes are addressed — IsFoldedDeck in BoardPage.razor now 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 the board.deck.more.one slot that TCount requires.

Verified in the checkout beyond the diff:

  • The auto-spread in SaveCard computes the flag before the save and only commits if (spread) after the successful path; the PermissionDeniedException and generic failure paths return early, so a failed create never opens the deck.
  • L.TCount uses key.one at exactly one and fills {0} with the count, matching the "1 more" / "1 weitere" copy and the e2e assertions.
  • In sortable-interop.js the Sortable is bound with draggable: '.card', so the injected column-deck-more button and the ::before/::after edges are neither draggable nor counted in evt.newIndex, and the ghost's card-ghost class is what the deck's order: -1 rule targets.
  • CanFold/IsFolded in ColumnView derive purely from the filtered view, so filter and deck compose without either knowing about the other; the toggle's aria-expanded/aria-controls are 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.
  • As noted before, a non-matching card dropped onto a filtered, folded deck goes to index 0 and vanishes from view — pre-existing filtered-column behaviour, not introduced here.

⏱ 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

✅ **Verdict: approve** — no blocking issues found. Good to merge. This is a re-review: the minor points from earlier passes are addressed — `IsFoldedDeck` in `BoardPage.razor` now 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 the `board.deck.more.one` slot that `TCount` requires. Verified in the checkout beyond the diff: - The auto-spread in `SaveCard` computes the flag before the save and only commits `if (spread)` after the successful path; the `PermissionDeniedException` and generic failure paths `return` early, so a failed create never opens the deck. - `L.TCount` uses `key.one` at exactly one and fills `{0}` with the count, matching the `"1 more"` / `"1 weitere"` copy and the e2e assertions. - In `sortable-interop.js` the Sortable is bound with `draggable: '.card'`, so the injected `column-deck-more` button and the `::before`/`::after` edges are neither draggable nor counted in `evt.newIndex`, and the ghost's `card-ghost` class is what the deck's `order: -1` rule targets. - `CanFold`/`IsFolded` in `ColumnView` derive purely from the filtered view, so filter and deck compose without either knowing about the other; the toggle's `aria-expanded`/`aria-controls` are 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. - As noted before, a non-matching card dropped onto a filtered, folded deck goes to index 0 and vanishes from view — pre-existing filtered-column behaviour, not introduced here. --- ⏱ 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](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-50-32-248Z.md) <!-- opencode-reviewed-head:ff39593b92ea9e6711a2447737bd5e409ae8cdb5 --> <!-- opencode-verdict:approve -->
@ -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.",
Collaborator

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.

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.
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-52-00-575Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 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.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/684); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-684.md) — updating every ~5 minutes while the task runs.
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. This re-review confirms the one note from my last pass is resolved: Changelog.cs no 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: _spreadDecks is circuit-local and cleared on board switch; ResolveDropIndex short-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/::after edges stay invisible to SortableJS; IsFoldedDeck carries the explanatory comment about decks of one; and both locales define the board.deck.more.one slot.

Remaining non-blocking notes, unchanged from earlier passes:

  • A non-matching card dropped onto a filtered, folded deck still vanishes from view at index 0 — pre-existing filtered-column behaviour, not introduced here.
  • IsFoldedDeck is 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 in docs/ui.md would 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. This re-review confirms the one note from my last pass is resolved: `Changelog.cs` no 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: `_spreadDecks` is circuit-local and cleared on board switch; `ResolveDropIndex` short-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`/`::after` edges stay invisible to SortableJS; `IsFoldedDeck` carries the explanatory comment about decks of one; and both locales define the `board.deck.more.one` slot. Remaining non-blocking notes, unchanged from earlier passes: - A non-matching card dropped onto a filtered, folded deck still vanishes from view at index 0 — pre-existing filtered-column behaviour, not introduced here. - `IsFoldedDeck` is 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 in `docs/ui.md` would 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](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-245/run-2026-10-03T02-52-00-575Z.md) <!-- opencode-reviewed-head:85b78f355312d82d9cd62871a75e7a106249b91a --> <!-- opencode-verdict:approve -->
claude-reviewer left a comment

✅ 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.

  • State and drops: _spreadDecks is circuit-local and cleared on a board switch. ResolveDropIndex returns 0 for a folded deck before the filter mapping.
  • Sortable binding fix: 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.
  • Auto-spread on save: it fires only when the deck already shows a matching card.
  • Edges, copy and docs: the edges are the list's own pseudo-elements, so SortableJS sees no extra children. Both locales have the strings and the changelog line, and the docs are updated.
  • Tests: the new e2e tests use boards of their own.

Minor, not blocking, and unchanged from earlier passes:

  • IsFoldedDeck is true for a deck of one, while ColumnView.IsFolded is false there. The comment now present on IsFoldedDeck covers this.
  • A non-matching card dropped on a filtered, folded deck goes to index 0 and then disappears from view. Filtered columns already behaved this way.

🧠 claude-sonnet-5-5 · ⏱ 0m 5s · 2 turns · tokens: 27.0k in / 512 out (+12.2k cache-read)
📜 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. - **State and drops:** `_spreadDecks` is circuit-local and cleared on a board switch. `ResolveDropIndex` returns 0 for a folded deck before the filter mapping. - **Sortable binding fix:** `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. - **Auto-spread on save:** it fires only when the deck already shows a matching card. - **Edges, copy and docs:** the edges are the list's own pseudo-elements, so SortableJS sees no extra children. Both locales have the strings and the changelog line, and the docs are updated. - **Tests:** the new e2e tests use boards of their own. Minor, not blocking, and unchanged from earlier passes: - `IsFoldedDeck` is true for a deck of one, while `ColumnView.IsFolded` is false there. The comment now present on `IsFoldedDeck` covers this. - A non-matching card dropped on a filtered, folded deck goes to index 0 and then disappears from view. Filtered columns already behaved this way. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 5s · 2 turns · tokens: 27.0k in / 512 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-245/run-684.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:85b78f355312d82d9cd62871a75e7a106249b91a -->
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente!245
No description provided.