Finish board settings, members and the profile in the UI language v2 #255
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!255
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "ui-v2-settings"
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?
Board settings, Members and the profile in the UI language v2: the draft's grouped lists, saving as you go, Undo instead of Save buttons, and the phone's drill-in.
One grouped-list system in app.css ("Pages of grouped lists":
.page/.page-head/.page-crumb/.page-title,.group/.group-label/.group-box/.group-row/.group-note,input.switchwith role=switch,input.swatch,.chip, small and flat buttons, ⋯ row menus), plusComponents/MemberFace.razor(picture, or initials on a DID-hashed colour) andComponents/Settings/TrashGlyph.razor. The old admin cards, tables and choice cards are gone; the super-admin's board record uses the same lists.Board settings has a crumb, the large "Board settings" title and segmented tabs, and no title bar, as in the draft.
BoardService.IsPinnedAsync), and the delete row (soft delete with Undo).BoardsChanged.ColumnService/TagService.CardCountsAsync, andUndoServicekeys for columns, tags and requests.Members shows "N members · M waiting", an Add a member lookup row, and access requests with face, name, handle or email, chips, date and message. Their Deny / Approve / Approve and invite are deferred through
UndoServicewith Undo, and an unresolvable or banned requester is refused first. Invites show code, chip, copy link, Disable, "+ Create invite" and "for {name}". Current members have a ⋯ menu with Ban…, Remove and Lift ban.Profile is grouped lists: the name saves on leave, the picture on pick, and the "where it applies" radio writes nothing to the network by itself.
Phone (<600px): a drill-in. General is a list under the board's phone bar (name and pin, then Columns, Tags and Access rows, then a red Delete row). The sub-pages have a bar of their own (‹ Settings, the title, Share), with red-minus delete, a three-line handle, an add row closing the list, trailing ticks, and an Admin tag on Access.
Judgement calls
Docs:
ui.md(a new Settings, members and your profile: grouped lists section, Undo, the title bar),boards-and-permissions.md(access applies at once, pin, delete),auth.md(profile),access-requests.md(deferred answers), andfrontend.md(controls, drag, the v2 status line; the old Admin — Members · redesign canvas frame is marked superseded).design/trace/capture.mjsno longer clicks the one-click delete. The changelog line is folded into the save-as-you-go bullet.Verified:
tools/verify.sh(600 unit tests). The full Playwright suite passed 161/161 on an isolated stack, on this exact tree stacked on the board and Home PRs. The access, permissions, requests, moderation and profile specs were reworked for the new flows. Checked by hand against a seeded dev app, light and dark, desktop and phone: rename, delete and Undo for columns and tags; access grant, revoke, admin, visibility and Undo, including the last-admin refusal; ban and lift through the member menu; deny and Undo on a request; drag order.🤖 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.
I reviewed this from a truncated diff, so the tail was not visible. That covers the remaining BoardSettings markup, the columns, tags and
UndoServicecode, the CSS, the tests and the locale entries. The visible half hangs together._generationkey to redraw refused rows are sound. TheLastBoardAdminExceptionpath is handled.UndoService, with the resolve and ban checks done before the toast, fits the stated reasoning. The docs were updated alongside, as CLAUDE.md requires.RequestsPanel: theMemberBannedExceptioncatch was dropped from the deferred approval. A ban landing inside the roughly 7 s window would surface as the generic "already answered" toast rather than the banned message. This is minor and unlikely.ApplyAsyncreads the access state and writes the whole thing back, so two admins editing at the same moment can overwrite each other. TheSetAccessAsyncrules still hold, and the old Save button had the same exposure, so this is acceptable.🧠 claude-sonnet-5-5 · ⏱ 0m 16s · 2 turns · tokens: 65.9k in / 1.9k out (+12.2k cache-read)
📜 Full session transcript
💬 Verdict: comment — the reviewer did not submit a structured review; the raw output follows.
I've reviewed the diff and the touched components/services. Let me check one last detail — how toast actions run relative to the access write queue:
⏱ 30m 3s · 38 steps · $0.1453 · tokens: 408.4k in / 1.6k out (+1878.8k cache-read) · Go: 5h 5% · wk 14% · mo 90%
📜 Full session transcript
47232d9: an access Undo now goes through the same write queue as the changes (UndoAsync). Each step reads and writes the whole access state, so an Undo pressed beside a change still in flight could have written back the state from before that change. opencode's review ran out of time just as it got to this.Not changed:
UndoService.Deferpick a message per exception, for a very narrow race.@opencode /review
🤖 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 latest commit, "Queue an access Undo behind the writes in flight", fixes a race I would otherwise have raised.
UndoAsyncnow takes the same_writingsemaphore asChangeAsync. An Undo that overlaps a change in flight can no longer write back the state from before that change.My earlier notes were non-blocking, and I haven't re-verified them:
MemberBannedExceptionin the deferred approval: the catch is still absent inRequestsPanel.razor. A ban landing inside the roughly 7 s window would show the generic "already answered" toast instead of the banned message. This is unlikely and minor.I have not re-read the truncated tail of the diff since the first pass. Nothing here blocks merging.
🧠 claude-sonnet-5-5 · ⏱ 0m 8s · 3 turns · tokens: 66.9k in / 574 out (+90.9k cache-read)
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
Re-review. My previous pass left one open question — how a toast's Undo runs relative to the access page's write queue — and it is resolved: toast actions run through the circuit dispatcher (
ToastContainer.RunAsync), and the access page'sUndoAsyncjoins the same_writingsemaphore as the changes, so an Undo beside an in-flight change queues instead of writing back the state before it. Nothing blocking remains; the non-blockers from earlier passes stand as noted (a ban landing inside a request's Undo window shows the generic "already handled" failure toast rather than the banned message; whole-state writes can lose a concurrent admin's edit, same exposure as the old Save button).The rest of the change checks out against the code: the access steps and
_generationredraw, the deferred request answers with the resolve and banned checks done before the toast,UndoService's claim race ("Too late to undo") and its failure path that settles the key so a hidden item comes back, the columns page's_moves-keyed redraw after a drop, the pin switch backed by realIsPinnedAsync/SetPinnedAsync, the board delete's Undo toast surviving the navigate-home becauseToastContaineris global, and every new locale key present in both languages. Docs and the trace script were updated alongside, as CLAUDE.md requires.Minor, non-blocking, in the findings: the restore-on-refusal of the text fields (board, column and tag renames) can leave the field showing the emptied or failed text, since Blazor skips writing a value equal to the one it last rendered — the very behaviour the access page works around with
_generationand the Columns page documents. Relatedly,PinAsyncflips the switch optimistically and doesn't redraw it on a failed write. Both are rare failure paths and don't lose data.⏱ 6m 49s · 13 steps · $0.0554 · tokens: 236.3k in / 2.1k out (+479.1k cache-read) · Go: 5h 6% · wk 14% · mo 90%
📜 Full session transcript
@ -50,0 +142,4 @@var name = _name.Trim();if (name.Length == 0 || name == _board.Name){_name = _board.Name;This restores
_nameafter a refused or emptied rename, but the input may keep showing what the user typed (or an emptied field): Blazor skips writing an attribute equal to the value it last rendered — the exact skip the access page works around with_generationand the comment inColumns.ReloadAsyncdocuments. Same for the restore inColumns.RenameAsync(_edits[columnId] = current) andTags.SaveAsync(draft with { Name = tag.Name }). The model is right, so data is safe; only the field can look wrong until something else re-renders. A generation counter in those rows'@key, as on the access page, would close it.🔧 Let opencode-bot fix this — tick the box and opencode-bot picks up the review above, on this PR branch. Nothing runs until you do.