feat(dm): system-event rows, grouping window, header icons, call end-state feedback (LC-433) #437

Merged
longjacksonle merged 1 commit from feat/dm-view-call-feedback into main 2026-06-22 01:17:20 +02:00

DM conversation view UI/UX + a call end-state signaling fix (refs data/ref-dm.png, data/ref-call-modal.png). Closes LC-433 (sub-tasks LC-434..LC-438).

1+2 - Call/system events as timeline rows (LC-434)

is_system messages ("started a call.", "Call transcript saved") were rendered with {{ message.body }} raw, so the transcript link showed literal markdown and the rows floated as plain right-aligned text. They now render as a centered, muted event row (icon + actor + event + time, like the day divider). The transcript body is parsed inline (new MessageView::system_html / render_system_inline) into a real anchor; only internal /paths are linkified and both parts are HTML-escaped.

3 - Grouping window 15 -> 5 min (LC-435)

MESSAGE_GROUPING_WINDOW_SECONDS 900 -> 300, the shared is_follow_up_of predicate used by both room + DM. Same-author messages sent minutes/hours apart now break into a fresh block (header + avatar + timestamp); rapid bursts still group tightly (py-0 within a group, py-2 between). Grouping tests updated.

4 - DM header consistency (LC-436)

Mute + Block now match the call/video/devices icon-button treatment. Mute is a bell / slashed-bell toggle (the sr-only checkbox keeps the existing hx-post mute wiring; :has(input:checked) swaps the icon and tints it); Block is a ban icon button with a tooltip.

5 - Incoming-call modal polish (LC-437)

Caller avatar above the name, theme-token surfaces + Accept/Decline (works in dark), and Escape now declines the ring (keyboard-dismissable) in addition to the existing focus trap. No overhaul.

6 - Call end-states no longer silent (LC-438)

  • Server logs decline/cancel as call-event rows ("declined the call." / "cancelled the call.") so both members keep a thread record (mirrors "started a call.").
  • Caller: a toast "{peer} declined the call" on reject (plus the existing status).
  • Callee: a toast "Missed call from {peer}" when the caller cancels while ringing.
  • A 45s no-answer timeout ends a ringing outgoing call with "No answer" (signals cancel, so the callee still gets the missed-call record).
  • decline already signaled reject; cancel already signaled cancel; both now have feedback + a record on both sides.

New i18n: js-call-declined-by / js-call-missed-from / js-call-no-answer (en + es). New tests: render_system_inline (transcript link parse, escaping, external-link rejection) + the 5-min grouping window. just check, just test, just test-saas pass; call.js syntax-checked; Tailwind rebuilt. (Dev server is SSO-gated, so a click-through on the PR build - light + dark - is worthwhile, especially the call decline/cancel/no-answer paths.)

🤖 Generated with Claude Code

DM conversation view UI/UX + a call end-state signaling fix (refs data/ref-dm.png, data/ref-call-modal.png). Closes LC-433 (sub-tasks LC-434..LC-438). ## 1+2 - Call/system events as timeline rows (LC-434) is_system messages ("started a call.", "[Call transcript saved](/transcripts/N)") were rendered with `{{ message.body }}` raw, so the transcript link showed literal markdown and the rows floated as plain right-aligned text. They now render as a centered, muted event row (icon + actor + event + time, like the day divider). The transcript body is parsed inline (new `MessageView::system_html` / `render_system_inline`) into a real anchor; only internal `/paths` are linkified and both parts are HTML-escaped. ## 3 - Grouping window 15 -> 5 min (LC-435) `MESSAGE_GROUPING_WINDOW_SECONDS` 900 -> 300, the shared `is_follow_up_of` predicate used by both room + DM. Same-author messages sent minutes/hours apart now break into a fresh block (header + avatar + timestamp); rapid bursts still group tightly (`py-0` within a group, `py-2` between). Grouping tests updated. ## 4 - DM header consistency (LC-436) Mute + Block now match the call/video/devices icon-button treatment. Mute is a bell / slashed-bell toggle (the sr-only checkbox keeps the existing hx-post mute wiring; `:has(input:checked)` swaps the icon and tints it); Block is a ban icon button with a tooltip. ## 5 - Incoming-call modal polish (LC-437) Caller avatar above the name, theme-token surfaces + Accept/Decline (works in dark), and Escape now declines the ring (keyboard-dismissable) in addition to the existing focus trap. No overhaul. ## 6 - Call end-states no longer silent (LC-438) - Server logs decline/cancel as call-event rows ("declined the call." / "cancelled the call.") so both members keep a thread record (mirrors "started a call."). - Caller: a toast "{peer} declined the call" on reject (plus the existing status). - Callee: a toast "Missed call from {peer}" when the caller cancels while ringing. - A 45s no-answer timeout ends a ringing outgoing call with "No answer" (signals cancel, so the callee still gets the missed-call record). - decline already signaled `reject`; cancel already signaled `cancel`; both now have feedback + a record on both sides. New i18n: js-call-declined-by / js-call-missed-from / js-call-no-answer (en + es). New tests: `render_system_inline` (transcript link parse, escaping, external-link rejection) + the 5-min grouping window. `just check`, `just test`, `just test-saas` pass; call.js syntax-checked; Tailwind rebuilt. (Dev server is SSO-gated, so a click-through on the PR build - light + dark - is worthwhile, especially the call decline/cancel/no-answer paths.) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(dm): system-event rows, 5-min grouping, header icons, call end-state feedback (LC-433)
All checks were successful
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 6s
check-secrets / TruffleHog (pull_request) Successful in 10s
check-secrets / Kingfisher (pull_request) Successful in 11s
Check / clippy + fmt + tests (pull_request) Successful in 5m43s
Create release / Create release from merged PR (pull_request) Has been skipped
8dc799e44d
DM conversation polish + a call end-state signaling fix.

LC-434 - Call/system events render as a centered, muted timeline row (icon + actor + event + time, like the day divider) instead of right-floating plain text. The transcript-saved "[label](url)" body is parsed inline (new MessageView::system_html / render_system_inline) into a real anchor instead of literal markdown; only internal /paths are linkified and everything is escaped.

LC-435 - Message grouping window narrowed 15 min -> 5 min (MESSAGE_GROUPING_WINDOW_SECONDS=300, shared by room + DM). Same-author messages sent far apart now break into a fresh block with header + avatar + timestamp; rapid bursts still group tightly (py-0 within a group, py-2 between). Grouping tests updated.

LC-436 - DM header Mute + Block now match the call/video/devices icon buttons. Mute is a bell/slashed-bell toggle (sr-only checkbox keeps the hx-post wiring, :has(input:checked) swaps the icon + tints); Block is a ban icon button with a tooltip.

LC-437 - Incoming-call dialog: caller avatar above the name, theme-token surfaces/buttons (works in dark), and Escape now declines (keyboard-dismissable) on top of the existing focus trap.

LC-438 - No call end-state is silent anymore. The server logs decline/cancel as call-event rows ("declined the call." / "cancelled the call.") so both parties keep a thread record (mirrors "started a call."). The caller gets a toast "{peer} declined the call" on reject; the callee gets "Missed call from {peer}" when the caller cancels while ringing; and a 45s no-answer timeout ends a ringing outgoing call with "No answer" (signals cancel, so the callee still gets the missed-call record).

New i18n: js-call-declined-by / -missed-from / -no-answer (en + es). New tests: render_system_inline (transcript link, escaping, external-link rejection) + the updated grouping window. just check, just test, just test-saas pass; call.js syntax-checked; Tailwind rebuilt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/dm-view-call-feedback 2026-06-22 01:17:20 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!437
No description provided.