feat(announcements): open the full announcement from the banner text - #987
Merged
Merged
Conversation
The banner pill renders no body, so its only affordances were a ✕ and an optional external CTA — which trains people to dismiss unread, with What's New (buried in the user menu) as the only other route to the content. The text is now a button that opens the announcement's own dialog, and the dialog owns the ack from there: any of its exits writes `dismissed` (or `acknowledged`) and retires the pill durably. Having read the body is a stronger signal of consumption than clicking ✕ on a one-line strip, and leaving the pill up afterwards would only ask the user to dismiss something they have already dealt with. It opens the single-announcement dialog rather than the What's New list deliberately: the pill named one thing, and handing back a list to search through is a worse answer than the thing itself. Three supporting changes: - `AnnouncementModalData.sourceSurface` (default `modal`) attributes every ack to where the gesture happened, so reach stats can tell a banner that earned a read from an interruption nobody asked for. No backend change: `AnnouncementSurface` is a plain literal and the ack route never cross-checks it against the announcement's own `surfaces`. - `AnnouncementModalService.openFor()` is the one entry point for a user-initiated open. It skips the §D8 turn-safety gate — a click is not an interruption — but still respects `openRef`, and marks the item `shown` so the auto-modal cannot re-interrupt with something already read. - A `requiresAck` announcement gets no ✕ on the pill. Suppression is rank-based and covers the banner *and* modal slots alike, so a ✕ there let a user retire a compliance notice before the blocking modal ever fired, leaving no `acknowledged` record anywhere. The text button uses an `sr-only` prefix rather than an `aria-label`: the visible line may be the summary, so a title-derived label would leave the visible words outside the accessible name (WCAG 2.5.3, Label in Name). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merged
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.
The announcement banner pill renders no body, so its only affordances were a ✕ and an optional external CTA — which trains people to dismiss unread, with What's New (buried in the user menu) as the only other route to the content.
The pill's text is now a button that opens the announcement's own dialog, and the dialog owns the ack from there: any of its exits writes
dismissed(oracknowledged) and retires the pill durably, on every device. Having read the body is a stronger signal of consumption than clicking ✕ on a one-line strip, and leaving the pill up afterwards would only ask the user to dismiss something they have already dealt with.It opens the single-announcement dialog rather than the What's New list deliberately: the pill named one thing, and handing back a list to search through is a worse answer than the thing itself.
Supporting changes
AnnouncementModalData.sourceSurface(defaultmodal) attributes every ack to where the gesture happened, so reach stats can tell a banner that earned a read from an interruption nobody asked for. No backend change is needed —AnnouncementSurfaceis a plain 3-value literal and the ack route never cross-checks it against the announcement's ownsurfaces. The funnel counters key on action alone, so attribution cannot distort them.AnnouncementModalService.openFor()is now the one entry point for a user-initiated open, and dialog construction lives in a single private method soopenRefcannot drift. It skips the §D8 turn-safety gate — a click is not an interruption, the user is the one asking — but still refuses to stack on an open dialog, and marks the itemshownso the auto-modal cannot re-interrupt with something already read.disableClosestill tracksrequiresAckeven when the user opened it themselves.requiresAckannouncement gets no ✕ on the pill. This closes a real hole: dismissal suppression is rank-based and covers the banner and modal slots alike, so a ✕ on the strip let a user retire a compliance notice before the blocking modal ever fired — leaving noacknowledgedrecord anywhere. On those, the only way out is to open it and press the button.Accessibility
The text button uses an
sr-onlyprefix span rather than anaria-label. The visible line may be thesummary, so a title-derived label would leave the visible words outside the accessible name — a WCAG 2.5.3 (Label in Name) failure for voice-control users. The untruncated headline stays reachable viatitle.Testing
25 new cases across the banner, modal, and modal-service specs, including the ack-attribution matrix, the
requiresAck✕ suppression, andopenForbypassing the gate while still respectingopenRef. Full SPA suite: 2510 passed;tsc --noEmitclean.Not browser-verified —
SKIP_AUTH=falselocally, so this needs a manual pass. Worth eyeballing: truncation and left-alignment of the text button at ~500px width, the focus ring, and that the banner stays visible behind the dialog until the ack lands.Spec updated:
docs/specs/feature-announcements.md§D1 (banner) and §D2 (the ack'ssurfacerecords where the gesture happened, not which surface owns the announcement).🤖 Generated with Claude Code