Skip to content

feat(announcement): info/pinned/blocking levels, a closable banner, and an archive - #353

Merged
lshw54 merged 4 commits into
codefrom
feat/announcement-levels-and-history
Jul 27, 2026
Merged

feat(announcement): info/pinned/blocking levels, a closable banner, and an archive#353
lshw54 merged 4 commits into
codefrom
feat/announcement-levels-and-history

Conversation

@lshw54

@lshw54 lshw54 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

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:

Level Behaviour
info Closable at once; closing counts as read
pinned Locked for a countdown, then the × and 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.

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). parseSeenIds folds 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.

lshw54 added 2 commits July 28, 2026 02:15
…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.
@lshw54 lshw54 changed the title feat(announcement): three levels, a history dialog, and a 10s countdown for the current notice feat(announcement): info/pinned/blocking levels, a closable banner, and an archive Jul 27, 2026
lshw54 added 2 commits July 28, 2026 02:56
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.
@lshw54
lshw54 merged commit 0809efa into code Jul 27, 2026
3 checks passed
@lshw54
lshw54 deleted the feat/announcement-levels-and-history branch July 27, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[建議]更新公告等待時間過長以及能手動關閉置頂公告訊息

1 participant