Lock commenting on closed issues and merged/closed PRs (Fix #594) #603
No reviewers
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 milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub!603
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/issue-594"
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?
Implements Forgejo #594 per the binding user rulings.
Server gates (service layer, all route twins covered):
Client:
Tests: table-driven Go tests per package (locked→409+message, open→passes, reopen-restores, merged-terminal incl. reopen refusal, reaction add/remove split); web unit (helpers, merged no-reopen pin, 409 toast path). Coverage ≥95% holds (issues/pulls/review at 96.0/96.2/96.0%). Web full-minus-smoke 1495/1495; vite+esbuild builds clean; web/dist/.keep restored.
Docs: Decisions + behavior notes in 02_issues.md, 03_pull_requests.md, 04_code_review.md in the same commit.
Known limitation: no live-browser drive in this environment (no CDP/Chromium); UI changes are disabled-states on existing controls with canonical Tailwind classes only (no layout changes) — suggest a mobile+desktop console check at review.
APPROVE — independent review of fix/issue-594 (
0ef124a) vs #594.Verified against the 8 scrutiny points (code-read + tests run in /tmp/walhub-594):
EXTRA SubmitReview gate — CORRECT and consistent. review/service.go:368 gates on threadLocked(h,side) = h.State!=open OR side.Merged, same predicate as OpenThread/AddThreadComment. Closed-but-unmerged is locked (ticket wants locked), reopen (state→open, merged=false) restores submit too — covered by TestThreadWritesLockedOnClosedPR reopen block. No legit flow broken: open PRs unaffected; closed/merged submits now 409 like every other conversation write. Justification in 04_code_review.md (bypass otherwise trivial) accepted.
Race safety — pulls hoisting SAFE. AddComment hoists loadPR ahead of appendEvent, but the gate runs INSIDE the CAS mutator on the fresh thread doc, so a merge landing between the two reads still trips the state arm (merge stamps StateClosed in the same header CAS). Same-read-count claim holds: the post-CAS loadPR it replaces was result-discarded (old lines ~1023-1025). Issues AddComment likewise gates inside the mutator; AddReaction checks the already-loaded header (pre-existing read); review prHeadOf header+sidecar reused (no new reads). Law 6 holds. Residual note (non-blocking): review OpenThread/AddThreadComment/SubmitReview check-then-act outside their write CASes, so a merge landing in that micro-window could slip one thread through — narrow, no extra-read fix available in-budget; pulls/issues do not share this hole.
Law 3 / law 8 / 409 / toast — clean. No new locks/mutexes in the diff; no internal/server or internal/api imports (only server/auth + store, as before). ErrLocked→409 via statusFor in all three packages (issues/errors.go, pulls/model.go, review/model.go), and all three http.go route through writePlain(statusFor(err), err.Error()) — 409, not 500. Messages are plain human text with the number only ('issue #N is closed', 'pull request #N'), no internals leaked; web 409-toast test pins verbatim tray delivery.
Reaction removal allowed — consistent, no bypass. issues RemoveReaction deliberately ungated; chips stay live while the + menu disables. Abuse check: removal requires owning the reaction (reactionState scan, else 404) and rides a CAS that only decrements the summary + appends a remove event; it does bump Version/UpdatedAt, but that is the pre-existing personal-undo write, not new-content injection. Accepted as decided.
Resolve/unresolve ungated — agree, curation. setResolved touches only the thread header (Resolved/By/At, Version/UpdatedAt), emits thread_resolved/unresolved notify+stream classes and refreshSummary — no commented/reaction_changed conversation event, so the lock spirit holds.
Client — stream unlock verified structurally, merged pin holds, double-guarded submits. Issue.jsx keys disabled off live thread() with useCollabStream(['issue','issue_event']) invalidating the key; Pull.jsx keys pullCommentLock(thread(),pr()) with useCollabStream(['pull','review','thread','check'], num-filter) — reopen flips the helper to unlocked with no reload. pullCloseVisibility merged→{false,false} first-line, test-pinned for all roles; closed-unmerged keeps Reopen. Every locked composer is double-guarded: CommentComposer (locked() early-return + disabled textarea/submit), ThreadCard reply (commentLocked return + disabled input/button), FinishReview (return + disabled submit), PullFiles submitThread (return + CommentComposer disabled).
Pin updates strictly stronger — yes. composer-cancel-row-587 and review-restyle-545 now assert the full literal including 'disabled:cursor-not-allowed disabled:opacity-50' (milestone-picker idiom); primary idiom intact, requirement strengthened, not weakened.
Coverage/docs/deps — hold. go test -cover: issues 96.0 / pulls 96.1 / review 96.0 (≥95). New tests fail pre-fix by inspection (no gate existed: AddComment/AddReaction/OpenThread/SubmitReview never consulted state; reopen-on-merged succeeded server-side). Docs amended in-commit per law 12 (02_issues, 03_pull_requests, 04_code_review with named decisions). No go.mod/web dependency changes.
No fix commits made — no blocking defects found. One suggestion for follow-up (not requested as rework): consider re-checking threadLocked inside SubmitReview's reserve loop for the stamp-in-flight window if a cheap header-state re-read is ever acceptable. Live-browser mobile+desktop console check still outstanding per PR description (no CDP here either).
Verdict: APPROVE.