Invitation lifecycle UI: expiry display + invitee inbox/accept page #362
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#362
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Survey: crueber/walhub#349 candidate 10.
Evidence
Design
Acceptance criteria
Fixed by PR #369 (#369) — expiry column in org Invitations tab, new /invitations inbox (list/preview/accept/decline + shared-link deep link), header link for authenticated users; inbox rows now serve expires_at. Tests green; real-browser check recorded open (loopback guard).
Review of PR #369 (fix/issue-362), verified in a scratch worktree (created/removed by me; main worktree left clean, read-only):
(1) Expires column — CORRECT. web/src/pages/Org.jsx InvitesTab: DateTime while pending, expired chip once isInviteExpired fires (at-or-before-now boundary, tested), em-dash fallback when the row predates the field. Cancel stays on expired rows. Matches law 5 (display fails open, server fails closed).
(2) Inbox page — CORRECT. web/src/pages/Invitations.jsx lists invites.mine() rows (org+repo, kind/scope/role/inviter/expiry cells), subject-authorized preview without token, accept disabled once expired with 409-fail-closed title (server enforces at the issuer object in findInvite, internal/identity/invites.go:370-372), decline via top-level DELETE, ?id=&token= deep-link token-authorized preview on top. Anonymous gets the friendly sign-in message (handler 401s anonymous at http_invites.go:28-31,57-60 — pre-existing backend, page handles it).
(3) Header link gating — CORRECT. web/src/App.jsx gates on shared unreadCount() !== null from Notifications.jsx:12; null exactly when the authenticated unread_count probe failed, so zero new requests and anonymous never sees the link.
(4) InboxEntry.expires_at — CORRECT. internal/identity/invites.go:50 omitempty append-only; both create sites stamp (org :191, repo :231); no fan-out (MyInvites returns rows directly). Pre-#362 rows decode '' and isInviteExpired fails open (never expired on display) while accept still 409s via the issuer object. Documented in features/01 §7 + Decisions.
(5) Route ordering — CORRECT. /invitations (index.jsx:66) sits with the other statics before /:owner (:72) with the standard comment.
(6) Repo-admin panel out-of-scope — TRUE. No repo invite list UI exists (Access.jsx is bindings-only; repo invites are create/cancel via API); inbox covers repo-kind rows with expiry; follow-up noted in 12_web_ui.md Decisions.
(7) Accept/decline auth — CORRECT, invitee-only. AcceptInvite (:408-416) resolves through the caller's own inbox (findInvite :328) plus subject match; top-level DELETE cancelInvite (http_invites.go:109-121) does the same. Others' invites are unreachable (409/403).
(8) Checks — identity -race green; cover 96.3% (gate holds); gofmt/vet clean; go build ./... green; node --test 718 pass / 0 fail / 3 skipped (smoke only); vite build + esbuild bundle green; go.mod + web/package.json untouched (no new deps); docs (features/01 §7 + Decisions, 12_web_ui Decisions) accurate.
Notes (non-blocking): node smoke tests only skip when the base URL is unreachable — something on this machine's :8080 answers /healthz 200 but /repos.js 503 (setup-only-mode shape); I did not touch it and ran unit tests with an unreachable base instead. Real-browser check still open per the PR description (shared obscura daemon loopback guard; no private daemon per workspace rules) — noted explicitly, no browser used.
No fixes needed — nothing pushed. MERGE RECOMMENDATION: ready to merge.
Fixed by PR #369 (review clean — all 8 points pass, accept fail-closed, invitee-only, back-compat verified; 718 pass), merged. Closing.