Fix #109: timeline system rows + honest close #111

Merged
crueber merged 1 commit from fix/issue-109 into main 2026-09-05 04:11:03 +00:00
Owner

Fixes #109.

Timeline (web/src/components/ThreadTimeline.jsx, shared by issues + PRs): comments render as divider-separated entries (author/date header, body, reaction rows) with no per-comment boxes; the composer stays a distinct card. Every non-comment event kind renders as a single-line centered muted system row ({actor} {fragment}, e.g. anon added the "approved" label).

System text moves to web/src/lib/issue-events.js (unit-tested in web/test/unit/issue-events.test.js): sentence fragments instead of +/- sigils, and state_changed never asserts a reason the event lacks.

Honest close: the API defaults an omitted state_reason to completed (02 §7, internal/issues service.go), so it cannot mean null — on an open issue both Close and Comment-and-Close are reason choosers (Close as completed / Close as not planned); closePatch throws instead of building a reason-less body. Reopen stays a plain button; the closed header reads Closed as … from the recorded state_reason.

Verified: real browser drive (headless Chrome via CDP, canonical host) — thread with comments + label add/remove rows, close-as-not-planned, reopen (reason cleared), comment-and-close-as-completed, dark + light, zero console errors. node --test: 241 pass; data-guard + reaction-cache fail identically on origin/main (pre-existing, unrelated). No new deps. Docs: 08 §2 rows + decision bullet.

Fixes #109. **Timeline** (`web/src/components/ThreadTimeline.jsx`, shared by issues + PRs): comments render as divider-separated entries (author/date header, body, reaction rows) with no per-comment boxes; the composer stays a distinct card. Every non-comment event kind renders as a single-line centered muted system row (`{actor} {fragment}`, e.g. `anon added the "approved" label`). **System text** moves to `web/src/lib/issue-events.js` (unit-tested in `web/test/unit/issue-events.test.js`): sentence fragments instead of `+/-` sigils, and `state_changed` never asserts a reason the event lacks. **Honest close**: the API defaults an omitted `state_reason` to `completed` (02 §7, `internal/issues` service.go), so it cannot mean null — on an open issue both Close and Comment-and-Close are reason choosers (Close as completed / Close as not planned); `closePatch` throws instead of building a reason-less body. Reopen stays a plain button; the closed header reads `Closed as …` from the recorded `state_reason`. **Verified**: real browser drive (headless Chrome via CDP, canonical host) — thread with comments + label add/remove rows, close-as-not-planned, reopen (reason cleared), comment-and-close-as-completed, dark + light, zero console errors. `node --test`: 241 pass; `data-guard` + `reaction-cache` fail identically on origin/main (pre-existing, unrelated). No new deps. Docs: 08 §2 rows + decision bullet.
ThreadTimeline (08 §2): comments render as divider-separated entries
with no per-comment boxes; every non-comment event kind renders as a
single-line centered muted system row ('{actor} {fragment}'). Applies
to the shared renderer, so PR conversations match.

System text moves to web/src/lib/issue-events.js (unit-tested):
sentence fragments ('added the "approved" label'), and state_changed
never asserts a reason the event lacks ('closed' vs 'closed as …').

Honest close: the API defaults an omitted state_reason to completed
(02 §7), so on an open issue both close controls are reason choosers
('Close as completed' / 'Close as not planned', same for
Comment-and-Close); closePatch throws instead of building a
reason-less body. Reopen stays a plain button. The closed header reads
'Closed as …' from the recorded state_reason.

Browser-verified (headless Chrome via CDP): thread + label rows +
close-as-not-planned + reopen + comment-and-close-as-completed, dark
and light, zero console errors.
Sign in to join this conversation.
No description provided.