QA pass over the member-owned boards and home page work #212
No reviewers
Labels
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente!212
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "qa-home-and-boards"
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?
A QA pass over the merged #191–#195 (member-owned boards, the home page, per-member order). I reviewed the code and ran the full e2e suite against
main: 16 of 158 tests failed, because the bots merged without running e2e, which CI doesn't run. All 158 pass on this branch. Two tests are timing-flaky and have been hardened; both are noted below.Closes #205, #209, #210.
Security
Settings/Columns.OnColumnMovedis[JSInvokable]and takes its column id from the DOM, andRequireAdministeronly asked whether the viewer was an admin, not an admin of which board. Every member is an admin of the boards they make, so every member qualified.BoardViewernow carries theBoardIdit was built for, andRequireAdminister(actor, boardId)refuses any mismatch (BoardPermissionsTests).BoardPage.OnCardMoved→MoveCardAsynctook no viewer, so a member could reorder cards on a board they can't see. It now takes the board the page resolved and refuses a card that isn't on it.Behaviour fixes
BoardSuccession,BoardSuccessionTests). It refuses only when a board has nobody else on it.Allowlist:Adminsloses/admin, so the app now logs a warning per DID. There's also a note indeploy/README.md.ForYouService.Lookback)./adminpages (for a non-super-admin, following one would loop) and a board's settings pages./launch/rememberaccepts a same-hostOriginfrom browsers that send noSec-Fetch-Site(Safari before 16.4).Memberslink is a row in the account menu at every width.CommentListno longer re-reads and decrypts the thread on every render of the dialog. The calendar's dialog no longer shows edit controls for a round trip to somebody who can't edit.Docs
CardPermissions's class comment.auth.mdon/admin/boards,frontend.mdon the default board.boards-and-permissions.md: the/route, the Admin flyout,ViewerDid, the demotion path, and "a call site cannot ask about a different board".ui.md's antiforgery claim (the middleware doesn't check a JSON-bound endpoint).testing.md,data.md, anddeploy/README.md.e2e
helpers/members.js. One of its three copies filled signup fields that don't exist.openBoardpicks the oldest board. Since #195 the first tile is the one you last opened, so three tests had been asserting nothing.share.speccleaned up via/.tests/e2e/smoke-access.pngremoved, and screenshots are now ignored.filter.spec's drop (a press before the interop re-wires the columns selects text instead) andaccess.spec's switcher check.Separately (#211, already merged)
While running this suite, a real OAuth token for the admin DID sat in the local test DB, and
profile.specwrote to that account's real Bluesky profile. #211 makes it impossible for a dev sign-in to act on an Atmosphere account.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
✅ Verdict: approve — no blocking issues found.
A thorough QA pass, and the two security holes are closed correctly, not cosmetically.
Security fixes verified against the code:
RequireAdminister(actor, boardId)now refuses a viewer built for a different board. I checked every call site:BoardService(rename/delete/SetAccess),ColumnServiceandTagServiceall pass the board of the row being written, and every page-built viewer comes fromBoardService.ViewerOnAsync(boardId, did), which carries theBoardId.BoardViewer.For(...)(BoardId = 0) appears only on read paths, so the new check cannot refuse a legitimate admin.BoardPermissionsTestspins both the mismatch and the BoardId-less viewer.MoveCardAsynctakes the board the page resolved and refuses a card not on it;BoardPageis the only caller and passes_board.Id.Behaviour fixes verified:
MemberService.RemoveAsyncpromotes the longest-standing other member in the same save (tracked entities, oneSaveChangesAsync), and throwsLastBoardAdminExceptionbefore any write when a board has nobody else — so a partial promotion can never land.BoardSuccessionis pure and pinned, including the tie-break.MigrateAsync— right in both ordinary cases (fresh DB, existing DB).EnsureBoardAdminsAsyncpromotes a held plain grant instead of inserting a duplicate (board, DID) key.IsSuperAdminPage,IsAboutBoardincluding settings pages) and the sign-in/sign-out cookie forget are consistent, and the/middleware renders home on a refused target rather than redirecting, so the loop described in the PR can't happen.ForYouService.Lookback, theCommentListreload guard (record-struct equality is the right tool), and the Calendar modal viewer being computed before the dialog renders are all correct.Docs and tests are updated in the same PR as the convention requires, the e2e helper consolidation removes the copy that could never have passed, and the two flaky tests are hardened rather than papered over.
Two non-blocking observations:
OnRedirectToAccessDeniedoverride always redirects, dropping the default handler's AJAX→403 branch. Harmless here since Blazor circuit authorization doesn't travel that HTTP path, but worth knowing if a fetch-based caller ever hits an authorized endpoint.No
Changelog.Unreleasedadditions, but every change here refines behaviour of features already listed in the unreleased block, so nothing member-visible is going unannounced.⏱ 3m 46s · 14 steps · $0.0632 · tokens: 261.8k in / 9.6k out (+637.7k cache-read) · Go: 5h 9% · wk 3% · mo 34%
📜 Full session transcript