Add moderator reply to event messages - #241
Open
junkerderprovinz wants to merge 7 commits into
Open
Conversation
Adds a "Reply" action to the message-moderation panel, next to the existing pin/ban/delete actions. Clicking it opens an inline composer (no page reload, no server round-trip just to show/hide the input -- that part is plain Alpine state, matching how the actions menu itself already toggles); submitting stores the reply on the same Post row (one reply per message, not a full thread) and broadcasts the update through the existing :post_updated PubSub event, which the attendee view, the presenter view and the moderation panel already all handle -- so the reply shows up live everywhere without adding a new event type. Attendees see it as a small "Moderator reply" block under the original message. New migration adds two nullable columns (reply_body, replied_at) to posts. No changes to existing behaviour when reply_body is unset. Verified end-to-end against a real running instance (compose.dev.yml, two separate browser sessions -- one as the moderator, one as an anonymous attendee): posted a question as the attendee, replied as the moderator, watched the reply appear on the attendee's screen live with no refresh. Fixes ClaperCo#234
The "reply" handle_event clause nested three case expressions, which
exceeds Credo's Refactor.Nesting limit (max depth is 2) and failed the
"Check Credo Warnings" step of the Elixir CI workflow.
Move the post lookup and the update call into a private reply/3 helper,
mirroring how the neighbouring "pin" clause delegates to pin/2.
Behaviour is unchanged: the empty-body guard still runs before the
database lookup, and every branch still returns {:noreply, socket}.
presenter.html.heex builds its own post markup instead of going through PostComponent, and that markup only printed post.body. A reply therefore reached attendee phones and the moderation panel but never the projected screen, which is usually the only place the room is looking. Add the reply block to both the post list and the pinned-post list, sized off @iFrame like the surrounding presenter markup.
Submitting a blank reply was a silent no-op, and a failing reply_to_post/2 changeset was swallowed just as quietly, so a moderator got no feedback either way and could not tell a delivered reply from a dropped one. Report both with put_flash(:error, ...), the way the other failure paths in this module already do.
Three small corrections to the reply the panel renders:
- the composer <form> sat outside the :if={!@readonly} guard, so it was
emitted (inert, x-show hidden) on the read-only stats page too;
- "Your reply" is wrong as soon as a different co-moderator wrote it, so
it now reads "Moderator reply", matching the attendee and presenter
views and reusing a string that already exists;
- replied_at was written but never read anywhere, so it now shows next to
the label as HH:MM, formatted like the post timestamp above it.
Adds reply_to_post/2 context tests next to the update_post/2 pair, and LiveView tests for replying from the moderation panel, for the blank reply now being reported rather than dropped, and for the reply reaching the presenter display.
Ran `mix gettext.extract --merge`, so default.pot and every locale's default.po pick up the strings this feature adds. They land untranslated and fall back to the English source until a translation pass, matching how strings arrived in previous feature PRs. The run also refreshes `#:` source references across the catalogues, which had drifted from the current source tree before this branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#234 asks for a moderator "reply" option on event chat messages.
What this adds
A "Reply" action in the message-moderation panel, next to the existing pin/ban/delete. Clicking it opens an inline composer (client-side Alpine state, no server round-trip just to show or hide the input, the same pattern the actions menu itself already uses). Submitting stores the reply on the same
Postrow (one reply per message, not a full thread) and broadcasts through the existing:post_updatedPubSub event, which the attendee view, presenter view and moderation panel all already handle, so the reply shows up live everywhere without a new event type.Attendees see it as a small "Moderator reply" block under the original message, and the projected presenter display shows the same block under the message it answers.
New migration adds two nullable columns (
reply_body,replied_at) toposts. No change to existing behavior whenreply_bodyis unset.Defects found and fixed on review
A second pass over my own branch turned up one real gap and several rough edges.
The reply never reached the projected screen.
presenter.html.heexbuilds its own post markup rather than going throughPostComponent, and that markup only printedpost.body. So a reply reached attendee phones and the moderation panel but never the shared display, which is usually the one screen the whole room is actually looking at. The reply block is now rendered in both the post list and the pinned-post list there, sized off@iframelike the surrounding presenter markup.Failures were silent. Submitting a blank reply was a no-op with no feedback, and a failing
reply_to_post/2changeset was swallowed just as quietly, so a moderator could not tell a delivered reply from a dropped one. Both now report throughput_flash(:error, ...), matching the other failure paths inmanage.ex.Three corrections to the panel's reply block. The composer
<form>sat outside the:if={!@readonly}guard, so it was emitted (inert, hidden byx-show) on the read-only stats page too, and is now inside it. The label read "Your reply", which is wrong as soon as a different co-moderator wrote it, so it now reads "Moderator reply", consistent with the attendee and presenter views and reusing a string that already exists. Andreplied_atwas written but never read anywhere, so it now appears next to that label asHH:MM, formatted like the post timestamp directly above it.Translations
Ran
mix gettext.extract --merge, sodefault.potand every locale'sdefault.popick up the five new strings. They land untranslated and fall back to the English source until a translation pass, matching how strings arrived in previous feature PRs such as #214. Note that the same run also refreshes#:source references across the catalogues, which had drifted from the current source tree before this branch, so the diff on those files is larger than the five new strings alone.Not included
Verification
mix testgives 342 tests, 0 failures, run just now against this branch at its current head, in a container matching.github/workflows/elixir.yml(Elixir 1.18.4 / OTP 28, postgres:15, same env).mix format --check-formattedandmix credo diff --from-git-merge-base origin/mainare both clean. The earlier "336/336" in this description was the count before this round of fixes added tests.The branch previously had no tests at all for the feature. It now has
reply_to_post/2context tests next to the existingupdate_post/2pair, plus LiveView tests for replying from the moderation panel, for the blank reply being reported rather than dropped, and for the reply reaching the presenter display. The last two fail against the unfixed branch, which is how the two bugs above were confirmed rather than assumed.End to end against a real running instance, two separate browser sessions (one moderator, one anonymous attendee): posted a question as the attendee, replied as the moderator, watched the reply appear on the attendee's screen live with no refresh.
On CI
For transparency, since this PR previously quoted a test number that upstream CI never confirmed: the Elixir CI run on this PR from 2026-08-16 failed at "Check Credo Warnings" and its "Run tests" step was skipped, so the suite never ran upstream at all.
6c5ce05addressed those Credo findings. The run queued against the current head is sitting inaction_required, GitHub's approval gate for workflow runs on pull requests from forks, so it needs a maintainer to approve it before it will execute. Every number above comes from my own container run, not from upstream CI.Fixes #234