feat: add dismissible announcement banner#404
Conversation
Add a site-wide announcement banner configured via the new `announcement` theme option (HTML allowed, so it can include emphasis and a link to changelog notes). Readers dismiss it with a ×; the dismissal persists in localStorage and is keyed to a content hash, so editing the message re-shows the banner for everyone who dismissed the previous one. An optional `announcement_expires` ISO date auto-hides the banner after that day — enforced client-side (disappears for visitors even without a rebuild) and at build time (an already-expired notice is omitted from the HTML). An invalid expiry date logs a warning and fails open. The banner defaults to empty/off, so existing sites are unchanged. The renderer iterates a list of notices internally so per-page announcements can be added additively later (tracked in #403). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #404 +/- ##
=======================================
Coverage ? 51.41%
=======================================
Files ? 2
Lines ? 459
Branches ? 0
=======================================
Hits ? 236
Misses ? 223
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a new, site-wide (theme-option driven) announcement banner to the QuantEcon Book Theme, including template markup, styling, client-side dismissal/expiry behavior, Python-side option handling, and documentation.
Changes:
- Introduces
announcement/announcement_expirestheme options and Python helpers to build the announcement render context and validate expiry dates. - Adds a server-rendered (initially hidden) announcement bar to
layout.html, with new JS to reveal/clear rows based on dismissal and expiry, plus new SCSS styling (dark mode + RTL). - Adds new docs and changelog entry, plus a new test module covering parsing, build-time behavior, and wiring.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_announcement.py | New tests covering option registration, wiring, date parsing, build-time skip, and fail-open validation. |
| src/quantecon_book_theme/theme/quantecon_book_theme/theme.conf | Registers the new theme options so Sphinx accepts them. |
| src/quantecon_book_theme/theme/quantecon_book_theme/layout.html | Renders the announcement bar/rows (hidden by default) when announcements exist. |
| src/quantecon_book_theme/assets/styles/index.scss | Forwards the new announcement SCSS partial. |
| src/quantecon_book_theme/assets/styles/_announcement.scss | New styles for the banner (including dark mode + RTL adjustments). |
| src/quantecon_book_theme/assets/scripts/index.js | Wires initAnnouncement() into the theme’s JS initialization. |
| src/quantecon_book_theme/assets/scripts/announcement.js | New JS module implementing dismissal persistence and client-side expiry. |
| src/quantecon_book_theme/init.py | Adds _parse_iso_date, _build_announcements, and validate_announcement; injects announcements into page context. |
| docs/user/index.md | Adds “announcements” to the user guide TOC. |
| docs/user/configuration.md | Documents the new theme options in the configuration reference. |
| docs/user/announcements.md | New dedicated user documentation page for behavior/details. |
| CHANGELOG.md | Adds an Unreleased changelog entry describing the feature. |
🎭 Visual Regression Test ResultsDetails
Failed testsdesktop-chrome › theme.spec.ts › Visual Regression Tests › intro - full page screenshot Skipped testsdesktop-chrome › theme.spec.ts › Visual Regression Tests › prob-matrix - full page screenshot |
Two correctness fixes from the #404 review: - announcement.js: parse the expiry `YYYY-MM-DD` with an explicit regex into local-time date components and expire at 23:59:59.999, instead of `new Date("...T23:59:59")`. Removes the ~1s-early cutoff and any cross-engine ambiguity in how the date string is parsed. - __init__.py: the build-time expiry skip compared a UTC "today" against the expiry date, while the client-side check is the reader's local end-of-day — so a rebuild just after UTC midnight could omit a banner that's still the expiry day in Americas timezones. The skip now keeps a one-day UTC grace so a notice is never dropped from the HTML while it's still "today" anywhere; the client side hides it precisely per-reader. Tests and the expiry docs updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump FIXTURES_SHA in both ci.yml and update-snapshots.yml to the fixtures commit that adds an announcement to the fixtures _config.yml, so this PR's Netlify preview and visual build show the new banner. Both workflows are kept in sync so regenerated snapshots are produced against the same input. The banner shifts every page's layout, so the visual job is expected to fail until the baselines are regenerated via /update-snapshots. See QuantEcon/quantecon-book-theme-fixtures#1 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the announcement banner's look selectable via a new announcement_style theme option, so the team can choose between two styles (and switch the default in one place): - bar (default): a thin, full-width strip with centred text that sits just below the toolbar and scrolls away — discreet, for standing notices. - callout: the original boxed in-column notice with an accent border — preserved as a style so nothing is lost. Both share the same markup, JS, and global dismissal; only the styling differs, and both adapt to dark mode and RTL. The value is validated at build start (unknown values warn and fall back to bar), mirroring color_scheme. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pull the bar-style strip up to sit flush beneath the fixed toolbar (closing the ~13px gap left by .qe-page's 4rem top padding vs the 50px+1px toolbar) so it reads as a second tier of the header, and slim it down (thinner vertical padding, slightly smaller text). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Announcement banner — two styles, which should be default?The banner now supports two looks via a new The use case: Side-by-side on the fixtures site (top =
Switching is a one-liner in Vote: I like the |
Sets html_theme_options.announcement so the dismissible announcement banner (QuantEcon/quantecon-book-theme#404) renders across the fixtures site — visible in the Netlify preview and covered by the visual regression snapshots. No announcement_expires is set on purpose, so the banner always renders and visual snapshots stay stable (an expiry date would eventually pass and silently change the build output). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
fixtures #1 merged the announcement-banner demo into the fixtures config on main. Re-pin FIXTURES_SHA in both ci.yml and update-snapshots.yml from the (now-merged) branch commit to the squash-merge commit on fixtures main, so the pin tracks a durable commit rather than a deletable branch tip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
38231f4 to
00ede48
Compare
|
/update-snapshots |
|
✅ All visual snapshots have been regenerated and committed to this PR. 📦 Download snapshot-update-diff artifact to review before/after images. |

Summary
Adds a site-wide announcement banner configured from
_config.yml(orconf.py) — for notices like a software upgrade with a link to changelog notes. Readers can dismiss it, and it can auto-expire on a date. The banner defaults to empty/off, so existing sites are unaffected.Behavior
×button hides the banner and the choice persists inlocalStorage. Dismissal is keyed to a hash of the message text, so when you editannouncementthe banner re-appears for everyone, including readers who dismissed the old one.announcement_expires(ISOYYYY-MM-DD, shown through the end of that day) hides the banner client-side so it disappears for visitors even without a rebuild, and is also skipped at build time so an already-expired notice is omitted from the generated HTML. An unparseable date logs a build warning and fails open (the banner keeps showing) so a typo never silently hides an active notice.Design note: built to be additive
Per-page announcements are intentionally not in this PR (global was the priority), but the internals are built so they can be added additively without reworking this code: the template loops over a list of notices,
announcement.jsevaluates each row independently (own content-hash dismissal + own expiry), and thelocalStoragedismissed-set already tracks multiple IDs. Tracked in #403, where the open question is whether per-page should be config-keyed or authored in MyST frontmatter.Changes
theme.conf—announcement,announcement_expires__init__.py—_build_announcements(content hash + build-time expiry skip),_parse_iso_date,validate_announcement(fail-open)layout.html— hidden bar looping over notices at the top of.qe-pageassets/scripts/announcement.js(new) + wired intoindex.jsassets/styles/_announcement.scss(new) +@forwardinindex.scsstests/test_announcement.py(new) — 24 unit testsdocs/user/announcements.md(new), configuration cross-ref, CHANGELOGTesting
webpackbuild is clean and the new JS/CSS appears in the compiled bundles (the bundles are gitignored and built at packaging time, so they are not in this diff).test_build.py) was not run locally — it needs the theme installed via its entry point, which is blocked by a pre-existing stale.nodeenv(node v18.18.0 vs the v20.18.0sphinx-theme-builderexpects). This is independent of the change and will run in CI. A separate maintenance PR will fix the local testing setup. With the default empty config the template renders nothing ({% if announcements %}), so the file-regression baseline is unchanged.🤖 Generated with Claude Code