Name the talk the audience was about, not the accepted one - #325
Merged
Conversation
An audience-segmented Compose send built `{{submission.title}}` from
`submissions.find(accepted) ?? submissions[0]`, which never looks at the
audience. A speaker with an accepted talk and a declined one is in both
segments, so the "Submission declined" mailing named his accepted talk and
told him it had not made it in.
`qualifyingSubmissions` returns the submissions that put a recipient in the
audience rather than a boolean, and `mergeSubmission` picks from that set.
`matchesAudience` is now the emptiness check over the same function, so the
two can no longer disagree about which proposals the segment is about.
The session follows that submission too, so `{{session.room}}` cannot
describe one talk while the title names another.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
A speaker with an accepted talk and a declined one falls into the "Declined speakers" audience
legitimately. Sending the "Submission declined" template to that audience produced a message naming
his accepted talk and telling him it had not made it into the programme.
What was wrong
resolveRecipientsdecided who to write to and then, separately, what to say to them:spec— the audience — is nowhere in that line.matchesAudienceknew which proposals hadqualified the recipient, but it answered a boolean and threw the rest away, so the merge fields fell
back to accepted-first across everything the person owns. For anyone with exactly one submission the
two agree, which is why this reads correct until a speaker has two.
The judge verified it by contrast: the automatic per-decision notification for the same decision,
recipient and template names the right talk. It does, because
fanOutSubmissionTemplateoverridessubmission.*from the row it was called about and never consults this code. Compose has no suchoverride, and there is no third path — those two are the whole of it.
The fix
qualifyingSubmissions(spec, submissions, isScheduled)returns the submissions that put a recipientin the audience.
mergeSubmissionpicks from that set, andmatchesAudienceis now the emptinesscheck over the same function — so the filter and the merge fields cannot disagree about which
proposals a segment is about.
Three things worth naming:
list, an outstanding task: still accepted-first across the lot. Every triggered send resolves its
recipient through
manual, so nothing that was already naming the right talk changes.ref, not row order. Two declined talks in one declined-speakers sendused to resolve to whichever row the join returned first. A mailing that is resent should not
change its subject.
{{session.room}}naming one talk while{{submission.title}}names another is the same defect wearing a different field. A recipientwhose qualifying proposal is not on the agenda now gets empty session fields and no calendar
attachment, which is the truth about it, rather than another talk's room.
Verification
bun run lint,bun run typecheck,bun run test(190 files, 2046 tests) andbun run buildallpass.
bun run docs:openapiandbun run docs:mcpregenerate to no diff.Thirteen new tests over
mergeSubmissionandqualifyingSubmissions, including the reported case inone assertion, both segments resolving differently for the same speaker, and the non-submission
audiences keeping their old answer.
Not covered: the send itself.
resolveRecipientsreads six tables and the unit suite has nodatabase, so what a test here can observe is the pick, not the mail.
Part of the
2026-08-17T05-46-05evaluation cycle. Not merged, not deployed.