feat(announcement): info/pinned/blocking levels, a closable banner, and an archive - #353
Merged
Merged
Conversation
…wn for the current notice Announcements were a single hard-wired notice with one global countdown. They are now a registry with per-entry rules: - info — pops once until acknowledged, closable immediately - forced — pops once until acknowledged, own forcedSeconds gate - forcedEveryTime — pops on EVERY launch, never recorded as read Which one pops is decided by pendingAnnouncement(), so publishing a notice is an edit to src/constants/announcement.ts, never to the component. Old entries stay in the list because the history reads from it. - The shipped #323 dual-line notice becomes level 'forced' with a 10-second countdown (was a global 30) — a per-announcement knob, so the next notice can choose its own. - Announcement history dialog: every notice ever published with its read state, any of them re-readable without a countdown. Reachable from the title-bar banner and from Settings -> Maintenance (the shared services/announcementUi opener lets any page raise it). - Acknowledgements are now a SET of ids under announcementSeenIds (Config.xml + localStorage). parseSeenIds folds in both the old single-id value and the even older app-version value, so the format change re-forces nobody. - The card renders titleKey/bodyKeys/links from the registry; the dual-line two-track body is kept as an opt-in layout for that entry. - i18n: level labels + history strings in all three locales; the key-usage guard learns announcement.* is consumed dynamically. Tests: registry/migration specs rewritten (13 cases) and modal specs reworked for per-announcement countdowns plus the history dialog. 679 frontend tests, typecheck, lint, prettier and build pass.
…an archive that opens what you asked for
Reworked after review — the previous interaction was the problem: the
strip could not be dismissed, and clicking it opened a LIST before the
text, so reading a notice took two layers.
Levels, least to most insistent:
- info closable at once; closing counts as read
- pinned locked for a countdown, then the × and the backdrop both
work; closing counts as read
- blocking same countdown, but only the acknowledge button counts as
read — any other close brings it back next launch
The dual-track notice drops to info. It was a 30-second lock with no
way out but the button; most people have read it by now, and holding
everyone for half a minute again earns nothing.
Interaction:
- The banner opens the notice ITSELF, one click, one layer — and gains
a × that dismisses the strip for good (recorded per announcement, so
a later notice brings its own strip back).
- That is only safe because the text stays reachable, so the archive
lists every notice ever published: subject, date, chevron, open the
one you want. Laying them all out in full made a single notice a wall
of text and would only get worse as they accumulate.
- The Settings entry moves from the maintenance tools at the bottom to
the top of the App section — a notice buried at the bottom is one
nobody finds.
- The overlay gains a × and a backdrop close, both gated by the
countdown, both routed through closingCountsAsRead so can
tell an acknowledgement from an escape.
The body moves out of the overlay into AnnouncementBody, keyed by id,
so the overlay and the archive render exactly the same thing. A future
announcement is a case there plus a registry entry.
680 frontend tests (29 rewritten across registry + modal), typecheck,
lint, prettier and build pass.
The page put unrelated things side by side: a two-column grid with buttons, three selects and a checkbox down the left and four unrelated toggles down the right, under a section header that was just the app name. The Classic NGM path sat under 'maintenance', which it is not. Regrouped into cards that each answer one question: - Account — manage accounts, announcements (things you DO here) - General — update channel + prompt, language, login mode - Appearance— theme colour, dark mode - Behaviour — launch after login, minimise to tray, disable hw accel - Game — unchanged, still per-selected-game - Classic — the NGM path, out of maintenance where it never belonged - Maintenance — window reset + WebView2 cache, last: you only come here when something is wrong Compactness, after the first pass stacked eight full-width cards into one very tall column: - the container tiles cards two-up (auto-fill, so a narrow window keeps a single column instead of squeezing), with the header, the game section and the empty-state note spanning the full width - rows put label and control on ONE line; the stacked form gave every select two lines and doubled each card's height for nothing - Back moves from a lone footer into the page header beside the title, where it reads as part of the page rather than a stray control No behaviour, config keys or data-test hooks changed. Section titles added to all three locales. 680 frontend tests, typecheck, lint, prettier and build pass.
…pped Verified in the running app over CDP, which is how these were found: - The selects collapsed to just their chevron: the compact row rule set `width: auto` with no flex basis, so they shrank to min-content. They now take the space the label leaves, with a 128px floor. - The Classic NGM row overflowed its card (the browse button hung outside): that row carries an input plus two buttons, so the section is full width and the row may wrap. - Cards overlapped: CSS `columns` + `column-span: all` + `break-inside: avoid` mis-measures in Chromium, and the tall launch-behaviour card ran under the full-width game note. Replaced with two explicit flex stacks — each card is exactly as tall as its content, so neither grid-row holes nor spanning overlaps are possible, and a narrow window wraps to one stack. - Maintenance is full width too, so its buttons sit side by side and its tip stops wrapping mid-sentence. Checked programmatically after the change: pairwise card intersection is empty, no element exceeds its card, selects measure 165px. 680 frontend tests, typecheck, lint, prettier and build pass.
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.
What
Announcements were a single hard-wired notice: one global 30-second lock, no way out but the button, and no way back to the text afterwards except a strip you couldn't dismiss.
Levels
Three, least to most insistent — publishing a notice is now a matter of picking one:
infopinnedblockingThe dual-track notice drops to
info. It was a 30-second lock with no way out but the button; most people have read it by now, and holding everyone for half a minute again earns nothing.Closable banner, and an archive to make that safe
The banner gets a × that closes it for good (recorded per announcement, so a later notice brings its own strip back), and clicking the strip now opens the notice itself — one click, one layer, instead of a list you had to get past first.
That's only reasonable if the text stays reachable, so announcements get an archive: subject, date, chevron, open the one you want. Laying them all out in full made a single notice a wall of text and would only get worse as they accumulate. Its entry moves from the maintenance tools at the bottom of Settings to the top of the App section — a notice buried above "about" isn't one anyone will find.
Shared body
The text moves out of the overlay into
AnnouncementBody, keyed by id, so the overlay and the archive render exactly the same thing. A future announcement is a case there plus a registry entry.Read state
Acknowledged ids are a set under
announcementSeenIds(Config.xml + localStorage; either store counts).parseSeenIdsfolds in the previous single-id value and the older app-version value, so the format change re-forces nobody. Banner dismissals live in their own key, since hiding a strip is a display choice, not a claim to have read anything.Tests / checks
29 specs across the registry (level semantics, migration, pending selection) and the modal (info-level close paths incl. × and backdrop, banner dismissal persistence, archive rows carrying subject + date, re-reads not re-recording). 680 frontend tests, typecheck, eslint, prettier and production build pass. No Rust changes.