feat(messaging): quote-reply with inline chip #89

Merged
nrupard merged 2 commits from feat/quote-reply into main 2026-05-13 21:44:07 +02:00
Owner

Summary

  • Adds a per-message Quote action that drops a "Replying to ..." chip into the composer; the next send writes a top-level message with a quote_id reference and renders an inline chip above its body linking back to the original.
  • Distinct from threads: quote-replies stay in the main timeline rather than being collapsed into the side panel. Application enforces parent_id and quote_id are mutually exclusive (no quoting from inside a thread, no quoting a thread reply).
  • New migration 0020 adds messages.quote_id (FK ON DELETE SET NULL) and a partial index.
  • views::room::build_quote_preview(_bulk) resolves a quote_id to author label + single-line 140-char excerpt; bulk variant issues one IN-clause SELECT plus one batched display-names lookup so page and DM renders stay O(1) on quotes.
  • All render paths populated: page render, DM render, single-message render, thread-panel parent render, render_new_message, render_edited_message.

Test plan

  • just check clean
  • new views::room::tests for excerpt newline/truncate/no-pre-escape behavior pass
  • manually quote-reply in a room; chip renders above body, click jumps to anchor
  • cancel the chip in the composer; next send has no quote_id
  • Quote button hidden on thread replies (no chip rendered there)
  • soft-delete a quoted message; chip becomes "(quoted message was deleted)"

🤖 Generated with Claude Code

## Summary - Adds a per-message Quote action that drops a "Replying to ..." chip into the composer; the next send writes a top-level message with a `quote_id` reference and renders an inline chip above its body linking back to the original. - Distinct from threads: quote-replies stay in the main timeline rather than being collapsed into the side panel. Application enforces `parent_id` and `quote_id` are mutually exclusive (no quoting from inside a thread, no quoting a thread reply). - New migration 0020 adds `messages.quote_id` (FK `ON DELETE SET NULL`) and a partial index. - `views::room::build_quote_preview(_bulk)` resolves a `quote_id` to author label + single-line 140-char excerpt; bulk variant issues one `IN`-clause `SELECT` plus one batched display-names lookup so page and DM renders stay O(1) on quotes. - All render paths populated: page render, DM render, single-message render, thread-panel parent render, `render_new_message`, `render_edited_message`. ## Test plan - [x] `just check` clean - [x] new `views::room::tests` for excerpt newline/truncate/no-pre-escape behavior pass - [ ] manually quote-reply in a room; chip renders above body, click jumps to anchor - [ ] cancel the chip in the composer; next send has no `quote_id` - [ ] Quote button hidden on thread replies (no chip rendered there) - [ ] soft-delete a quoted message; chip becomes "(quoted message was deleted)" 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(messaging): quote-reply with inline chip above body
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 11s
c376369685
Adds a per-message Quote action that drops a "Replying to ..." chip into the composer; the next send writes a top-level message with a quote_id reference and renders an inline chip above its body that links back to the original. Distinct from threads: the reply still lives in the main timeline rather than being collapsed into the side panel.

- New migration 0020 adds messages.quote_id (FK ON DELETE SET NULL) and a partial index. Mutually exclusive with parent_id at the application layer: replies inside threads cannot quote.
- db::chat::insert_message_quoted carries the optional quote_id; the existing insert_message stays as a thin wrapper so every test that posts a message keeps compiling unchanged.
- views::room::build_quote_preview(_bulk) resolves a quote_id to author label plus a single-line excerpt (140 chars, ellipsis past that). The bulk variant issues one IN-clause SELECT plus one batched display-names lookup so page renders and DM renders both stay O(1) on quotes.
- Page render, DM render, single-message render, thread-panel parent render, ws::render_new_message, and ws::render_edited_message all populate quote_preview so the chip is consistent across initial paint, edit broadcasts, and live new-message fanout.
- Composer gains a hidden #composer-quote-bar slot the Quote button hx-gets into; the chip carries a hidden quote_id input so the next submit picks it up. The form's after-request handler clears the slot on success.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(messaging): rename hover actions Quote/Reply to Reply/Thread
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 11s
3db7928ca3
Now that the in-room quote-reply lives in the main timeline, calling it "Quote" while the thread side-panel action is "Reply" is backwards: most users default to replying inline, and only escalate to a thread for longer back-and-forth. Swap the labels so the inline action reads "Reply" and the thread-panel action reads "Thread".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nrupard deleted branch feat/quote-reply 2026-05-13 21:44:07 +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!89
No description provided.