Lock commenting (comments, replies, reactions, inline threads) on closed issues and merged/closed PRs #594
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#594
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?
User rulings (binding)
internal/pulls/service.go:908); astate:"open"patch on a merged PR currently SUCCEEDS server-side (only the UI hides the button —pull-state.js:219showReopen:false). This ticket must close that gap: server-side refusal of reopen-on-merged, and no reopen affordance anywhere for merged PRs (client already correct — bind it with a test).UpdatePRacceptsstate: open|closedfor author or triage role (internal/pulls/service.go:896-901), and the client shows Reopen viapullCloseVisibility(web/src/lib/pull-state.js:213-224). Reopening a plain-closed PR restores commenting, same as issues.What's requested
Lock commenting on finished conversations: when an issue is closed, or a PR is merged or closed, the thread becomes read-only — no new comments, no replies, no reactions, no inline review threads. Reopening (or unmerging the closed state) restores commenting.
Both layers are in scope:
Reopen restores everything: state flips back to open and the gate opens with it (the gate should key on current thread state, not a one-way latch).
Evidence (static read of current tree, no local repro)
Every write path authenticates and checks read access, then mutates without ever consulting thread state:
internal/issues/service.goAddComment(~:199) —requireAuthenticated+requireReadonly; the CAS mutator appends unconditionally regardless oft.State.internal/issues/service.goAddReaction(:609) /:678) — validate actor, target event, and emoji set (RemoveReaction(ReactionContents), never state.internal/pulls/service.goAddComment(~:992) — same shape; no check ofmergedor closed state.internal/review/threads.goOpenThread(:63),:257) — auth + read only; threads can be opened and replied to on a merged PR today.AddThreadComment(web/src/pages/Issue.jsxandweb/src/pages/Pull.jsxrender the composer unconditionally — the state chip (chip-closed/chip-mergedviapullBadgeViewinweb/src/lib/pull-state.js) is display-only;Pull.jsx:1441gates some UI onthread()?.state === "open" || pr()?.mergedbut not the composers.Net effect: closing an issue or merging a PR doesn't stop the conversation, which surprises users and lets activity events (
commented,reaction_changed) mutate the thread after it's finished.Architecture notes
threadLocked(th) bool) checked at the top ofAddComment,AddReaction,RemoveReaction(issues),AddComment(pulls), andOpenThread/AddThreadComment(review). Keep it in the service layer, not the HTTP handler, so all three route twins are covered by construction.ErrInvalid/ErrNotFoundininternal/issues/errors.go(mirrored for pulls/review), surfaced as 409 with a human-readable message the SPA can toast.EventClosedByPR/ state transitions already exist ininternal/issues/model.go;pull-state.jsalready computes merged vs plain-closed for display — reusecanModifyPullState-style client helpers (web/src/lib/pull-state.js) for the affordance gating rather than new logic.Acceptance criteria
state:"open"on a merged PR (409); regression test asserting reopen-on-merged fails server-side; client renders no reopen control for merged PRs (testpullCloseVisibilitymerged →{showClose:false, showReopen:false}stays).issue/pullstream frames so a reopen unlocks without a reload.Fixed by #603 (merged): service-layer threadLocked gates (issues/pulls/review → 409) incl. SubmitReview bypass-closure; reaction-remove stays open as personal undo; reopen-on-merged refused 409 + no client control; disabled-with-reason UI on all three pages keyed on live fetches (stream unlock, double-guarded submits); reopen restores everything. Verified: cover ≥95% all three packages, -race green, 1495 web unit green (smoke excluded, pre-existing), vite/esbuild green, independent review APPROVE.