Add in-app announcement banner with i18n feed - #2
Merged
Conversation
Polls a configurable URL every 10 minutes and displays the latest message as a dismissible banner in the main alerts area. Users can dismiss individual messages (persisted in localStorage) or opt out globally (persisted in config.yaml). Includes install cutoff to hide pre-install messages for fresh installs, timed release via published_at, optional expiration via show_until, and XSS protection (HTML-escaped body, https-only links and CTA).
Replace flat title/body fields with a translations map keyed by locale. The frontend picks the user's locale via getLocale(), falling back to the message's default_locale and then to any available translation. CTA URL can be overridden per translation. Review fixes on top of the initial feature: - Schema backward compat: accept legacy flat title/body/cta_label and normalize them into a synthesized single-locale translation. - Timed release in the fetcher: pick the first active message instead of the first well-formed one, so a pre-scheduled message never shadows an active message lower in the feed. - Setup-mode readiness: allow GET /api/announcements through the requireReady middleware, and retry the first fetch faster on the frontend (30s, capped) so the banner appears shortly after setup completes instead of waiting a full 10-minute poll. - Concurrency: guard announcer/announcerCancel with an RWMutex; add announcerSnapshot()/stopAnnouncer() helpers. Adds i18n keys in en.json and fr.json for the dismiss/opt-out labels; other locales fall back to English via the existing i18n mechanism. Adds Go tests covering active-window filtering, legacy schema normalization, cache preservation on 404, and end-to-end fetch.
SEObserver
force-pushed
the
feature/in-app-announcements
branch
from
April 23, 2026 20:20
91f8e2c to
fc26635
Compare
6 tasks
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
translationsmap (locale → {title, body, cta_label, cta_url}); falls back todefault_locale, then any translation.published_atandshow_until); legacy flat schema still accepted for forward/backward compat.Notes
GET /api/announcementsis whitelisted during setup mode so the banner surfaces right after onboarding without waiting a full poll.crawlobserver.com/announcements/feed.json(Cloudflare Pages, 5min cache).Test plan
go test ./internal/announcements/...— 10 tests covering active-window filtering, legacy schema, 404 cache preservation, empty-feed retractionannouncements.enabled = falsein config.yaml, poll stops