{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "name": "@undrr/undrr-mangrove",
  "version": "2.0.0",
  "description": "Machine-readable release history, tag diffs, and component changelogs for UNDRR Mangrove.",
  "_note": "100% automated by the build. Generated from CHANGELOG.md and component MDX documentation stories. Do not edit manually.",
  "_links": "Pull requests, issues, tags and GitHub Releases are on unisdr/undrr-mangrove, where changes are merged; it is not publicly readable, so they are given as plain-text references (prRef, such as unisdr/undrr-mangrove#1234, and tag) with no URL, in the text fields as well. Do not look these numbers up on the public fork, whose numbering is separate. Each release's changelogUrl points at its section of CHANGELOG.md on the public fork, https://github.com/PreventionWeb/undrr-mangrove.",
  "generatedAt": "2026-09-30T10:20:52.476Z",
  "urls": {
    "releases": "https://mangrove.undrr.org/releases.json",
    "changelog": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md",
    "repository": "https://github.com/unisdr/undrr-mangrove",
    "publicRepository": "https://github.com/PreventionWeb/undrr-mangrove",
    "releaseNotesV2": "https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs"
  },
  "latest": {
    "version": "2.0.0",
    "date": "2026-09-24",
    "isPrerelease": false,
    "tag": "v2.0.0",
    "tagUrl": null,
    "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-2026-09-24",
    "releaseUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-2026-09-24",
    "summary": "The stable release of Mangrove 2.0, published on the npm `latest` tag. The [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)) describe 2.0 as a whole, with an upgrade checklist, the numbered breaking changes and upgrade guidance from 1.x. 1.x is now in best-effort support, with no schedule for further releases. Earlier sections of this file record each prerelease. Prereleases: 2.0.0-alpha.1 (unisdr/undrr-mangrove#1061), 2.0.0-alpha.2, 2.0.0-alpha.3, 2.0.0-alpha.4, 2.0.0-beta.1, 2.0.0-beta.2 (tagged, not published to npm), 2.0.0-beta.3, 2.0.0-rc.1, 2.0.0-rc.2 and 2.0.0-rc.3.",
    "changesCount": 4,
    "changes": [
      {
        "category": "Changes since 2.0.0-rc.3",
        "title": "HubHeader vanilla runtime:",
        "description": "New `js/hub-header.js` enhances static hub header markup without React, scrolling the current section into view on load and driving the edge fades; with `data-mg-hub-header-detect-current` it marks the current section from the URL. The React `HubHeader` runs the same script. (unisdr/undrr-mangrove#1292, unisdr/undrr-mangrove#1293)",
        "pr": 1292,
        "prRef": "unisdr/undrr-mangrove#1292",
        "prUrl": null,
        "raw": "**HubHeader vanilla runtime:** New `js/hub-header.js` enhances static hub header markup without React, scrolling the current section into view on load and driving the edge fades; with `data-mg-hub-header-detect-current` it marks the current section from the URL. The React `HubHeader` runs the same script. (unisdr/undrr-mangrove#1292, unisdr/undrr-mangrove#1293)"
      },
      {
        "category": "Changes since 2.0.0-rc.3",
        "title": "PageHeader language links fall back to the dropdown:",
        "description": "`languageDisplay=\"links\"` renders the icon dropdown below the tablet breakpoint and when there are more languages than the new `maxLanguageLinks` prop (default 6), replacing the two-letter codes that wrapped under the logo. Default and example language lists follow United Nations order, alphabetical by English name. (unisdr/undrr-mangrove#1284)",
        "pr": 1284,
        "prRef": "unisdr/undrr-mangrove#1284",
        "prUrl": null,
        "raw": "**PageHeader language links fall back to the dropdown:** `languageDisplay=\"links\"` renders the icon dropdown below the tablet breakpoint and when there are more languages than the new `maxLanguageLinks` prop (default 6), replacing the two-letter codes that wrapped under the logo. Default and example language lists follow United Nations order, alphabetical by English name. (unisdr/undrr-mangrove#1284)"
      },
      {
        "category": "Changes since 2.0.0-rc.3",
        "title": "Changelog entries use a comma between version and date:",
        "description": "Component MDX entries are now written `- **X.Y.Z**, YYYY-MM-DD ([#PR](url)): Description` and release headings here `## X.Y.Z, YYYY-MM-DD`; every existing entry was converted, and em dashes in changelog prose were replaced with commas, colons and parentheses. The changelog parser reads both the new and the old dash form. It also fixes a bug: 27 SyndicationSearchWidget entries that already used a comma were published in `releases.json` with no date. GitHub heading anchors for release headings in this file change as a result (for example `#200-rc3--2026-09-20` is now `#200-rc3-2026-09-20`). The [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs) now covers dash usage: en dashes where United Nations style uses them, no em dashes and no dashes as separators. (unisdr/undrr-mangrove#1289, unisdr/undrr-mangrove#1290)",
        "pr": 1289,
        "prRef": "unisdr/undrr-mangrove#1289",
        "prUrl": null,
        "raw": "**Changelog entries use a comma between version and date:** Component MDX entries are now written `- **X.Y.Z**, YYYY-MM-DD ([#PR](url)): Description` and release headings here `## X.Y.Z, YYYY-MM-DD`; every existing entry was converted, and em dashes in changelog prose were replaced with commas, colons and parentheses. The changelog parser reads both the new and the old dash form. It also fixes a bug: 27 SyndicationSearchWidget entries that already used a comma were published in `releases.json` with no date. GitHub heading anchors for release headings in this file change as a result (for example `#200-rc3--2026-09-20` is now `#200-rc3-2026-09-20`). The [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs) now covers dash usage: en dashes where United Nations style uses them, no em dashes and no dashes as separators. (unisdr/undrr-mangrove#1289, unisdr/undrr-mangrove#1290)"
      },
      {
        "category": "Changes since 2.0.0-rc.3",
        "title": "New editorial style guide, and a topic-scoped AI manifest to go with it:",
        "description": "`docs/EDITORIAL-MANUAL.md` documents mechanical style rules: capitalization, punctuation, numbers and dates, abbreviations, italics, spelling, UNDRR-specific terminology (Sendai Framework target/priority/indicator naming, GAR and campaign naming, gender language), disability inclusive terminology (person-first language and a recommended-vs-avoid term table), and gender-bias review technique (avoiding gendered-default constructions, \"flip it to test it\"). Each rule is cited to its source: the internal UNDRR Publications SOP, the United Nations Disability-Inclusive Communications Guidelines, the DPI Gender Checklist for Content Creators, the UN Geneva Web Style Guide or the United Nations Editorial Manual, in that priority order where they conflict. Rendered in Storybook under **Contributing/Editorial manual**. The AI manifest pipeline gains a new, genuinely separate output, `llms-editorial-manual.txt`, so an agent generating UI copy can fetch just the writing/style rules instead of the full component manifest; linked from `llms.txt`, `llms.json` (`urls.editorialManual`) and `docs/AI-MCP-INTEGRATION.md`. (unisdr/undrr-mangrove#1286, unisdr/undrr-mangrove#1287, unisdr/undrr-mangrove#1288)",
        "pr": 1286,
        "prRef": "unisdr/undrr-mangrove#1286",
        "prUrl": null,
        "raw": "**New editorial style guide, and a topic-scoped AI manifest to go with it:** `docs/EDITORIAL-MANUAL.md` documents mechanical style rules: capitalization, punctuation, numbers and dates, abbreviations, italics, spelling, UNDRR-specific terminology (Sendai Framework target/priority/indicator naming, GAR and campaign naming, gender language), disability inclusive terminology (person-first language and a recommended-vs-avoid term table), and gender-bias review technique (avoiding gendered-default constructions, \"flip it to test it\"). Each rule is cited to its source: the internal UNDRR Publications SOP, the United Nations Disability-Inclusive Communications Guidelines, the DPI Gender Checklist for Content Creators, the UN Geneva Web Style Guide or the United Nations Editorial Manual, in that priority order where they conflict. Rendered in Storybook under **Contributing/Editorial manual**. The AI manifest pipeline gains a new, genuinely separate output, `llms-editorial-manual.txt`, so an agent generating UI copy can fetch just the writing/style rules instead of the full component manifest; linked from `llms.txt`, `llms.json` (`urls.editorialManual`) and `docs/AI-MCP-INTEGRATION.md`. (unisdr/undrr-mangrove#1286, unisdr/undrr-mangrove#1287, unisdr/undrr-mangrove#1288)"
      }
    ]
  },
  "releases": [
    {
      "version": "2.0.0",
      "date": "2026-09-24",
      "isPrerelease": false,
      "tag": "v2.0.0",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-2026-09-24",
      "summary": "The stable release of Mangrove 2.0, published on the npm `latest` tag. The [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)) describe 2.0 as a whole, with an upgrade checklist, the numbered breaking changes and upgrade guidance from 1.x. 1.x is now in best-effort support, with no schedule for further releases. Earlier sections of this file record each prerelease. Prereleases: 2.0.0-alpha.1 (unisdr/undrr-mangrove#1061), 2.0.0-alpha.2, 2.0.0-alpha.3, 2.0.0-alpha.4, 2.0.0-beta.1, 2.0.0-beta.2 (tagged, not published to npm), 2.0.0-beta.3, 2.0.0-rc.1, 2.0.0-rc.2 and 2.0.0-rc.3.",
      "changes": [
        {
          "category": "Changes since 2.0.0-rc.3",
          "title": "HubHeader vanilla runtime:",
          "description": "New `js/hub-header.js` enhances static hub header markup without React, scrolling the current section into view on load and driving the edge fades; with `data-mg-hub-header-detect-current` it marks the current section from the URL. The React `HubHeader` runs the same script. (unisdr/undrr-mangrove#1292, unisdr/undrr-mangrove#1293)",
          "pr": 1292,
          "prRef": "unisdr/undrr-mangrove#1292",
          "prUrl": null,
          "raw": "**HubHeader vanilla runtime:** New `js/hub-header.js` enhances static hub header markup without React, scrolling the current section into view on load and driving the edge fades; with `data-mg-hub-header-detect-current` it marks the current section from the URL. The React `HubHeader` runs the same script. (unisdr/undrr-mangrove#1292, unisdr/undrr-mangrove#1293)"
        },
        {
          "category": "Changes since 2.0.0-rc.3",
          "title": "PageHeader language links fall back to the dropdown:",
          "description": "`languageDisplay=\"links\"` renders the icon dropdown below the tablet breakpoint and when there are more languages than the new `maxLanguageLinks` prop (default 6), replacing the two-letter codes that wrapped under the logo. Default and example language lists follow United Nations order, alphabetical by English name. (unisdr/undrr-mangrove#1284)",
          "pr": 1284,
          "prRef": "unisdr/undrr-mangrove#1284",
          "prUrl": null,
          "raw": "**PageHeader language links fall back to the dropdown:** `languageDisplay=\"links\"` renders the icon dropdown below the tablet breakpoint and when there are more languages than the new `maxLanguageLinks` prop (default 6), replacing the two-letter codes that wrapped under the logo. Default and example language lists follow United Nations order, alphabetical by English name. (unisdr/undrr-mangrove#1284)"
        },
        {
          "category": "Changes since 2.0.0-rc.3",
          "title": "Changelog entries use a comma between version and date:",
          "description": "Component MDX entries are now written `- **X.Y.Z**, YYYY-MM-DD ([#PR](url)): Description` and release headings here `## X.Y.Z, YYYY-MM-DD`; every existing entry was converted, and em dashes in changelog prose were replaced with commas, colons and parentheses. The changelog parser reads both the new and the old dash form. It also fixes a bug: 27 SyndicationSearchWidget entries that already used a comma were published in `releases.json` with no date. GitHub heading anchors for release headings in this file change as a result (for example `#200-rc3--2026-09-20` is now `#200-rc3-2026-09-20`). The [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs) now covers dash usage: en dashes where United Nations style uses them, no em dashes and no dashes as separators. (unisdr/undrr-mangrove#1289, unisdr/undrr-mangrove#1290)",
          "pr": 1289,
          "prRef": "unisdr/undrr-mangrove#1289",
          "prUrl": null,
          "raw": "**Changelog entries use a comma between version and date:** Component MDX entries are now written `- **X.Y.Z**, YYYY-MM-DD ([#PR](url)): Description` and release headings here `## X.Y.Z, YYYY-MM-DD`; every existing entry was converted, and em dashes in changelog prose were replaced with commas, colons and parentheses. The changelog parser reads both the new and the old dash form. It also fixes a bug: 27 SyndicationSearchWidget entries that already used a comma were published in `releases.json` with no date. GitHub heading anchors for release headings in this file change as a result (for example `#200-rc3--2026-09-20` is now `#200-rc3-2026-09-20`). The [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs) now covers dash usage: en dashes where United Nations style uses them, no em dashes and no dashes as separators. (unisdr/undrr-mangrove#1289, unisdr/undrr-mangrove#1290)"
        },
        {
          "category": "Changes since 2.0.0-rc.3",
          "title": "New editorial style guide, and a topic-scoped AI manifest to go with it:",
          "description": "`docs/EDITORIAL-MANUAL.md` documents mechanical style rules: capitalization, punctuation, numbers and dates, abbreviations, italics, spelling, UNDRR-specific terminology (Sendai Framework target/priority/indicator naming, GAR and campaign naming, gender language), disability inclusive terminology (person-first language and a recommended-vs-avoid term table), and gender-bias review technique (avoiding gendered-default constructions, \"flip it to test it\"). Each rule is cited to its source: the internal UNDRR Publications SOP, the United Nations Disability-Inclusive Communications Guidelines, the DPI Gender Checklist for Content Creators, the UN Geneva Web Style Guide or the United Nations Editorial Manual, in that priority order where they conflict. Rendered in Storybook under **Contributing/Editorial manual**. The AI manifest pipeline gains a new, genuinely separate output, `llms-editorial-manual.txt`, so an agent generating UI copy can fetch just the writing/style rules instead of the full component manifest; linked from `llms.txt`, `llms.json` (`urls.editorialManual`) and `docs/AI-MCP-INTEGRATION.md`. (unisdr/undrr-mangrove#1286, unisdr/undrr-mangrove#1287, unisdr/undrr-mangrove#1288)",
          "pr": 1286,
          "prRef": "unisdr/undrr-mangrove#1286",
          "prUrl": null,
          "raw": "**New editorial style guide, and a topic-scoped AI manifest to go with it:** `docs/EDITORIAL-MANUAL.md` documents mechanical style rules: capitalization, punctuation, numbers and dates, abbreviations, italics, spelling, UNDRR-specific terminology (Sendai Framework target/priority/indicator naming, GAR and campaign naming, gender language), disability inclusive terminology (person-first language and a recommended-vs-avoid term table), and gender-bias review technique (avoiding gendered-default constructions, \"flip it to test it\"). Each rule is cited to its source: the internal UNDRR Publications SOP, the United Nations Disability-Inclusive Communications Guidelines, the DPI Gender Checklist for Content Creators, the UN Geneva Web Style Guide or the United Nations Editorial Manual, in that priority order where they conflict. Rendered in Storybook under **Contributing/Editorial manual**. The AI manifest pipeline gains a new, genuinely separate output, `llms-editorial-manual.txt`, so an agent generating UI copy can fetch just the writing/style rules instead of the full component manifest; linked from `llms.txt`, `llms.json` (`urls.editorialManual`) and `docs/AI-MCP-INTEGRATION.md`. (unisdr/undrr-mangrove#1286, unisdr/undrr-mangrove#1287, unisdr/undrr-mangrove#1288)"
        }
      ]
    },
    {
      "version": "2.0.0-rc.3",
      "date": "2026-09-20",
      "isPrerelease": true,
      "tag": "v2.0.0-rc.3",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-rc3-2026-09-20",
      "summary": "The final planned polishing release before stable 2.0, with accessibility, typography, responsive layout and integration fixes. Stable 2.0 is expected shortly, subject to final integration testing.",
      "changes": [
        {
          "category": "General",
          "title": "Play-function assertions now run in CI, and a failing one fails the build.",
          "description": "Ten story files carried `play` functions and nothing executed them: there was no `@storybook/test-runner`, no `@storybook/addon-vitest` and no interactions addon in the tree, so a failing assertion appeared only as a console error in the browser of someone who happened to open that story. The issue as filed attributed this to `chromatic.yml` triggering on `pull_request: types: opened`; that is a second, unrelated gap rather than the cause: Chromatic snapshots pixels, runs with `exitZeroOnChanges: true`, and does not report a play-function failure as a failure, so even a run on every push would not have caught one. That trigger gap is real and is filed as unisdr/undrr-mangrove#1274 rather than fixed here, because widening the trigger on a workflow whose `paths:` filter includes `package.json` and `.storybook/**` would make TurboSnap fall back to a full rebuild of all 457 stories on exactly the pushes that change those files. `@storybook/test-runner` drives a headless Chromium over the built Storybook in the `Build and Deploy` workflow, which already runs on every push to a PR. **All 16 play functions across those ten files run, along with a render smoke test for every story: 458 tests over 84 story files.** jsdom was considered and rejected: these assertions read `getBoundingClientRect()`, `scrollWidth` and `getComputedStyle()`, and `Tag`'s \"Reflow and languages\" story says in its own comment that it exists to catch what jsdom cannot; running them under Jest would have produced a green suite that asserts nothing. Enabling the runner found two existing failures. `NewsletterPromotion`'s confirmation-focus assertion could never pass in a browser: the heading appears in one commit and is focused by a passive effect in the next, so a DOM query resolves on the first of those and the focus check ran a frame early; it now waits. `CookieConsentBanner`'s rendering check failed about two runs in three, and the cause was in the component rather than the story: its effect loads three CDN assets and waits 300ms, which outlives a story change, so the chain from a discarded mount still reached `run()` after cleanup had torn the banner down. That component fix shipped in unisdr/undrr-mangrove#1271 instead of here, and this branch carries none of it. That story's on-canvas status line also reported \"Bar rendered\" off a single height measurement while the assertions it appeared to summarise had already failed on an unstyled bar; it now reports every runtime condition the play function asserts but one, names the ones that failed, and in particular checks visibility rather than only the box: a `visibility: hidden` bar reports a box, which is precisely how the old line could read \"Bar rendered\" for a bar the play function fails on. The runner needs a Storybook already being served, so `scripts/serve-storybook.mjs` serves a built one in about a hundred dependency-free lines rather than adding `http-server`, `wait-on` and `concurrently` for three lines of CI; it answers 400 on a malformed URL instead of exiting, logs every 4xx and 5xx so a CI failure is diagnosable, reports `EADDRINUSE` rather than dying silently behind a `&`, and sends `Cache-Control: no-store`. That header addresses an observed problem: the responses carried no validator of any kind, so Chromium applied heuristic caching, and a rebuild into the same directory left the browser mixing fresh chunks with stale ones and running a story that matched no commit: an assertion failing against markup the source cannot produce. It serves on **6099**, not 6006, so it cannot collide with `yarn storybook`; sharing the port let the runner quietly test the dev server instead of the build under test. Run it locally with `yarn build && yarn serve-storybook & yarn test-storybook`; the test timeout is raised to 60s because the cookie banner check waits on the live CDN, which is the one story in the set whose reliability depends on a third party; `docs/TESTING.md` sets out what that dependency gates. Enabling the runner also made the a11y addon's output visible to contributors for the first time, so its scope is now stated. The addon was registered with no configuration at all, so it ran axe's default rule set (which includes `best-practice` rules that are not WCAG success criteria) over the whole story, third-party embed markup included, and reported one line per story. Its numbers were therefore not a count of accessibility failures in this library, though they read like one. The addon is now scoped to the WCAG tags it is assumed to be checking (`wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, `wcag22aa`), so its warnings are limited to rules tagged for WCAG A and AA. It still does not fail the build: `parameters.a11y.test` is left unset, which defaults to `'todo'`. A triaged inventory of what a full sweep actually finds is unisdr/undrr-mangrove#1273. (unisdr/undrr-mangrove#1244)",
          "pr": 1274,
          "prRef": "unisdr/undrr-mangrove#1274",
          "prUrl": null,
          "raw": "**Play-function assertions now run in CI, and a failing one fails the build.** Ten story files carried `play` functions and nothing executed them: there was no `@storybook/test-runner`, no `@storybook/addon-vitest` and no interactions addon in the tree, so a failing assertion appeared only as a console error in the browser of someone who happened to open that story. The issue as filed attributed this to `chromatic.yml` triggering on `pull_request: types: opened`; that is a second, unrelated gap rather than the cause: Chromatic snapshots pixels, runs with `exitZeroOnChanges: true`, and does not report a play-function failure as a failure, so even a run on every push would not have caught one. That trigger gap is real and is filed as unisdr/undrr-mangrove#1274 rather than fixed here, because widening the trigger on a workflow whose `paths:` filter includes `package.json` and `.storybook/**` would make TurboSnap fall back to a full rebuild of all 457 stories on exactly the pushes that change those files. `@storybook/test-runner` drives a headless Chromium over the built Storybook in the `Build and Deploy` workflow, which already runs on every push to a PR. **All 16 play functions across those ten files run, along with a render smoke test for every story: 458 tests over 84 story files.** jsdom was considered and rejected: these assertions read `getBoundingClientRect()`, `scrollWidth` and `getComputedStyle()`, and `Tag`'s \"Reflow and languages\" story says in its own comment that it exists to catch what jsdom cannot; running them under Jest would have produced a green suite that asserts nothing. Enabling the runner found two existing failures. `NewsletterPromotion`'s confirmation-focus assertion could never pass in a browser: the heading appears in one commit and is focused by a passive effect in the next, so a DOM query resolves on the first of those and the focus check ran a frame early; it now waits. `CookieConsentBanner`'s rendering check failed about two runs in three, and the cause was in the component rather than the story: its effect loads three CDN assets and waits 300ms, which outlives a story change, so the chain from a discarded mount still reached `run()` after cleanup had torn the banner down. That component fix shipped in unisdr/undrr-mangrove#1271 instead of here, and this branch carries none of it. That story's on-canvas status line also reported \"Bar rendered\" off a single height measurement while the assertions it appeared to summarise had already failed on an unstyled bar; it now reports every runtime condition the play function asserts but one, names the ones that failed, and in particular checks visibility rather than only the box: a `visibility: hidden` bar reports a box, which is precisely how the old line could read \"Bar rendered\" for a bar the play function fails on. The runner needs a Storybook already being served, so `scripts/serve-storybook.mjs` serves a built one in about a hundred dependency-free lines rather than adding `http-server`, `wait-on` and `concurrently` for three lines of CI; it answers 400 on a malformed URL instead of exiting, logs every 4xx and 5xx so a CI failure is diagnosable, reports `EADDRINUSE` rather than dying silently behind a `&`, and sends `Cache-Control: no-store`. That header addresses an observed problem: the responses carried no validator of any kind, so Chromium applied heuristic caching, and a rebuild into the same directory left the browser mixing fresh chunks with stale ones and running a story that matched no commit: an assertion failing against markup the source cannot produce. It serves on **6099**, not 6006, so it cannot collide with `yarn storybook`; sharing the port let the runner quietly test the dev server instead of the build under test. Run it locally with `yarn build && yarn serve-storybook & yarn test-storybook`; the test timeout is raised to 60s because the cookie banner check waits on the live CDN, which is the one story in the set whose reliability depends on a third party; `docs/TESTING.md` sets out what that dependency gates. Enabling the runner also made the a11y addon's output visible to contributors for the first time, so its scope is now stated. The addon was registered with no configuration at all, so it ran axe's default rule set (which includes `best-practice` rules that are not WCAG success criteria) over the whole story, third-party embed markup included, and reported one line per story. Its numbers were therefore not a count of accessibility failures in this library, though they read like one. The addon is now scoped to the WCAG tags it is assumed to be checking (`wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, `wcag22aa`), so its warnings are limited to rules tagged for WCAG A and AA. It still does not fail the build: `parameters.a11y.test` is left unset, which defaults to `'todo'`. A triaged inventory of what a full sweep actually finds is unisdr/undrr-mangrove#1273. (unisdr/undrr-mangrove#1244)"
        },
        {
          "category": "General",
          "title": "Package paths named in the documentation are now checked by a test, and nine that did not resolve are fixed.",
          "description": "The same class of defect was reported four times: unisdr/undrr-mangrove#1252 (the package root), unisdr/undrr-mangrove#1253 (`dist/js/*.min.js`), unisdr/undrr-mangrove#1266 (the SCSS entry point under `stories/`) and unisdr/undrr-mangrove#1267 (extensionless vanilla JS specifiers), because docs are written against the repo tree while consumers install the tarball, and the two have never had the same shape. Rather than fix them one at a time, this pass swept all 166 Markdown and MDX files at once and resolved every specifier in them against a freshly packed `2.0.0-rc.2` (275 files; top level `components/ css/ error-pages/ fonts/ js/ scss/`, nothing else). The two filed issues account for four of the nine: the SCSS entry point is `@undrr/undrr-mangrove/scss/assets/scss/style`, not `…/stories/…` (no tarball has ever held a `stories/` directory, 1.4.1 included, so the 1.4 release notes carry a correction rather than a rewrite), and the vanilla modules need their `.js`, because Node ESM will not add one and the package deliberately ships no `exports` map that could (`import('@undrr/undrr-mangrove/js/preview-access')` throws `ERR_MODULE_NOT_FOUND`; the same line with `.js` resolves and binds `mgPreviewAccess`). The sweep found five more. The **Sass integration guide**, the main page for Sass consumers, taught fourteen imports under `stories/assets/scss/` and `stories/Components/`, every one of which fails against the tarball with the `includePaths` configuration the same page recommends, and claimed the package ships `tokens/`, which it does not; the guide now names the published `scss/…` paths and says plainly that the tarball's shape is not the repository's. The **Grid** page pointed at `assets/css/grid.min.css`, a file the CDN has never served on any version (there is no standalone grid stylesheet, the `assets/` segment was doubled, and nothing under Mangrove is minified), and now names the theme stylesheet the grid rules compile into. The **CDN reference** listed `BarChart`, `MapComponent` and `Fetcher`, removed in 1.8.0 and in no bundle since; its legacy CSS table advertised files 2.0 deleted without saying so; and its JavaScript table, which calls itself \"the complete list\", was missing `js/drawer.js`. Two new tests keep it this way: `scripts/published-paths.cjs` models the tarball from the sources in milliseconds, `published-paths.test.js` holds that model against `assemble-npm-package.mjs` and against a real `dist/` when one exists, and `docs-package-paths.test.js` resolves every package specifier, package-root Sass import, CDN URL and CDN-reference table path in the documentation against it. What it does not cover is written at the top of that file, and the boundary is narrower than \"every path is checked\": the model is built from the working tree, not from a registry tarball or a CDN listing, so resolving against it is a necessary condition and never a sufficient one: a path added on a branch resolves in the test and 404s for consumers until a release ships it, which is the state `js/drawer.js` is in and which `docs/RELEASES.md` now carries a release step for. The sweep reads all 166 tracked `.md` and `.mdx` files; 1287 of their 27661 lines, across 78 files, are exempt as changelog prose or as rows of a release note's before/after tables, where naming a dead path is the point. CDN URLs are resolved only at the version in `package.json`; `latest/` is a moving pointer at the newest stable release, tracks neither this branch nor the `next` tag, and is left to the release procedure rather than guessed at.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null,
          "raw": "**Package paths named in the documentation are now checked by a test, and nine that did not resolve are fixed.** The same class of defect was reported four times: unisdr/undrr-mangrove#1252 (the package root), unisdr/undrr-mangrove#1253 (`dist/js/*.min.js`), unisdr/undrr-mangrove#1266 (the SCSS entry point under `stories/`) and unisdr/undrr-mangrove#1267 (extensionless vanilla JS specifiers), because docs are written against the repo tree while consumers install the tarball, and the two have never had the same shape. Rather than fix them one at a time, this pass swept all 166 Markdown and MDX files at once and resolved every specifier in them against a freshly packed `2.0.0-rc.2` (275 files; top level `components/ css/ error-pages/ fonts/ js/ scss/`, nothing else). The two filed issues account for four of the nine: the SCSS entry point is `@undrr/undrr-mangrove/scss/assets/scss/style`, not `…/stories/…` (no tarball has ever held a `stories/` directory, 1.4.1 included, so the 1.4 release notes carry a correction rather than a rewrite), and the vanilla modules need their `.js`, because Node ESM will not add one and the package deliberately ships no `exports` map that could (`import('@undrr/undrr-mangrove/js/preview-access')` throws `ERR_MODULE_NOT_FOUND`; the same line with `.js` resolves and binds `mgPreviewAccess`). The sweep found five more. The **Sass integration guide**, the main page for Sass consumers, taught fourteen imports under `stories/assets/scss/` and `stories/Components/`, every one of which fails against the tarball with the `includePaths` configuration the same page recommends, and claimed the package ships `tokens/`, which it does not; the guide now names the published `scss/…` paths and says plainly that the tarball's shape is not the repository's. The **Grid** page pointed at `assets/css/grid.min.css`, a file the CDN has never served on any version (there is no standalone grid stylesheet, the `assets/` segment was doubled, and nothing under Mangrove is minified), and now names the theme stylesheet the grid rules compile into. The **CDN reference** listed `BarChart`, `MapComponent` and `Fetcher`, removed in 1.8.0 and in no bundle since; its legacy CSS table advertised files 2.0 deleted without saying so; and its JavaScript table, which calls itself \"the complete list\", was missing `js/drawer.js`. Two new tests keep it this way: `scripts/published-paths.cjs` models the tarball from the sources in milliseconds, `published-paths.test.js` holds that model against `assemble-npm-package.mjs` and against a real `dist/` when one exists, and `docs-package-paths.test.js` resolves every package specifier, package-root Sass import, CDN URL and CDN-reference table path in the documentation against it. What it does not cover is written at the top of that file, and the boundary is narrower than \"every path is checked\": the model is built from the working tree, not from a registry tarball or a CDN listing, so resolving against it is a necessary condition and never a sufficient one: a path added on a branch resolves in the test and 404s for consumers until a release ships it, which is the state `js/drawer.js` is in and which `docs/RELEASES.md` now carries a release step for. The sweep reads all 166 tracked `.md` and `.mdx` files; 1287 of their 27661 lines, across 78 files, are exempt as changelog prose or as rows of a release note's before/after tables, where naming a dead path is the point. CDN URLs are resolved only at the version in `package.json`; `latest/` is a moving pointer at the newest stable release, tracks neither this branch nor the `next` tag, and is left to the release procedure rather than guessed at."
        },
        {
          "category": "General",
          "title": "The published package stops advertising a root entry point.",
          "description": "This is a **consumer-visible change to the shape of the npm package**, and nothing that works today breaks. `scripts/assemble-npm-package.mjs` wrote `main: \"dist/index.js\"` into the generated `package.json`, naming a file the tarball has never contained: `files` excludes `dist/`, and `npm pack` of `2.0.0-rc.2` (`next`) and `1.8.2` (`latest`) ships zero files under `dist/` or `src/`. So `import … from '@undrr/undrr-mangrove'` has failed with `ERR_MODULE_NOT_FOUND` on every version ever published, so the `main` field pointed consumers at a file no release contained. 2.0.0 removes it. **If a root import appeared to work in your project, it was resolving something other than the published package.** The working surface is unchanged and is what it always was, the per-directory subpaths: `@undrr/undrr-mangrove/components/IconCard.js`, `…/css/style.css`, `…/scss/…`, `…/js/…`, `…/fonts/…`, `…/error-pages/…`. Making the root work instead was considered and rejected: the only cheap way is a barrel over `components/`, and `components/ShowMore.js` and `components/Tab.js` read `document` at module scope, so the root would throw on import in Node and SSR while looking supported, which is harder to diagnose than a failure at import time. No `exports` map is added either; one with only a `\".\"` entry and no `\"./*\": \"./*\"` alongside would make every working subpath fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`, which is the one change in this area that would genuinely break consumers. Shipping a root stub that throws a friendlier message was rejected too: it resolves, so a bundler would build clean and fail in the browser rather than at build time. `scripts/__tests__/assemble-npm-package.test.js` now fails if `main`, `module` or `exports` reappears, or if any root file reaches the tarball. Documentation caught up in the same pass: 14 MDX pages taught `import { X } from '@undrr/undrr-mangrove'` and now show the subpath with the binding the published bundle actually exports, which is not uniform, so `QuoteHighlight`, `UserFeedback`, `MegaMenu`, `ScrollContainer` and `ShareButtons` are default imports while most others are named. The React integration guide's component table is rebuilt from the built bundles and lists the exact import line per component, and `CookieConsentBanner`, `Snackbar` and `CodeBlock` are now stated to ship in no bundle at all rather than appearing importable. Checking the table against `dist/components/` also turned up the opposite error: `FullWidth.mdx` said its wrapper component \"is not exported from `@undrr/undrr-mangrove`, so it is only available inside this repository\", when it has had a webpack entry since unisdr/undrr-mangrove#847 and the package has been publishing `components/FullWidth.js` the whole time; that page now gives the working import line. `docs/RELEASES.md` and `docs/ARCHITECTURE.md` follow, and `docs/RELEASES.md` no longer tells a releaser not to \"fix\" the dangling `main`. (unisdr/undrr-mangrove#1252)",
          "pr": 847,
          "prRef": "unisdr/undrr-mangrove#847",
          "prUrl": null,
          "raw": "**The published package stops advertising a root entry point.** This is a **consumer-visible change to the shape of the npm package**, and nothing that works today breaks. `scripts/assemble-npm-package.mjs` wrote `main: \"dist/index.js\"` into the generated `package.json`, naming a file the tarball has never contained: `files` excludes `dist/`, and `npm pack` of `2.0.0-rc.2` (`next`) and `1.8.2` (`latest`) ships zero files under `dist/` or `src/`. So `import … from '@undrr/undrr-mangrove'` has failed with `ERR_MODULE_NOT_FOUND` on every version ever published, so the `main` field pointed consumers at a file no release contained. 2.0.0 removes it. **If a root import appeared to work in your project, it was resolving something other than the published package.** The working surface is unchanged and is what it always was, the per-directory subpaths: `@undrr/undrr-mangrove/components/IconCard.js`, `…/css/style.css`, `…/scss/…`, `…/js/…`, `…/fonts/…`, `…/error-pages/…`. Making the root work instead was considered and rejected: the only cheap way is a barrel over `components/`, and `components/ShowMore.js` and `components/Tab.js` read `document` at module scope, so the root would throw on import in Node and SSR while looking supported, which is harder to diagnose than a failure at import time. No `exports` map is added either; one with only a `\".\"` entry and no `\"./*\": \"./*\"` alongside would make every working subpath fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`, which is the one change in this area that would genuinely break consumers. Shipping a root stub that throws a friendlier message was rejected too: it resolves, so a bundler would build clean and fail in the browser rather than at build time. `scripts/__tests__/assemble-npm-package.test.js` now fails if `main`, `module` or `exports` reappears, or if any root file reaches the tarball. Documentation caught up in the same pass: 14 MDX pages taught `import { X } from '@undrr/undrr-mangrove'` and now show the subpath with the binding the published bundle actually exports, which is not uniform, so `QuoteHighlight`, `UserFeedback`, `MegaMenu`, `ScrollContainer` and `ShareButtons` are default imports while most others are named. The React integration guide's component table is rebuilt from the built bundles and lists the exact import line per component, and `CookieConsentBanner`, `Snackbar` and `CodeBlock` are now stated to ship in no bundle at all rather than appearing importable. Checking the table against `dist/components/` also turned up the opposite error: `FullWidth.mdx` said its wrapper component \"is not exported from `@undrr/undrr-mangrove`, so it is only available inside this repository\", when it has had a webpack entry since unisdr/undrr-mangrove#847 and the package has been publishing `components/FullWidth.js` the whole time; that page now gives the working import line. `docs/RELEASES.md` and `docs/ARCHITECTURE.md` follow, and `docs/RELEASES.md` no longer tells a releaser not to \"fix\" the dangling `main`. (unisdr/undrr-mangrove#1252)"
        },
        {
          "category": "General",
          "title": "The syndicated search widget's Drupal snippet hydrates:",
          "description": "its MDX told consumers to mark the container with `data-undrr-search-widget`, an attribute nothing in the library has ever queried, while the hydration selector is and always was `[data-mg-search-widget]`. Both the sentence and the copy-pasteable markup are corrected, along with the same spelling in the `CustomEndpoint` story's Drupal example, and no occurrence is left in the repo. Documentation only. (unisdr/undrr-mangrove#1255)",
          "pr": 1255,
          "prRef": "unisdr/undrr-mangrove#1255",
          "prUrl": null,
          "raw": "**The syndicated search widget's Drupal snippet hydrates:** its MDX told consumers to mark the container with `data-undrr-search-widget`, an attribute nothing in the library has ever queried, while the hydration selector is and always was `[data-mg-search-widget]`. Both the sentence and the copy-pasteable markup are corrected, along with the same spelling in the `CustomEndpoint` story's Drupal example, and no occurrence is left in the repo. Documentation only. (unisdr/undrr-mangrove#1255)"
        },
        {
          "category": "General",
          "title": "`QuoteHighlight` now sanitises its three HTML props, closing the divergence from `TextCta`:",
          "description": "`quote`, `attribution` and `attributionTitle` go through DOMPurify's default allow-list before they are rendered, which is the call `TextCta` already makes on its `text` prop and the cards and `Drawer` make on theirs. The difference was not a documented decision (unisdr/undrr-mangrove#1258 looked for a stated reason and found none), and of the three components documented there, this is the one whose content matches what `TextCta` sanitises: three short authored strings, hydration-fed from `data-quote`, `data-attribution` and `data-attribution-title` on a CMS-rendered container, so the values travel through markup rather than staying inside a consumer's wrapper. `Tab` and `MegaMenu` are deliberately not changed: `Tab`'s `data` is a whole panel body and a default pass would strip the `<iframe>` and embeds the prop exists to carry, and `MegaMenu`'s `bannerDescription` is injected as an entire `<article>` that consumers build in their own wrapper. **Consumer-visible:** the allow-list keeps everything these props are for (`<cite>`, `<em>`, `<br>`, and an anchor on an attribution name, each covered by a test), but markup that previously rendered is now filtered: `<script>`, event-handler attributes, `javascript:` URLs, and `<iframe>` and other embeds. Panel-sized content placed in a pull quote is removed. The one removal that is not an attack shape, and the one worth checking before you upgrade, is `target`: DOMPurify's default list drops `target` and `referrerpolicy`, so an attribution link authored as `target=\"_blank\"` now opens in the same tab. That is the library-wide default and not a choice made for this component; `TextCta`, `Drawer` and the cards strip it from their HTML props too, and everything else an anchor carries (`href`, `rel`, `class`, `id`, `title`, `hreflang`, `lang`, `dir`, `aria-*`, `data-*`, including Linkit's `data-entity-*`) survives, so nothing goes unstyled and no entity reference is lost. Upstream sanitising is no longer required and is still worth keeping; the component's pass is a floor, not a replacement for a CMS output filter. The \"you own sanitising this\" wording unisdr/undrr-mangrove#1258 added is reversed everywhere it was written: the component comment, the three render sites, the propTypes, the MDX section (now \"Quote HTML handling\"), the three CDN attribute rows and the AI manifest entry. The `<` check on `quote` stays and is now described for what it does: it picks the wrapper element, a `<p>` for a plain quote and a `<span>` for one with markup, and is no part of the safety. One knock-on worth knowing for anyone sanitising another component later: DOMPurify needs a DOM and the AI manifest renders components in plain Node, so the renderer now skips `QuoteHighlight` as it already skips `VerticalCard` and the rest, and its `renderedHtml` is curated in `scripts/ai-manifest/component-data.js` rather than auto-rendered, accurate today, and now something a markup change has to keep in step by hand. The manifest already has a guard for exactly that, `checkCuratedDrift`, which re-renders a curated component from `dist/` and compares its BEM classes; it cannot render a sanitising component either, and an empty `catch` discarded that failure, so every curated entry belonging to a DOMPurify component appeared covered when it was not. It now reports each one as \"could not verify\" instead, which is how this gap became visible and is the only warning a reader has that the block is hand-maintained. The curated example also carries an `aria-label` on the `<section>` that React does not emit (the component gives the section no accessible name, which the curated-HTML a11y lint requires and which is a real limitation left for its own issue), so the example's published name and the hydration note both say the label is the consumer's to write and translate, rather than leaving that in a source comment nothing publishes. (unisdr/undrr-mangrove#1259)",
          "pr": 1258,
          "prRef": "unisdr/undrr-mangrove#1258",
          "prUrl": null,
          "raw": "**`QuoteHighlight` now sanitises its three HTML props, closing the divergence from `TextCta`:** `quote`, `attribution` and `attributionTitle` go through DOMPurify's default allow-list before they are rendered, which is the call `TextCta` already makes on its `text` prop and the cards and `Drawer` make on theirs. The difference was not a documented decision (unisdr/undrr-mangrove#1258 looked for a stated reason and found none), and of the three components documented there, this is the one whose content matches what `TextCta` sanitises: three short authored strings, hydration-fed from `data-quote`, `data-attribution` and `data-attribution-title` on a CMS-rendered container, so the values travel through markup rather than staying inside a consumer's wrapper. `Tab` and `MegaMenu` are deliberately not changed: `Tab`'s `data` is a whole panel body and a default pass would strip the `<iframe>` and embeds the prop exists to carry, and `MegaMenu`'s `bannerDescription` is injected as an entire `<article>` that consumers build in their own wrapper. **Consumer-visible:** the allow-list keeps everything these props are for (`<cite>`, `<em>`, `<br>`, and an anchor on an attribution name, each covered by a test), but markup that previously rendered is now filtered: `<script>`, event-handler attributes, `javascript:` URLs, and `<iframe>` and other embeds. Panel-sized content placed in a pull quote is removed. The one removal that is not an attack shape, and the one worth checking before you upgrade, is `target`: DOMPurify's default list drops `target` and `referrerpolicy`, so an attribution link authored as `target=\"_blank\"` now opens in the same tab. That is the library-wide default and not a choice made for this component; `TextCta`, `Drawer` and the cards strip it from their HTML props too, and everything else an anchor carries (`href`, `rel`, `class`, `id`, `title`, `hreflang`, `lang`, `dir`, `aria-*`, `data-*`, including Linkit's `data-entity-*`) survives, so nothing goes unstyled and no entity reference is lost. Upstream sanitising is no longer required and is still worth keeping; the component's pass is a floor, not a replacement for a CMS output filter. The \"you own sanitising this\" wording unisdr/undrr-mangrove#1258 added is reversed everywhere it was written: the component comment, the three render sites, the propTypes, the MDX section (now \"Quote HTML handling\"), the three CDN attribute rows and the AI manifest entry. The `<` check on `quote` stays and is now described for what it does: it picks the wrapper element, a `<p>` for a plain quote and a `<span>` for one with markup, and is no part of the safety. One knock-on worth knowing for anyone sanitising another component later: DOMPurify needs a DOM and the AI manifest renders components in plain Node, so the renderer now skips `QuoteHighlight` as it already skips `VerticalCard` and the rest, and its `renderedHtml` is curated in `scripts/ai-manifest/component-data.js` rather than auto-rendered, accurate today, and now something a markup change has to keep in step by hand. The manifest already has a guard for exactly that, `checkCuratedDrift`, which re-renders a curated component from `dist/` and compares its BEM classes; it cannot render a sanitising component either, and an empty `catch` discarded that failure, so every curated entry belonging to a DOMPurify component appeared covered when it was not. It now reports each one as \"could not verify\" instead, which is how this gap became visible and is the only warning a reader has that the block is hand-maintained. The curated example also carries an `aria-label` on the `<section>` that React does not emit (the component gives the section no accessible name, which the curated-HTML a11y lint requires and which is a real limitation left for its own issue), so the example's published name and the hydration note both say the label is the consumer's to write and translate, rather than leaving that in a source comment nothing publishes. (unisdr/undrr-mangrove#1259)"
        },
        {
          "category": "General",
          "title": "The hero's veil now carries a contrast guarantee the register cannot express:",
          "description": "a hero composites a gradient over an editorial photograph the component never sees, so no token pair describes what its copy is read against, which is how IRP's body copy measured 3.91:1 over a bright sky while every register entry passed. Increasing the tint does not work, and the reason is now documented: IRP's hero blue measures 4.71 against white at **full** opacity, so the colour has no headroom to spend and no amount of turning the tint up buys a margin; repointing `--mg-color-hero` at IRP's deeper blue was measured too and still lands at 4.46. The guarantee has to come from a neutral layer whose effect does not depend on which brand colour is underneath, so `hero.scss` now paints a scrim (`--mg-hero-scrim-color`, neutral-900, at `--mg-hero-scrim-opacity` 0.16) above the tint. Both layers hold at full strength across the copy and clear beyond it over `--mg-hero-scrim-fade`, reading the same `--mg-hero-copy-inline-end` the padding is derived from rather than the old fixed 48%, which also fixes the 910px–1164px band, where the copy ran two thirds of the way across the frame into tint that had already fallen to about 0.56. Measured by rendering the component, re-screenshotting with the copy set to `transparent` and sampling the painted pixels behind each line box: IRP 3.91 → 5.14, PreventionWeb 5.07 → 6.47, UNDRR 6.33 → 7.83, DELTA 6.32 → 7.82, MCR2030 8.87 → 10.35, in both the default and `--secondary` variants and in both directions. Those before-figures are a property of the photograph sampled rather than of the themes: re-measured against a near-white image, all five failed (IRP 2.51, PreventionWeb 2.91, UNDRR 3.34, MCR2030 4.06, DELTA 4.33), and the scrim's 0.16 is the solved floor (the minimum that holds 4.5:1 over pure white is 0.137 on IRP), not a round number with room to come down. Visible in all five themes: above 900px the copy band is about 16% deeper and the photograph beyond it is not; below 900px the scrim is held flat, so there the photograph is 16% deeper too. Two conditions the protected band rests on are enforced rather than assumed: an unbreakable word (a long German compound, a URL) used to size the title past the copy column and paint over the bare photograph, and `--contained`, which takes its parent's width rather than the viewport's, kept a viewport-derived offset the copy no longer had and lost its photograph entirely when narrower than the band. **Below 900px the vertical veil still fails in every theme** (2.4–3.1, from 1.7–2.8 before): there the copy fills the frame, so either the photograph is veiled across the whole frame or some copy is unreadable, and choosing between those is a design decision across five brands. The measurements, the method and three costed options are in [the hero scrim](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/COLOUR-CONTRAST-METHODOLOGY.md#the-hero-scrim). (unisdr/undrr-mangrove#1256)",
          "pr": 1256,
          "prRef": "unisdr/undrr-mangrove#1256",
          "prUrl": null,
          "raw": "**The hero's veil now carries a contrast guarantee the register cannot express:** a hero composites a gradient over an editorial photograph the component never sees, so no token pair describes what its copy is read against, which is how IRP's body copy measured 3.91:1 over a bright sky while every register entry passed. Increasing the tint does not work, and the reason is now documented: IRP's hero blue measures 4.71 against white at **full** opacity, so the colour has no headroom to spend and no amount of turning the tint up buys a margin; repointing `--mg-color-hero` at IRP's deeper blue was measured too and still lands at 4.46. The guarantee has to come from a neutral layer whose effect does not depend on which brand colour is underneath, so `hero.scss` now paints a scrim (`--mg-hero-scrim-color`, neutral-900, at `--mg-hero-scrim-opacity` 0.16) above the tint. Both layers hold at full strength across the copy and clear beyond it over `--mg-hero-scrim-fade`, reading the same `--mg-hero-copy-inline-end` the padding is derived from rather than the old fixed 48%, which also fixes the 910px–1164px band, where the copy ran two thirds of the way across the frame into tint that had already fallen to about 0.56. Measured by rendering the component, re-screenshotting with the copy set to `transparent` and sampling the painted pixels behind each line box: IRP 3.91 → 5.14, PreventionWeb 5.07 → 6.47, UNDRR 6.33 → 7.83, DELTA 6.32 → 7.82, MCR2030 8.87 → 10.35, in both the default and `--secondary` variants and in both directions. Those before-figures are a property of the photograph sampled rather than of the themes: re-measured against a near-white image, all five failed (IRP 2.51, PreventionWeb 2.91, UNDRR 3.34, MCR2030 4.06, DELTA 4.33), and the scrim's 0.16 is the solved floor (the minimum that holds 4.5:1 over pure white is 0.137 on IRP), not a round number with room to come down. Visible in all five themes: above 900px the copy band is about 16% deeper and the photograph beyond it is not; below 900px the scrim is held flat, so there the photograph is 16% deeper too. Two conditions the protected band rests on are enforced rather than assumed: an unbreakable word (a long German compound, a URL) used to size the title past the copy column and paint over the bare photograph, and `--contained`, which takes its parent's width rather than the viewport's, kept a viewport-derived offset the copy no longer had and lost its photograph entirely when narrower than the band. **Below 900px the vertical veil still fails in every theme** (2.4–3.1, from 1.7–2.8 before): there the copy fills the frame, so either the photograph is veiled across the whole frame or some copy is unreadable, and choosing between those is a design decision across five brands. The measurements, the method and three costed options are in [the hero scrim](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/COLOUR-CONTRAST-METHODOLOGY.md#the-hero-scrim). (unisdr/undrr-mangrove#1256)"
        },
        {
          "category": "General",
          "title": "The hero's mobile composition changes: below 900px the copy comes off the photograph.",
          "description": "This is **consumer-visible** on every page that uses the background layout, and it is a layout change rather than a colour one. The vertical veil could not be made to work. Below the tablet breakpoint the copy filled the frame while the veil faded toward the top, so the eyebrow label sat where the tint was at its 0.08 end opacity, and it **failed 4.5:1 in all five themes at every width from 320px to the breakpoint**: IRP 2.25–2.43, UNDRR 2.21–2.40, PreventionWeb 2.39–2.58, MCR2030 2.68–3.03, DELTA 2.80–3.15, with IRP's title also under the 3:1 large-text bar on the two narrowest phones. 72 of 210 measured cells failed, in both directions. Three options were costed in unisdr/undrr-mangrove#1256 and the layout one was taken: the photograph is now a banner across the top of the hero, `--mg-hero-banner-block-size` deep, and the copy sits beneath it on the theme's hero colour under the same 0.16 scrim the desktop copy band already wears. Nothing is read against a photograph any more, so the guarantee stops being a sampling result and becomes a token pair: IRP 6.19, PreventionWeb 8.12, UNDRR and DELTA 10.08, MCR2030 13.67, the same to within 0.01 over a near-white wall, a high-key sky, a busy high-frequency pattern and a white-sky landscape. 0 of 210 cells fail. The two colour options were rejected for the same reason: both buy the copy by veiling the photograph across the whole frame, at exactly the viewport where the photograph is smallest and least able to carry detail; and the \"before\" screenshots show the old veil was already doing that while the copy still failed, so at that size the photograph was already largely obscured. **What consumers will see below 900px:** the hero is taller, because the banner and the copy are stacked rather than overlaid; a first screenful that was \"image with words on it\" is now \"image, then words\"; and `size=\"immersive\"` drops its `min-height` there and spends the room on a deeper banner instead, because that height existed to give the photograph space _behind_ copy that is no longer there. **Above 900px nothing moves**: a horizontal re-sweep at 910, 1164, 1440 and 1920 in both directions returns all 120 cells identical to the figures unisdr/undrr-mangrove#1256 shipped, to the last decimal place. CSS only: no new element and no script, so a consumer who hand-authors the markup and takes the stylesheet alone gets the same composition; the banner is a `::after` taking the inline `background-image` by `inherit`, which is also why the element's own painting is sized away rather than set to `none`. New property `--mg-hero-banner-block-size`; a theme moves the banner's depth with it and the panel follows. Measurements, the rejected options and the implementation's two load-bearing details are in [the hero scrim](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/COLOUR-CONTRAST-METHODOLOGY.md#the-hero-scrim), and the `Mobile banner and panel` story pins a phone viewport so it can be looked at. One case the stylesheet cannot decide for itself: a hero with **no** background image has nothing for the banner to inherit, so `Hero` and `ChildHero` now add `mg-hero--no-image` when `imgback` is absent and the banner collapses to nothing there rather than reserving a third of a phone screen for an image that never arrives; the copy keeps the panel and the same figures. The inline `background-image` is dropped with it, so an image-less hero stops requesting `url(undefined)` on every page that has one. Consumers who hand-author the markup and omit the background image must add that class themselves. (unisdr/undrr-mangrove#1264)",
          "pr": 1256,
          "prRef": "unisdr/undrr-mangrove#1256",
          "prUrl": null,
          "raw": "**The hero's mobile composition changes: below 900px the copy comes off the photograph.** This is **consumer-visible** on every page that uses the background layout, and it is a layout change rather than a colour one. The vertical veil could not be made to work. Below the tablet breakpoint the copy filled the frame while the veil faded toward the top, so the eyebrow label sat where the tint was at its 0.08 end opacity, and it **failed 4.5:1 in all five themes at every width from 320px to the breakpoint**: IRP 2.25–2.43, UNDRR 2.21–2.40, PreventionWeb 2.39–2.58, MCR2030 2.68–3.03, DELTA 2.80–3.15, with IRP's title also under the 3:1 large-text bar on the two narrowest phones. 72 of 210 measured cells failed, in both directions. Three options were costed in unisdr/undrr-mangrove#1256 and the layout one was taken: the photograph is now a banner across the top of the hero, `--mg-hero-banner-block-size` deep, and the copy sits beneath it on the theme's hero colour under the same 0.16 scrim the desktop copy band already wears. Nothing is read against a photograph any more, so the guarantee stops being a sampling result and becomes a token pair: IRP 6.19, PreventionWeb 8.12, UNDRR and DELTA 10.08, MCR2030 13.67, the same to within 0.01 over a near-white wall, a high-key sky, a busy high-frequency pattern and a white-sky landscape. 0 of 210 cells fail. The two colour options were rejected for the same reason: both buy the copy by veiling the photograph across the whole frame, at exactly the viewport where the photograph is smallest and least able to carry detail; and the \"before\" screenshots show the old veil was already doing that while the copy still failed, so at that size the photograph was already largely obscured. **What consumers will see below 900px:** the hero is taller, because the banner and the copy are stacked rather than overlaid; a first screenful that was \"image with words on it\" is now \"image, then words\"; and `size=\"immersive\"` drops its `min-height` there and spends the room on a deeper banner instead, because that height existed to give the photograph space _behind_ copy that is no longer there. **Above 900px nothing moves**: a horizontal re-sweep at 910, 1164, 1440 and 1920 in both directions returns all 120 cells identical to the figures unisdr/undrr-mangrove#1256 shipped, to the last decimal place. CSS only: no new element and no script, so a consumer who hand-authors the markup and takes the stylesheet alone gets the same composition; the banner is a `::after` taking the inline `background-image` by `inherit`, which is also why the element's own painting is sized away rather than set to `none`. New property `--mg-hero-banner-block-size`; a theme moves the banner's depth with it and the panel follows. Measurements, the rejected options and the implementation's two load-bearing details are in [the hero scrim](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/COLOUR-CONTRAST-METHODOLOGY.md#the-hero-scrim), and the `Mobile banner and panel` story pins a phone viewport so it can be looked at. One case the stylesheet cannot decide for itself: a hero with **no** background image has nothing for the banner to inherit, so `Hero` and `ChildHero` now add `mg-hero--no-image` when `imgback` is absent and the banner collapses to nothing there rather than reserving a third of a phone screen for an image that never arrives; the copy keeps the panel and the same figures. The inline `background-image` is dropped with it, so an image-less hero stops requesting `url(undefined)` on every page that has one. Consumers who hand-author the markup and omit the background image must add that class themselves. (unisdr/undrr-mangrove#1264)"
        },
        {
          "category": "General",
          "title": "An exception in the contrast register must now name a pair that exists:",
          "description": "`WCAG_EXCEPTIONS` and `PERCEPTUAL_EXCEPTIONS` are read in exactly one place (the per-pair tests generated from `COMPONENT_PAIRS`, keyed `theme|pair name`), so a key naming a pair that is not there is never looked up, asserts nothing, and leaves no trace. Ten of them (`<theme>|focus ring on the snackbar, for comparison`, across both tables) had been inert since unisdr/undrr-mangrove#1164 moved Snackbar into Notice and deleted the pairs. They are removed, and the suite now fails, naming the offending keys, when any exception key does not correspond to a pair. This matters because an exception is a recorded judgement that one specific failing contrast is acceptable, and without this check the register cannot tell an exception that is doing its job from one whose pair was renamed underneath it, in which case the reasoning is orphaned _and_ the real pair is now measured with no exception at all. This is the counterpart of the `.mg-tag--accent` guard from unisdr/undrr-mangrove#1249, which binds the register's pairs to what the stylesheet paints; same seam, opposite direction. (unisdr/undrr-mangrove#1257)",
          "pr": 1164,
          "prRef": "unisdr/undrr-mangrove#1164",
          "prUrl": null,
          "raw": "**An exception in the contrast register must now name a pair that exists:** `WCAG_EXCEPTIONS` and `PERCEPTUAL_EXCEPTIONS` are read in exactly one place (the per-pair tests generated from `COMPONENT_PAIRS`, keyed `theme|pair name`), so a key naming a pair that is not there is never looked up, asserts nothing, and leaves no trace. Ten of them (`<theme>|focus ring on the snackbar, for comparison`, across both tables) had been inert since unisdr/undrr-mangrove#1164 moved Snackbar into Notice and deleted the pairs. They are removed, and the suite now fails, naming the offending keys, when any exception key does not correspond to a pair. This matters because an exception is a recorded judgement that one specific failing contrast is acceptable, and without this check the register cannot tell an exception that is doing its job from one whose pair was renamed underneath it, in which case the reasoning is orphaned _and_ the real pair is now measured with no exception at all. This is the counterpart of the `.mg-tag--accent` guard from unisdr/undrr-mangrove#1249, which binds the register's pairs to what the stylesheet paints; same seam, opposite direction. (unisdr/undrr-mangrove#1257)"
        },
        {
          "category": "General",
          "title": "New component: Segmented control, for a one-of-N view switcher.",
          "description": "The library had no segmented control, button group or toggle group, and none of the nearest existing components fitted, so consumers worked around each one: Tabs look right but the ARIA tabs pattern expects a `tabpanel` per tab, which a view switcher has none of; Chips read as filters that combine; Radio is the correct semantics with no compact presentation, so teams built their own visual layer, which diverged from Mangrove's button and focus-ring tokens. The risk-resilience-maps team reported it after shipping a `role=\"tablist\"` with no panels (unisdr/undrr-mangrove#1188). `.mg-segmented-control` is the presentation and nothing else: a `fieldset`, a `legend` and one `input[type=radio]` per segment, each followed by its own `label`, with the inputs clipped out of sight and the labels drawn from the button tokens. No JavaScript and no ARIA, so arrow-key navigation, the checked state, the \"N of M\" announcement, form submission and RTL traversal stay the browser's and stay correct. Vanilla HTML consumers can hand-author the markup; React consumers get `SegmentedControl` with `value`/`defaultValue`, `hideLegend`, `size` and `fullWidth`. Details in the component changelog. (unisdr/undrr-mangrove#1188)",
          "pr": 1188,
          "prRef": "unisdr/undrr-mangrove#1188",
          "prUrl": null,
          "raw": "**New component: Segmented control, for a one-of-N view switcher.** The library had no segmented control, button group or toggle group, and none of the nearest existing components fitted, so consumers worked around each one: Tabs look right but the ARIA tabs pattern expects a `tabpanel` per tab, which a view switcher has none of; Chips read as filters that combine; Radio is the correct semantics with no compact presentation, so teams built their own visual layer, which diverged from Mangrove's button and focus-ring tokens. The risk-resilience-maps team reported it after shipping a `role=\"tablist\"` with no panels (unisdr/undrr-mangrove#1188). `.mg-segmented-control` is the presentation and nothing else: a `fieldset`, a `legend` and one `input[type=radio]` per segment, each followed by its own `label`, with the inputs clipped out of sight and the labels drawn from the button tokens. No JavaScript and no ARIA, so arrow-key navigation, the checked state, the \"N of M\" announcement, form submission and RTL traversal stay the browser's and stay correct. Vanilla HTML consumers can hand-author the markup; React consumers get `SegmentedControl` with `value`/`defaultValue`, `hideLegend`, `size` and `fullWidth`. Details in the component changelog. (unisdr/undrr-mangrove#1188)"
        },
        {
          "category": "General",
          "title": "Lint and format coverage now reaches the code the library ships:",
          "description": "ESLint's `files` patterns claimed story files and TypeScript and nothing else, so 205 component `.jsx` files (the shipped components and their tests) were walked past with `File ignored because no matching configuration was supplied`, and `src/` was passed to neither ESLint nor Prettier. `.jsx` is now claimed by the same rule set as the stories beside it, `src` is in `lint:js`, `lint:check` and the Prettier glob, and `yarn lint:coverage` (part of `lint:check`) fails when any source file matches no configuration, so an unlinted file in one of those directories cannot escape silently. Turning coverage on surfaced 74 problems; `prefer-const`, `eqeqeq`, `no-return-assign`, `no-console`, `func-names` and `no-bitwise` are now clean, with four React ref callbacks rewritten to statement bodies so they stop returning the element they assign. React 19.3 does not reject that return value (it stores it as the ref cleanup and, finding it is not a function, still calls `ref(null)` on detach), so the four sites behaved correctly; returning a _function_ from a ref callback is the shape that would silently replace the detach, and the statement bodies keep these four out of reach of it. `react/no-danger` is enabled at warning severity over a 22-site backlog that needs a per-site audit (the rule only inspects lowercase DOM elements, so the real figure is the 36 non-test `dangerouslySetInnerHTML` sites in the tree, and a clean rule run is necessary rather than sufficient), and `react/no-array-index-key` (51 sites) stays off; both decisions are recorded in `eslint.config.mjs`. Of the 13 suppression comments that became live, five were misplaced (they sat a line above the violation and suppressed nothing) and are now attached to the right line; four named rules that stay off and have become plain comments keeping their explanation. Unused directives are reported as errors rather than warnings, so a stale suppression fails the build. (unisdr/undrr-mangrove#1235, unisdr/undrr-mangrove#1241, PR unisdr/undrr-mangrove#1247)",
          "pr": 1235,
          "prRef": "unisdr/undrr-mangrove#1235",
          "prUrl": null,
          "raw": "**Lint and format coverage now reaches the code the library ships:** ESLint's `files` patterns claimed story files and TypeScript and nothing else, so 205 component `.jsx` files (the shipped components and their tests) were walked past with `File ignored because no matching configuration was supplied`, and `src/` was passed to neither ESLint nor Prettier. `.jsx` is now claimed by the same rule set as the stories beside it, `src` is in `lint:js`, `lint:check` and the Prettier glob, and `yarn lint:coverage` (part of `lint:check`) fails when any source file matches no configuration, so an unlinted file in one of those directories cannot escape silently. Turning coverage on surfaced 74 problems; `prefer-const`, `eqeqeq`, `no-return-assign`, `no-console`, `func-names` and `no-bitwise` are now clean, with four React ref callbacks rewritten to statement bodies so they stop returning the element they assign. React 19.3 does not reject that return value (it stores it as the ref cleanup and, finding it is not a function, still calls `ref(null)` on detach), so the four sites behaved correctly; returning a _function_ from a ref callback is the shape that would silently replace the detach, and the statement bodies keep these four out of reach of it. `react/no-danger` is enabled at warning severity over a 22-site backlog that needs a per-site audit (the rule only inspects lowercase DOM elements, so the real figure is the 36 non-test `dangerouslySetInnerHTML` sites in the tree, and a clean rule run is necessary rather than sufficient), and `react/no-array-index-key` (51 sites) stays off; both decisions are recorded in `eslint.config.mjs`. Of the 13 suppression comments that became live, five were misplaced (they sat a line above the violation and suppressed nothing) and are now attached to the right line; four named rules that stay off and have become plain comments keeping their explanation. Unused directives are reported as errors rather than warnings, so a stale suppression fails the build. (unisdr/undrr-mangrove#1235, unisdr/undrr-mangrove#1241, PR unisdr/undrr-mangrove#1247)"
        },
        {
          "category": "General",
          "title": "The repository's package root exports resolve again, and are now tested.",
          "description": "`src/index.js`, the file this repo's `package.json` names as `main` and as the `.` entry in `exports`, had two broken re-exports. `IconCard` was re-exported as a default that `IconCard.jsx` does not have, so the binding was `undefined` and rendering it threw `Element type is invalid`; it now uses the named form, matching the component and its hydration barrel. `CookieConsentBanner` was re-exported from a directory with no index file, which does not fail one binding but the whole module: importing anything from the entry point raised `Cannot find module`. That one had been broken since the component landed in June 2025. Neither had anything checking it, so a new test imports the entry point and asserts that no export is `undefined`. Hydration through `*.hydrate.js` was never affected. **This is not yet a consumer-visible fix.** The published npm tarball is assembled by `scripts/assemble-npm-package.mjs`, which ships neither `src/` nor `dist/` and writes a `package.json` with `main: \"dist/index.js\"` and no `exports` field, so on every published version to date, including `1.8.2` (`latest`) and `2.0.0-rc.2` (`next`), `import … from '@undrr/undrr-mangrove'` fails with `ERR_MODULE_NOT_FOUND` before any of these bindings is reached. Consumers of the package import the per-component subpaths (`@undrr/undrr-mangrove/components/IconCard.js`) instead. Making the package root resolve for them is a separate packaging fix. (unisdr/undrr-mangrove#1240, PR unisdr/undrr-mangrove#1247)",
          "pr": 1240,
          "prRef": "unisdr/undrr-mangrove#1240",
          "prUrl": null,
          "raw": "**The repository's package root exports resolve again, and are now tested.** `src/index.js`, the file this repo's `package.json` names as `main` and as the `.` entry in `exports`, had two broken re-exports. `IconCard` was re-exported as a default that `IconCard.jsx` does not have, so the binding was `undefined` and rendering it threw `Element type is invalid`; it now uses the named form, matching the component and its hydration barrel. `CookieConsentBanner` was re-exported from a directory with no index file, which does not fail one binding but the whole module: importing anything from the entry point raised `Cannot find module`. That one had been broken since the component landed in June 2025. Neither had anything checking it, so a new test imports the entry point and asserts that no export is `undefined`. Hydration through `*.hydrate.js` was never affected. **This is not yet a consumer-visible fix.** The published npm tarball is assembled by `scripts/assemble-npm-package.mjs`, which ships neither `src/` nor `dist/` and writes a `package.json` with `main: \"dist/index.js\"` and no `exports` field, so on every published version to date, including `1.8.2` (`latest`) and `2.0.0-rc.2` (`next`), `import … from '@undrr/undrr-mangrove'` fails with `ERR_MODULE_NOT_FOUND` before any of these bindings is reached. Consumers of the package import the per-component subpaths (`@undrr/undrr-mangrove/components/IconCard.js`) instead. Making the package root resolve for them is a separate packaging fix. (unisdr/undrr-mangrove#1240, PR unisdr/undrr-mangrove#1247)"
        },
        {
          "category": "General",
          "title": "Three components that render caller HTML now say so:",
          "description": "`MegaMenu` (`bannerDescription`, at the desktop banner, the no-items call-to-action panel and the mobile sidebar), `Tab` (a tab object's `data`) and `QuoteHighlight` (`quote`, `attribution`, `attributionTitle`) all render caller-supplied markup with `dangerouslySetInnerHTML` and none of them sanitises it. That is unchanged; this is documentation only, with no rendering change and no sanitisation added. Each now carries a block comment stating the contract, a pointer at every render site, prop documentation, a named section in its MDX placed where a consumer configuring that prop arrives, and a line in its AI manifest entry. The reason for writing it down is that the repo has a convention these three sit outside of: `dompurify` is a direct dependency and `VerticalCard`, `HorizontalCard`, `HorizontalBookCard`, `IconCard`, `StatsCardItem`, `Drawer` and `TextCta` all sanitise internally, so a reader who knows that code reads these sites as an oversight rather than a decision. To be precise: these are caller-supplied props, so the trust boundary is the consuming application's own code (a CMS output filter in most integrations), not a network response, which is what makes them a weaker case than unisdr/undrr-mangrove#1254. They are undocumented contracts, not vulnerabilities. Two things found while writing it: `Sidebar.jsx` already pointed at \"the same caller-sanitised HTML contract as the existing desktop banner\", which had never been written down anywhere, and `Tab`'s second `dangerouslySetInnerHTML` is a fixed string literal (an HTML comment JSX cannot otherwise emit) rather than caller input, now labelled as such so an audit does not have to work that out twice. `QuoteHighlight`'s manifest hydration note had carried the contract correctly for some time and was the only place it appeared. Superseded for `QuoteHighlight`, which now sanitises (see the entry above); the `MegaMenu` and `Tab` contracts stand as written. (PR unisdr/undrr-mangrove#1258)",
          "pr": 1254,
          "prRef": "unisdr/undrr-mangrove#1254",
          "prUrl": null,
          "raw": "**Three components that render caller HTML now say so:** `MegaMenu` (`bannerDescription`, at the desktop banner, the no-items call-to-action panel and the mobile sidebar), `Tab` (a tab object's `data`) and `QuoteHighlight` (`quote`, `attribution`, `attributionTitle`) all render caller-supplied markup with `dangerouslySetInnerHTML` and none of them sanitises it. That is unchanged; this is documentation only, with no rendering change and no sanitisation added. Each now carries a block comment stating the contract, a pointer at every render site, prop documentation, a named section in its MDX placed where a consumer configuring that prop arrives, and a line in its AI manifest entry. The reason for writing it down is that the repo has a convention these three sit outside of: `dompurify` is a direct dependency and `VerticalCard`, `HorizontalCard`, `HorizontalBookCard`, `IconCard`, `StatsCardItem`, `Drawer` and `TextCta` all sanitise internally, so a reader who knows that code reads these sites as an oversight rather than a decision. To be precise: these are caller-supplied props, so the trust boundary is the consuming application's own code (a CMS output filter in most integrations), not a network response, which is what makes them a weaker case than unisdr/undrr-mangrove#1254. They are undocumented contracts, not vulnerabilities. Two things found while writing it: `Sidebar.jsx` already pointed at \"the same caller-sanitised HTML contract as the existing desktop banner\", which had never been written down anywhere, and `Tab`'s second `dangerouslySetInnerHTML` is a fixed string literal (an HTML comment JSX cannot otherwise emit) rather than caller input, now labelled as such so an audit does not have to work that out twice. `QuoteHighlight`'s manifest hydration note had carried the contract correctly for some time and was the only place it appeared. Superseded for `QuoteHighlight`, which now sanitises (see the entry above); the `MegaMenu` and `Tab` contracts stand as written. (PR unisdr/undrr-mangrove#1258)"
        },
        {
          "category": "General",
          "title": "The half-scrollbar overhang on full-bleed components is documented, not fixed in the library:",
          "description": "every full-bleed escape Mangrove ships (`mg-hero`, `mg-hub-header`, `mg-on-this-page-nav`, `mg-container-full-width`) sizes itself with `100vw`. `vw` counts the classic vertical scrollbar and the containing block does not, so on any page that scrolls vertically each escape overhangs by half a scrollbar on both sides and the page picks up about 8px of horizontal scroll, in both directions, on every variant. Nothing inside a component can measure the scrollbar; only the page root can absorb it, with `overflow-x: clip` on both `html` and `body`. That rule is **not** shipped. It ends the sideways scroll by making anything wider than the page unreachable (no scrollbar, and `scrollIntoView()` cannot pan to it either), and the library cannot see what a consuming site renders. On the UNDRR platform specifically, Drupal's `views-view-table.html.twig` emits `<div class=\"table-responsive\">` and no UNDRR theme styles that class, so wide Views tables are read today by scrolling the page sideways; shipping the clip would have cut that content off on nine sites. The rule, the reasoning and its cost are now written up under [page-level setup](https://mangrove.undrr.org/?path=/docs/components-fullwidth--docs#page-level-setup) for a site to adopt deliberately, with pointers from the Hero docs and from Best practices. Includes why both `html` and `body` are load-bearing (either alone leaves the overflow entirely intact), why it must be `clip` and never `hidden` (`hidden` makes the page a scroll container and detaches `position: sticky`), and that Safari below 16 keeps today's behaviour. See unisdr/undrr-mangrove#1203.",
          "pr": 1203,
          "prRef": "unisdr/undrr-mangrove#1203",
          "prUrl": null,
          "raw": "**The half-scrollbar overhang on full-bleed components is documented, not fixed in the library:** every full-bleed escape Mangrove ships (`mg-hero`, `mg-hub-header`, `mg-on-this-page-nav`, `mg-container-full-width`) sizes itself with `100vw`. `vw` counts the classic vertical scrollbar and the containing block does not, so on any page that scrolls vertically each escape overhangs by half a scrollbar on both sides and the page picks up about 8px of horizontal scroll, in both directions, on every variant. Nothing inside a component can measure the scrollbar; only the page root can absorb it, with `overflow-x: clip` on both `html` and `body`. That rule is **not** shipped. It ends the sideways scroll by making anything wider than the page unreachable (no scrollbar, and `scrollIntoView()` cannot pan to it either), and the library cannot see what a consuming site renders. On the UNDRR platform specifically, Drupal's `views-view-table.html.twig` emits `<div class=\"table-responsive\">` and no UNDRR theme styles that class, so wide Views tables are read today by scrolling the page sideways; shipping the clip would have cut that content off on nine sites. The rule, the reasoning and its cost are now written up under [page-level setup](https://mangrove.undrr.org/?path=/docs/components-fullwidth--docs#page-level-setup) for a site to adopt deliberately, with pointers from the Hero docs and from Best practices. Includes why both `html` and `body` are load-bearing (either alone leaves the overflow entirely intact), why it must be `clip` and never `hidden` (`hidden` makes the page a scroll container and detaches `position: sticky`), and that Safari below 16 keeps today's behaviour. See unisdr/undrr-mangrove#1203."
        },
        {
          "category": "General",
          "title": "The AI manifest counts its global custom-property groups instead of stating a number:",
          "description": "`llms.txt` generated its list of global `--mg-*` groups from `GLOBAL_PREFIXES` but introduced it with a hand-typed \"Four groups\", so adding a group left the document claiming four and listing five. The count is now derived from the same data as the list, and `tokens.json`'s `scope.excludes` and `docs/AI-MCP-INTEGRATION.md` say \"every group\" rather than \"all four\". No group changed, so every generated document is byte-identical. (unisdr/undrr-mangrove#1203)",
          "pr": 1203,
          "prRef": "unisdr/undrr-mangrove#1203",
          "prUrl": null,
          "raw": "**The AI manifest counts its global custom-property groups instead of stating a number:** `llms.txt` generated its list of global `--mg-*` groups from `GLOBAL_PREFIXES` but introduced it with a hand-typed \"Four groups\", so adding a group left the document claiming four and listing five. The count is now derived from the same data as the list, and `tokens.json`'s `scope.excludes` and `docs/AI-MCP-INTEGRATION.md` say \"every group\" rather than \"all four\". No group changed, so every generated document is byte-identical. (unisdr/undrr-mangrove#1203)"
        },
        {
          "category": "General",
          "title": "The syndication search widget's endpoint trust contract is now written down:",
          "description": "`SyndicationSearchWidget` renders the search response as HTML at three sites in `ResultItem.jsx` and sanitises none of it, because Elasticsearch wraps matched terms in `<em>` and escaping the response would print those tags instead of emphasising the match. That is the right call and it is unchanged here; this is a documentation-only note, with no rendering change and no sanitisation added. It is worth recording because `searchEndpoint` is consumer-configurable, through the config prop and through `data-search-endpoint`, so pointing it at a different service means rendering that service's HTML unfiltered; and because a syndication widget renders into partner domains rather than only UNDRR's. The three sites now carry the reasoning, the component MDX documents the contract next to the endpoint configuration, and the AI manifest entry states it for agents wiring the widget up from the manifest alone. Scope note for anyone reading the `react/no-danger` backlog in `eslint.config.mjs`: this closes the ResultItem part of that audit in prose, not by adding suppressions; the rule is off on `main` today, and these sites keep plain comments so nothing goes stale either way. (PR unisdr/undrr-mangrove#1254)",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "raw": "**The syndication search widget's endpoint trust contract is now written down:** `SyndicationSearchWidget` renders the search response as HTML at three sites in `ResultItem.jsx` and sanitises none of it, because Elasticsearch wraps matched terms in `<em>` and escaping the response would print those tags instead of emphasising the match. That is the right call and it is unchanged here; this is a documentation-only note, with no rendering change and no sanitisation added. It is worth recording because `searchEndpoint` is consumer-configurable, through the config prop and through `data-search-endpoint`, so pointing it at a different service means rendering that service's HTML unfiltered; and because a syndication widget renders into partner domains rather than only UNDRR's. The three sites now carry the reasoning, the component MDX documents the contract next to the endpoint configuration, and the AI manifest entry states it for agents wiring the widget up from the manifest alone. Scope note for anyone reading the `react/no-danger` backlog in `eslint.config.mjs`: this closes the ResultItem part of that audit in prose, not by adding suppressions; the rule is off on `main` today, and these sites keep plain comments so nothing goes stale either way. (PR unisdr/undrr-mangrove#1254)"
        },
        {
          "category": "General",
          "title": "The manifest states each component's lifecycle instead of implying it:",
          "description": "`index.json` distinguished `vanillaHtml`, `vanillaModule` and `hydration`, but `vanillaModule` was only emitted when there was one. \"Vanilla HTML with no lifecycle module\" and \"vanilla HTML plus a module\" were therefore the same entry, and the only way to tell them apart was to fetch `/js/{guess}.js` and see whether it 404'd. `vanillaModule` is now a boolean on every component, `vanillaScripts` lists the files to load when it is true (each one the `file` of a `library.vanillaScripts` entry), and the detail file carries either the full contract or `false` plus an optional `vanillaModuleNote` saying why there is none. Reported by the `undrr-risk-resilience-maps` team (unisdr/undrr-mangrove#1197).",
          "pr": 1197,
          "prRef": "unisdr/undrr-mangrove#1197",
          "prUrl": null,
          "raw": "**The manifest states each component's lifecycle instead of implying it:** `index.json` distinguished `vanillaHtml`, `vanillaModule` and `hydration`, but `vanillaModule` was only emitted when there was one. \"Vanilla HTML with no lifecycle module\" and \"vanilla HTML plus a module\" were therefore the same entry, and the only way to tell them apart was to fetch `/js/{guess}.js` and see whether it 404'd. `vanillaModule` is now a boolean on every component, `vanillaScripts` lists the files to load when it is true (each one the `file` of a `library.vanillaScripts` entry), and the detail file carries either the full contract or `false` plus an optional `vanillaModuleNote` saying why there is none. Reported by the `undrr-risk-resilience-maps` team (unisdr/undrr-mangrove#1197)."
        },
        {
          "category": "General",
          "title": "`yarn validate-manifest` checks the lifecycle claim against the files on disk:",
          "description": "a `hydration` or `vanillaModule` contract naming a module under `js/` now fails the build unless `stories/assets/js/{name}.js` exists, so the manifest cannot advertise a URL that 404s or survive a rename. The `VANILLA_SCRIPTS` catalogue is checked in both directions: every entry must name a real file, and every module that ships must be listed. The second half caught `js/copy-button.js`, which shipped and was named by a component contract but was missing from the list; `js/drawer.js` and `js/table-of-contents.js` are listed too. A component cannot carry both a `vanillaModule` contract and a `vanillaModuleNote` explaining that it has none. The check reads the source tree, so it runs the same with or without a build, and `vanillaModules(name)` in `component-data.js` now builds the module URLs the way `hydrationModules(name)` already did, which is what makes a claim resolvable back to a file (unisdr/undrr-mangrove#1197).",
          "pr": 1197,
          "prRef": "unisdr/undrr-mangrove#1197",
          "prUrl": null,
          "raw": "**`yarn validate-manifest` checks the lifecycle claim against the files on disk:** a `hydration` or `vanillaModule` contract naming a module under `js/` now fails the build unless `stories/assets/js/{name}.js` exists, so the manifest cannot advertise a URL that 404s or survive a rename. The `VANILLA_SCRIPTS` catalogue is checked in both directions: every entry must name a real file, and every module that ships must be listed. The second half caught `js/copy-button.js`, which shipped and was named by a component contract but was missing from the list; `js/drawer.js` and `js/table-of-contents.js` are listed too. A component cannot carry both a `vanillaModule` contract and a `vanillaModuleNote` explaining that it has none. The check reads the source tree, so it runs the same with or without a build, and `vanillaModules(name)` in `component-data.js` now builds the module URLs the way `hydrationModules(name)` already did, which is what makes a claim resolvable back to a file (unisdr/undrr-mangrove#1197)."
        },
        {
          "category": "General",
          "title": "Browser-driven checks of the cookie consent bar need `hideFromBots: false`:",
          "description": "vanilla-cookieconsent's `hideFromBots` option defaults to `true`, and the UNDRR CDN configuration leaves it that way. When it is on and `navigator.webdriver` is `true` (any Playwright, Puppeteer, Selenium or CDP-driven session), the library returns from `run()` before creating a single node, while still reporting success: `window.CookieConsent` is a populated object and the CDN configuration script logs `Cookie banner initialized successfully`. This is why the bar was reported as never rendering in Storybook when it renders for a person, and it affects anything automated that expects to see it, not just the `CookieConsentBanner` stories. See unisdr/undrr-mangrove#1202, unisdr/undrr-mangrove#1242, and the component's \"Rendering check\" story for the pattern that asserts the bar for real.",
          "pr": 1202,
          "prRef": "unisdr/undrr-mangrove#1202",
          "prUrl": null,
          "raw": "**Browser-driven checks of the cookie consent bar need `hideFromBots: false`:** vanilla-cookieconsent's `hideFromBots` option defaults to `true`, and the UNDRR CDN configuration leaves it that way. When it is on and `navigator.webdriver` is `true` (any Playwright, Puppeteer, Selenium or CDP-driven session), the library returns from `run()` before creating a single node, while still reporting success: `window.CookieConsent` is a populated object and the CDN configuration script logs `Cookie banner initialized successfully`. This is why the bar was reported as never rendering in Storybook when it renders for a person, and it affects anything automated that expects to see it, not just the `CookieConsentBanner` stories. See unisdr/undrr-mangrove#1202, unisdr/undrr-mangrove#1242, and the component's \"Rendering check\" story for the pattern that asserts the bar for real."
        },
        {
          "category": "General",
          "title": "Show more target lookups are ordered, and the unisdr/undrr-mangrove#1222 reasoning is corrected:",
          "description": "`show-more.js` resolved its target with `document.querySelector`, so two toggles left on the default `.mg-show-more--container` both drove the first block on the page. It now climbs the toggle's ancestors and takes the first match inside one, falling back to the document if the walk reaches `<html>` without matching. The unisdr/undrr-mangrove#1222 note above said there was \"no container to scope to\" because revealed content need not be a sibling or descendant of its toggle. The premise holds, but it rules out _confining_ the lookup, not _ordering_ it, and an ancestor walk is never worse than the document-wide one (it is the same answer whenever the target is genuinely elsewhere, and the right one whenever two toggles share a selector). That note now says scoping was judged out of scope for the sweep rather than impossible. The React `ShowMore` wrapper needed a different fix: its items are flat siblings, so the walk cannot separate two that share a `collapsable_wrapper_class`; each item now carries a minted id and its toggle targets that. Detail in the ShowMore changelog. (unisdr/undrr-mangrove#1238, unisdr/undrr-mangrove#1228)",
          "pr": 1238,
          "prRef": "unisdr/undrr-mangrove#1238",
          "prUrl": null,
          "raw": "**Show more target lookups are ordered, and the unisdr/undrr-mangrove#1222 reasoning is corrected:** `show-more.js` resolved its target with `document.querySelector`, so two toggles left on the default `.mg-show-more--container` both drove the first block on the page. It now climbs the toggle's ancestors and takes the first match inside one, falling back to the document if the walk reaches `<html>` without matching. The unisdr/undrr-mangrove#1222 note above said there was \"no container to scope to\" because revealed content need not be a sibling or descendant of its toggle. The premise holds, but it rules out _confining_ the lookup, not _ordering_ it, and an ancestor walk is never worse than the document-wide one (it is the same answer whenever the target is genuinely elsewhere, and the right one whenever two toggles share a selector). That note now says scoping was judged out of scope for the sweep rather than impossible. The React `ShowMore` wrapper needed a different fix: its items are flat siblings, so the walk cannot separate two that share a `collapsable_wrapper_class`; each item now carries a minted id and its toggle targets that. Detail in the ShowMore changelog. (unisdr/undrr-mangrove#1238, unisdr/undrr-mangrove#1228)"
        },
        {
          "category": "General",
          "title": "Curated component descriptions now reach the AI manifest:",
          "description": "`getDescription()` resolved the component's own docblock ahead of the curated `description` in `scripts/ai-manifest/component-data.js`, so guidance written for the manifest never reached `index.json`, the file an agent greps first. An agent searching the index for \"icon button\" found nothing, even after unisdr/undrr-mangrove#1225 wrote the two icon-only classes into the Buttons entry. The curated description now wins everywhere, which also retires the narrower `patterns-*` exception added for unisdr/undrr-mangrove#1129. `summary` and `description` are no longer the same string: component entries take an optional one-sentence `summary` for the index listing, required once the description runs past 200 characters and enforced by `yarn validate-manifest` and by `scripts/__tests__/component-data.test.js`. 37 descriptions and 50 summaries changed; the descriptions that lost useful docblock detail (the `crop=\"autocrop\"` warning on Logos, the accepted icon name formats, the Cloudflare static templates on the error pages, and four more) had it folded into the curated text. Auto-rendered HTML examples are also named after the component instead of carrying the whole curated description as their `name`. (unisdr/undrr-mangrove#1237)",
          "pr": 1225,
          "prRef": "unisdr/undrr-mangrove#1225",
          "prUrl": null,
          "raw": "**Curated component descriptions now reach the AI manifest:** `getDescription()` resolved the component's own docblock ahead of the curated `description` in `scripts/ai-manifest/component-data.js`, so guidance written for the manifest never reached `index.json`, the file an agent greps first. An agent searching the index for \"icon button\" found nothing, even after unisdr/undrr-mangrove#1225 wrote the two icon-only classes into the Buttons entry. The curated description now wins everywhere, which also retires the narrower `patterns-*` exception added for unisdr/undrr-mangrove#1129. `summary` and `description` are no longer the same string: component entries take an optional one-sentence `summary` for the index listing, required once the description runs past 200 characters and enforced by `yarn validate-manifest` and by `scripts/__tests__/component-data.test.js`. 37 descriptions and 50 summaries changed; the descriptions that lost useful docblock detail (the `crop=\"autocrop\"` warning on Logos, the accepted icon name formats, the Cloudflare static templates on the error pages, and four more) had it folded into the curated text. Auto-rendered HTML examples are also named after the component instead of carrying the whole curated description as their `name`. (unisdr/undrr-mangrove#1237)"
        },
        {
          "category": "General",
          "title": "`yarn lint` no longer deletes `eslint-disable` comments:",
          "description": "ESLint's flat config defaults `linterOptions.reportUnusedDisableDirectives` to `warn`, and `--fix` acts on that warning, so `yarn lint:js` silently removed any suppression naming a rule the config does not enable, leaving trailing whitespace that `yarn prettier:check` rejected while `yarn lint:check` still exited 0. All 15 `eslint-disable` comments in the repo name rules `eslint.config.mjs` does not enable, and several of those rules it turns off on purpose, so enabling rules one by one was not the fix. Only two of the 15 were ever at risk, because the other 13 sit in `.jsx` files ESLint currently skips entirely (unisdr/undrr-mangrove#1235); they become live the day that coverage lands. `lint:js` now passes `--fix-type problem,suggestion,layout`, which omits `directive`, and `lint:check` drops `--quiet` so a genuinely stale directive is reported instead of deleted. `no-control-regex` is enabled, which makes the one suppression in a linted file real. A new `scripts/__tests__/lint-scripts.test.js` holds that wiring, and CI now runs `yarn lint`, `yarn prettier:check` and `git diff --exit-code` together so the fixing command can never again produce a tree the format check rejects. (unisdr/undrr-mangrove#1232)",
          "pr": 1235,
          "prRef": "unisdr/undrr-mangrove#1235",
          "prUrl": null,
          "raw": "**`yarn lint` no longer deletes `eslint-disable` comments:** ESLint's flat config defaults `linterOptions.reportUnusedDisableDirectives` to `warn`, and `--fix` acts on that warning, so `yarn lint:js` silently removed any suppression naming a rule the config does not enable, leaving trailing whitespace that `yarn prettier:check` rejected while `yarn lint:check` still exited 0. All 15 `eslint-disable` comments in the repo name rules `eslint.config.mjs` does not enable, and several of those rules it turns off on purpose, so enabling rules one by one was not the fix. Only two of the 15 were ever at risk, because the other 13 sit in `.jsx` files ESLint currently skips entirely (unisdr/undrr-mangrove#1235); they become live the day that coverage lands. `lint:js` now passes `--fix-type problem,suggestion,layout`, which omits `directive`, and `lint:check` drops `--quiet` so a genuinely stale directive is reported instead of deleted. `no-control-regex` is enabled, which makes the one suppression in a linted file real. A new `scripts/__tests__/lint-scripts.test.js` holds that wiring, and CI now runs `yarn lint`, `yarn prettier:check` and `git diff --exit-code` together so the fixing command can never again produce a tree the format check rejects. (unisdr/undrr-mangrove#1232)"
        },
        {
          "category": "General",
          "title": "Breaking: the four CamelCase SCSS aliases removed.",
          "description": "`ShowMore.scss`, `FullWidth.scss`, `TableOfContents.scss` and `SyndicationSearchWidget.scss` each existed only to `@import` their kebab-case sibling and `@warn` that they were deprecated. All four are deleted, so the npm package no longer ships `scss/Utilities/ShowMore/ShowMore.scss`, `scss/Utilities/FullWidth/FullWidth.scss`, `scss/Components/TableOfContents/TableOfContents.scss` or `scss/Components/SyndicationSearchWidget/SyndicationSearchWidget.scss`. Nothing in the library imported them (`stories/assets/scss/_components.scss` already imports the kebab-case files); they shipped only because the package assembly copies every `stories/**/*.scss`. Consumers importing a CamelCase path switch to the kebab-case one (`show-more`, `full-width`, `table-of-contents`, `syndication-search-widget`), which is the same stylesheet the alias forwarded to, so the compiled CSS is unchanged. Dropping one and keeping three would give consumers a break they could not generalise from, and a major release is the moment to take all four. Migration table in [the 2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs). (unisdr/undrr-mangrove#1193)",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null,
          "raw": "**Breaking: the four CamelCase SCSS aliases removed.** `ShowMore.scss`, `FullWidth.scss`, `TableOfContents.scss` and `SyndicationSearchWidget.scss` each existed only to `@import` their kebab-case sibling and `@warn` that they were deprecated. All four are deleted, so the npm package no longer ships `scss/Utilities/ShowMore/ShowMore.scss`, `scss/Utilities/FullWidth/FullWidth.scss`, `scss/Components/TableOfContents/TableOfContents.scss` or `scss/Components/SyndicationSearchWidget/SyndicationSearchWidget.scss`. Nothing in the library imported them (`stories/assets/scss/_components.scss` already imports the kebab-case files); they shipped only because the package assembly copies every `stories/**/*.scss`. Consumers importing a CamelCase path switch to the kebab-case one (`show-more`, `full-width`, `table-of-contents`, `syndication-search-widget`), which is the same stylesheet the alias forwarded to, so the compiled CSS is unchanged. Dropping one and keeping three would give consumers a break they could not generalise from, and a major release is the moment to take all four. Migration table in [the 2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs). (unisdr/undrr-mangrove#1193)"
        },
        {
          "category": "General",
          "title": "Story fixture hygiene:",
          "description": "a sweep of demo-only defects found in the 2.0 polish audit: a Legend \"Dark theme\" story that depended on a class the library never defined, a dead `via.placeholder.com` image, three demo pages that scrolled a 375px viewport sideways (including a ScrollContainer story that asked for a 1500px minimum width in an unbounded parent), colour inventories with no contrast caveat, and an empty CookieConsentBanner canvas with nothing explaining it. Each is recorded in the affected component's own changelog. The CookieConsentBanner \"Default\" story now states in the canvas that the component renders nothing itself and that the consent bar is drawn by the CDN library, which does not show it inside Storybook. (unisdr/undrr-mangrove#1193)",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null,
          "raw": "**Story fixture hygiene:** a sweep of demo-only defects found in the 2.0 polish audit: a Legend \"Dark theme\" story that depended on a class the library never defined, a dead `via.placeholder.com` image, three demo pages that scrolled a 375px viewport sideways (including a ScrollContainer story that asked for a 1500px minimum width in an unbounded parent), colour inventories with no contrast caveat, and an empty CookieConsentBanner canvas with nothing explaining it. Each is recorded in the affected component's own changelog. The CookieConsentBanner \"Default\" story now states in the canvas that the component renders nothing itself and that the consent bar is drawn by the CDN library, which does not show it inside Storybook. (unisdr/undrr-mangrove#1193)"
        },
        {
          "category": "General",
          "title": "Documentation links resolve again:",
          "description": "the review-checklist banner on all 75 component docs pages linked a relative repository path (`../../../docs/REVIEW-CHECKLIST.md`), which 404s in a built Storybook; it now links the published [review checklist](https://mangrove.undrr.org/?path=/docs/contributing-build-a-component-review-checklist--docs), as does the template in `docs/COMPONENT-GUIDE.md`. Six links pointed at story ids or `<LinkTo>` kinds the rc.1 regrouping renamed, and 24 more used a host-absolute `/docs/…--docs` path that is not a Storybook route. The guides under `docs/` are read on GitHub as well as in Storybook, so they keep their relative `FILE.md` links and the wrappers in `stories/Documentation/` now rewrite them at import time (`stories/Documentation/docsPageLinks.js`); the review checklist's own links work on both. A new `scripts/__tests__/docs-links.test.js` fails the build on a Markdown link or a `<LinkTo kind>` that resolves to no story, on either broken path form, and on a guide whose rewrite is missing or stale. (unisdr/undrr-mangrove#1191)",
          "pr": 1191,
          "prRef": "unisdr/undrr-mangrove#1191",
          "prUrl": null,
          "raw": "**Documentation links resolve again:** the review-checklist banner on all 75 component docs pages linked a relative repository path (`../../../docs/REVIEW-CHECKLIST.md`), which 404s in a built Storybook; it now links the published [review checklist](https://mangrove.undrr.org/?path=/docs/contributing-build-a-component-review-checklist--docs), as does the template in `docs/COMPONENT-GUIDE.md`. Six links pointed at story ids or `<LinkTo>` kinds the rc.1 regrouping renamed, and 24 more used a host-absolute `/docs/…--docs` path that is not a Storybook route. The guides under `docs/` are read on GitHub as well as in Storybook, so they keep their relative `FILE.md` links and the wrappers in `stories/Documentation/` now rewrite them at import time (`stories/Documentation/docsPageLinks.js`); the review checklist's own links work on both. A new `scripts/__tests__/docs-links.test.js` fails the build on a Markdown link or a `<LinkTo kind>` that resolves to no story, on either broken path form, and on a guide whose rewrite is missing or stale. (unisdr/undrr-mangrove#1191)"
        },
        {
          "category": "General",
          "title": "Documentation accuracy:",
          "description": "the CopyButton vanilla snippet pointed at `dist/js/copy-button.min.js`, which the CDN does not serve, and loaded an ES module without `type=\"module\"`; the contribution and accessibility guides taught `$mg-color-*` / `$mg-spacing-*` SCSS variables that 2.0 removed, and `$mg-text-color` / `$mg-link-color`, which never existed. The Brand guidelines page no longer nests a `<p>` inside a `<p>`, the only console error on any docs page, and the Loader page no longer carries a work-in-progress caveat. The Component gallery and the Content syndication page no longer advertise `BarChart`, `MapComponent` and `Fetcher`, removed in 1.8.0. (unisdr/undrr-mangrove#1191)",
          "pr": 1191,
          "prRef": "unisdr/undrr-mangrove#1191",
          "prUrl": null,
          "raw": "**Documentation accuracy:** the CopyButton vanilla snippet pointed at `dist/js/copy-button.min.js`, which the CDN does not serve, and loaded an ES module without `type=\"module\"`; the contribution and accessibility guides taught `$mg-color-*` / `$mg-spacing-*` SCSS variables that 2.0 removed, and `$mg-text-color` / `$mg-link-color`, which never existed. The Brand guidelines page no longer nests a `<p>` inside a `<p>`, the only console error on any docs page, and the Loader page no longer carries a work-in-progress caveat. The Component gallery and the Content syndication page no longer advertise `BarChart`, `MapComponent` and `Fetcher`, removed in 1.8.0. (unisdr/undrr-mangrove#1191)"
        },
        {
          "category": "General",
          "title": "AI manifest hydration coverage:",
          "description": "every component that ships a `*.hydrate.js` now carries a structured `hydration` contract: note, selector, CDN modules, data attributes, events and a runnable example. Previously only ServiceNotice and Tree did, so an agent copying `renderedHtml` for CopyButton, ShareButtons, IconCard, StatsCard, Gallery, MegaMenu, Pager, QuoteHighlight, ScrollContainer, TextCta, UserFeedback, Syndicated search, Drawer or Notice got markup with no behaviour and nothing saying so. CopyButton also gains a `vanillaModule` contract for `js/copy-button.js`. ShareButtons, Gallery, MegaMenu, Pager, ScrollContainer and Syndicated search had no `component-data.js` entry at all and gain one. (unisdr/undrr-mangrove#1194)",
          "pr": 1194,
          "prRef": "unisdr/undrr-mangrove#1194",
          "prUrl": null,
          "raw": "**AI manifest hydration coverage:** every component that ships a `*.hydrate.js` now carries a structured `hydration` contract: note, selector, CDN modules, data attributes, events and a runnable example. Previously only ServiceNotice and Tree did, so an agent copying `renderedHtml` for CopyButton, ShareButtons, IconCard, StatsCard, Gallery, MegaMenu, Pager, QuoteHighlight, ScrollContainer, TextCta, UserFeedback, Syndicated search, Drawer or Notice got markup with no behaviour and nothing saying so. CopyButton also gains a `vanillaModule` contract for `js/copy-button.js`. ShareButtons, Gallery, MegaMenu, Pager, ScrollContainer and Syndicated search had no `component-data.js` entry at all and gain one. (unisdr/undrr-mangrove#1194)"
        },
        {
          "category": "General",
          "title": "Drawer, Tree and Legend docs:",
          "description": "the Drawer and Tree docs pages embed their stories instead of showing prose and a props table with no example, the placeholder \"Item 1 / Item 2\" and \"Drawer content goes here.\" copy is replaced with realistic content, and Legend's duplicated canvas is gone and the page is filled out to match its peers. (unisdr/undrr-mangrove#1194)",
          "pr": 1194,
          "prRef": "unisdr/undrr-mangrove#1194",
          "prUrl": null,
          "raw": "**Drawer, Tree and Legend docs:** the Drawer and Tree docs pages embed their stories instead of showing prose and a props table with no example, the placeholder \"Item 1 / Item 2\" and \"Drawer content goes here.\" copy is replaced with realistic content, and Legend's duplicated canvas is gone and the page is filled out to match its peers. (unisdr/undrr-mangrove#1194)"
        },
        {
          "category": "General",
          "title": "The orange accent stops carrying text, and seven surfaces change visibly:",
          "description": "this repaints `Tag --accent`, `TextCta --secondary`, `Hero --secondary`, `HubHeader --surface-secondary`, `Card --secondary`, `AuthorImage --secondary` and the PreventionWeb and IRP primary button hover, on every site that uses them, in all five themes. **No orange token value changed**: `orange-900` is still `#eb752a`, `orange-800` still `#ed833f`, and `secondary`, `tag-accent`, `tag-accent--hover` and `hero--secondary` still resolve to them. What changed is the role: the orange is now only a border, a rule or a block with no copy over it, and every surface that used to carry text on it carries that text somewhere that passes. White on `#eb752a` is 2.95 and on `#ed833f` 2.65, against WCAG's 4.5; black passes WCAG at 7.11 and 7.92 but misses the 63 perceptual body-text floor at 57.4 and 61.1. No text colour carries body copy on this surface, so the copy moved instead. Surface by surface: the accent tag is `orange-50` (hover `orange-100`) with a `--mg-color-text` label at 15.7:1 and the orange as its border; `TextCta --secondary` takes the `soft` surface (white under an 8% wash of the accent) with dark eyebrow, headline and body and a solid orange leading rule, so its 90%-white body (2.42) is gone; `Hero --secondary` left the variant colour map and keeps the theme's hero colour in both layouts, with the default hero colour for its CTA ink and an 8px orange rule; `HubHeader --surface-secondary` no longer retargets the surface, so bar and banner stay the theme hero colour with the orange as a rule beneath the bar; `Card --secondary` gives its title the interactive colour like any other card link and takes a 4px accent rule; `AuthorImage --secondary` keeps the orange avatar ring and hover tint but hands the title to `--mg-color-text` and the focus outline to the standard focus ring. The seventh (unisdr/undrr-mangrove#1243) is why this belongs in the token layer rather than in each component: PreventionWeb and IRP had re-pointed `color.button-background--hover` and `color.button-outline-primary--hover` at `{color.orange-900}`, putting a white button label on the accent at 2.95:1 without any component choosing an orange background. Those hovers now use each brand's own deeper primitive: PreventionWeb `brand.teal-800` (#0c7575, 5.5:1 / 69.7) and IRP `brand.blue-800` (#0d67a3, 6.02:1 / 71.1), the same ones their secondary buttons already used. An orange rule is decoration, not identification: `#eb752a` is 2.95 on white and 2.66 on `orange-50`, both under the 3:1 of SC 1.4.11, so wherever the library now draws one, a fill, a label or a position carries the meaning alongside it. Darkening the accent to about `#b2571d` was prototyped and measured: it passes at 4.91 / 64.1, but it is 14.4 dE2000 from Sendai orange against a just-noticeable difference of about 2.3 (a rust, not the brand colour), and it would still have left the CTA's 90%-white body at 4.29, so the surface did not move. **If you overrode `--mg-color-tag-accent`, `--mg-color-tag-accent--hover`, `--mg-color-secondary` or `--mg-color-hero--secondary` in your own theme layer to work around the old failures, remove the override.** Those tokens are now read as a rule colour, not a text surface, so an override that darkened them will darken a border rather than fix anything, and a light override will leave the rule invisible. If you set `--tag-color-background-accent` to a dark fill, set `--mg-tag-foreground` with it: the accent label is now dark. The pairs are asserted in `stories/assets/scss/__tests__/tokens-contract.test.js`, so none of them can drift back. Per-component notes are in each component's MDX changelog. (unisdr/undrr-mangrove#1249)",
          "pr": 1243,
          "prRef": "unisdr/undrr-mangrove#1243",
          "prUrl": null,
          "raw": "**The orange accent stops carrying text, and seven surfaces change visibly:** this repaints `Tag --accent`, `TextCta --secondary`, `Hero --secondary`, `HubHeader --surface-secondary`, `Card --secondary`, `AuthorImage --secondary` and the PreventionWeb and IRP primary button hover, on every site that uses them, in all five themes. **No orange token value changed**: `orange-900` is still `#eb752a`, `orange-800` still `#ed833f`, and `secondary`, `tag-accent`, `tag-accent--hover` and `hero--secondary` still resolve to them. What changed is the role: the orange is now only a border, a rule or a block with no copy over it, and every surface that used to carry text on it carries that text somewhere that passes. White on `#eb752a` is 2.95 and on `#ed833f` 2.65, against WCAG's 4.5; black passes WCAG at 7.11 and 7.92 but misses the 63 perceptual body-text floor at 57.4 and 61.1. No text colour carries body copy on this surface, so the copy moved instead. Surface by surface: the accent tag is `orange-50` (hover `orange-100`) with a `--mg-color-text` label at 15.7:1 and the orange as its border; `TextCta --secondary` takes the `soft` surface (white under an 8% wash of the accent) with dark eyebrow, headline and body and a solid orange leading rule, so its 90%-white body (2.42) is gone; `Hero --secondary` left the variant colour map and keeps the theme's hero colour in both layouts, with the default hero colour for its CTA ink and an 8px orange rule; `HubHeader --surface-secondary` no longer retargets the surface, so bar and banner stay the theme hero colour with the orange as a rule beneath the bar; `Card --secondary` gives its title the interactive colour like any other card link and takes a 4px accent rule; `AuthorImage --secondary` keeps the orange avatar ring and hover tint but hands the title to `--mg-color-text` and the focus outline to the standard focus ring. The seventh (unisdr/undrr-mangrove#1243) is why this belongs in the token layer rather than in each component: PreventionWeb and IRP had re-pointed `color.button-background--hover` and `color.button-outline-primary--hover` at `{color.orange-900}`, putting a white button label on the accent at 2.95:1 without any component choosing an orange background. Those hovers now use each brand's own deeper primitive: PreventionWeb `brand.teal-800` (#0c7575, 5.5:1 / 69.7) and IRP `brand.blue-800` (#0d67a3, 6.02:1 / 71.1), the same ones their secondary buttons already used. An orange rule is decoration, not identification: `#eb752a` is 2.95 on white and 2.66 on `orange-50`, both under the 3:1 of SC 1.4.11, so wherever the library now draws one, a fill, a label or a position carries the meaning alongside it. Darkening the accent to about `#b2571d` was prototyped and measured: it passes at 4.91 / 64.1, but it is 14.4 dE2000 from Sendai orange against a just-noticeable difference of about 2.3 (a rust, not the brand colour), and it would still have left the CTA's 90%-white body at 4.29, so the surface did not move. **If you overrode `--mg-color-tag-accent`, `--mg-color-tag-accent--hover`, `--mg-color-secondary` or `--mg-color-hero--secondary` in your own theme layer to work around the old failures, remove the override.** Those tokens are now read as a rule colour, not a text surface, so an override that darkened them will darken a border rather than fix anything, and a light override will leave the rule invisible. If you set `--tag-color-background-accent` to a dark fill, set `--mg-tag-foreground` with it: the accent label is now dark. The pairs are asserted in `stories/assets/scss/__tests__/tokens-contract.test.js`, so none of them can drift back. Per-component notes are in each component's MDX changelog. (unisdr/undrr-mangrove#1249)"
        },
        {
          "category": "General",
          "title": "The orange accent carries no text:",
          "description": "Sendai orange (`orange-900`, `orange-800`, and the `secondary`, `tag-accent`, `tag-accent--hover` and `hero--secondary` tokens that resolve to them) is documented as a non-text accent: borders, rules, icon fills and blocks with no copy over them. No text colour clears AA on it: white measures 2.65 to 2.95 against a 4.5 minimum, and black, which passes WCAG at 7.11 to 7.92, still misses the perceptual body-text floor of 63. The rule is stated in `docs/COLOUR-CONTRAST-METHODOLOGY.md`, in `docs/REVIEW-CHECKLIST.md`, at the token definitions in `tokens/undrr.yaml` and `tokens/mangrove.yaml`, at each stylesheet that paints the surface, on the Storybook colour inventories (Utility CSS background and text colours, Brand identity) and in each affected component's docs. The six components that still paired text with the orange at the time (`Tag --accent`, `TextCta --secondary`, `Hero --secondary`, `HubHeader --surface-secondary`, `Card --secondary` and `AuthorImage --secondary`) were named as recorded exceptions with their measured values. All six were fixed later in this release; see the entry above. The contrast suite gains the Text CTA, hub header, author-image and quote-highlight pairs it was not measuring, so none of them can drift unnoticed. No colour changed. (unisdr/undrr-mangrove#1198)",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null,
          "raw": "**The orange accent carries no text:** Sendai orange (`orange-900`, `orange-800`, and the `secondary`, `tag-accent`, `tag-accent--hover` and `hero--secondary` tokens that resolve to them) is documented as a non-text accent: borders, rules, icon fills and blocks with no copy over them. No text colour clears AA on it: white measures 2.65 to 2.95 against a 4.5 minimum, and black, which passes WCAG at 7.11 to 7.92, still misses the perceptual body-text floor of 63. The rule is stated in `docs/COLOUR-CONTRAST-METHODOLOGY.md`, in `docs/REVIEW-CHECKLIST.md`, at the token definitions in `tokens/undrr.yaml` and `tokens/mangrove.yaml`, at each stylesheet that paints the surface, on the Storybook colour inventories (Utility CSS background and text colours, Brand identity) and in each affected component's docs. The six components that still paired text with the orange at the time (`Tag --accent`, `TextCta --secondary`, `Hero --secondary`, `HubHeader --surface-secondary`, `Card --secondary` and `AuthorImage --secondary`) were named as recorded exceptions with their measured values. All six were fixed later in this release; see the entry above. The contrast suite gains the Text CTA, hub header, author-image and quote-highlight pairs it was not measuring, so none of them can drift unnoticed. No colour changed. (unisdr/undrr-mangrove#1198)"
        },
        {
          "category": "General",
          "title": "Accessible names and announcements across six components:",
          "description": "a pre-release sweep for controls that exist but are not named, announced or distinguishable. **ShowMore's documented markup changes visibly:** the toggle is now `<button type=\"button\">` instead of `<a href=\"#\">`, with `aria-controls` maintained by `mgShowMore()`; an anchor still in consumer markup keeps working and is given `role=\"button\"` plus the Space handler that role promises. The toggle carries no `aria-expanded`: the collapse is `overflow: hidden` clipping, which leaves the content in the accessibility tree and the tab order, so there is nothing hidden to describe; the docs now say so, and focus entering a clipped container expands it. Error page logo links get an accessible name, in the component and, separately, in the eleven static Cloudflare templates, whose markup is maintained by hand rather than generated from the component. Card summary links and error-page body links are underlined at rest rather than distinguished by colour alone. Gallery HTML thumbnail previews are `inert`, so injected links and buttons are no longer focusable inside the tab button. Dismissing a Notice returns focus to the control that raised it or to the nearest neighbour instead of `<body>`. The vanilla CopyButton example has a real `aria-label`, every integration path now reads that plain `aria-label`, and both the script and `fromElement` still honour the older `data-aria-label` on an otherwise unnamed button. Per-component detail is in each component's MDX changelog. (unisdr/undrr-mangrove#1190)",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null,
          "raw": "**Accessible names and announcements across six components:** a pre-release sweep for controls that exist but are not named, announced or distinguishable. **ShowMore's documented markup changes visibly:** the toggle is now `<button type=\"button\">` instead of `<a href=\"#\">`, with `aria-controls` maintained by `mgShowMore()`; an anchor still in consumer markup keeps working and is given `role=\"button\"` plus the Space handler that role promises. The toggle carries no `aria-expanded`: the collapse is `overflow: hidden` clipping, which leaves the content in the accessibility tree and the tab order, so there is nothing hidden to describe; the docs now say so, and focus entering a clipped container expands it. Error page logo links get an accessible name, in the component and, separately, in the eleven static Cloudflare templates, whose markup is maintained by hand rather than generated from the component. Card summary links and error-page body links are underlined at rest rather than distinguished by colour alone. Gallery HTML thumbnail previews are `inert`, so injected links and buttons are no longer focusable inside the tab button. Dismissing a Notice returns focus to the control that raised it or to the nearest neighbour instead of `<body>`. The vanilla CopyButton example has a real `aria-label`, every integration path now reads that plain `aria-label`, and both the script and `fromElement` still honour the older `data-aria-label` on an otherwise unnamed button. Per-component detail is in each component's MDX changelog. (unisdr/undrr-mangrove#1190)"
        },
        {
          "category": "General",
          "title": "Logical properties in RTL:",
          "description": "Hero, FormErrorSummary, Syndicated search, Card, StatsCard, Author image and the legacy `.fa-before` icon gap use logical properties (`margin-inline-start`, `padding-inline-end`, `border-inline-start`, `inset-inline-end`, `text-align: start`) instead of physical left/right ones. The visible fix is the split hero, which broke out of the viewport in Arabic at mobile widths and gave the whole page a horizontal scrollbar; the syndicated search clear button also grew to a 36x36 target with an 18px glyph, and the field's reserved inline-end padding grew from 24px to 44px to match. The rest of the syndicated search widget's direction-specific spacing (custom select trigger and chevron, active-filter label, chip connector, result separator, facet label and subtype indents) is logical too. Apart from the search field's clear button and padding, LTR rendering is unchanged. (unisdr/undrr-mangrove#1192)",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null,
          "raw": "**Logical properties in RTL:** Hero, FormErrorSummary, Syndicated search, Card, StatsCard, Author image and the legacy `.fa-before` icon gap use logical properties (`margin-inline-start`, `padding-inline-end`, `border-inline-start`, `inset-inline-end`, `text-align: start`) instead of physical left/right ones. The visible fix is the split hero, which broke out of the viewport in Arabic at mobile widths and gave the whole page a horizontal scrollbar; the syndicated search clear button also grew to a 36x36 target with an 18px glyph, and the field's reserved inline-end padding grew from 24px to 44px to match. The rest of the syndicated search widget's direction-specific spacing (custom select trigger and chevron, active-filter label, chip connector, result separator, facet label and subtype indents) is logical too. Apart from the search field's clear button and padding, LTR rendering is unchanged. (unisdr/undrr-mangrove#1192)"
        },
        {
          "category": "General",
          "title": "Switch error state and size hooks:",
          "description": "`.mg-switch` gains the error treatment every other form control already had: `aria-invalid=\"true\"` on the input, `.mg-switch__input--error`, or `.mg-switch--error` on the label, giving the track a red boundary beside a `.mg-form-error` message, with the same class naming, `aria-describedby` wiring and `FormErrorSummary` relationship as a text input (unisdr/undrr-mangrove#1187). It does not move the thumb: the error says the thing the switch controls failed, not that the user's choice is undone. Every length in the switch now derives from `--mg-switch-size`, with `--mg-switch-track-inline-size`, `--mg-switch-track-block-size`, `--mg-switch-track-inset` and `--mg-switch-thumb-size` for one part at a time, so resizing a switch no longer means re-declaring the thumb's `transform`, an override that beat Mangrove's own `[dir=\"rtl\"]` rule and sent the thumb the wrong way by the wrong distance in Arabic (unisdr/undrr-mangrove#1199). A shared `.mg-switch--small` covers the toolbar, table row and panel header cases. The track's inset around the thumb is a transparent border rather than padding, which is what the error state colours and what forced colours now only recolours; the box, the thumb, the travel and every default are unchanged, so a default switch renders exactly as it did in rc.2. See [Checkbox](https://mangrove.undrr.org/?path=/docs/components-forms-checkbox--docs), \"Error state\" and \"Size\". (unisdr/undrr-mangrove#1214)",
          "pr": 1187,
          "prRef": "unisdr/undrr-mangrove#1187",
          "prUrl": null,
          "raw": "**Switch error state and size hooks:** `.mg-switch` gains the error treatment every other form control already had: `aria-invalid=\"true\"` on the input, `.mg-switch__input--error`, or `.mg-switch--error` on the label, giving the track a red boundary beside a `.mg-form-error` message, with the same class naming, `aria-describedby` wiring and `FormErrorSummary` relationship as a text input (unisdr/undrr-mangrove#1187). It does not move the thumb: the error says the thing the switch controls failed, not that the user's choice is undone. Every length in the switch now derives from `--mg-switch-size`, with `--mg-switch-track-inline-size`, `--mg-switch-track-block-size`, `--mg-switch-track-inset` and `--mg-switch-thumb-size` for one part at a time, so resizing a switch no longer means re-declaring the thumb's `transform`, an override that beat Mangrove's own `[dir=\"rtl\"]` rule and sent the thumb the wrong way by the wrong distance in Arabic (unisdr/undrr-mangrove#1199). A shared `.mg-switch--small` covers the toolbar, table row and panel header cases. The track's inset around the thumb is a transparent border rather than padding, which is what the error state colours and what forced colours now only recolours; the box, the thumb, the travel and every default are unchanged, so a default switch renders exactly as it did in rc.2. See [Checkbox](https://mangrove.undrr.org/?path=/docs/components-forms-checkbox--docs), \"Error state\" and \"Size\". (unisdr/undrr-mangrove#1214)"
        },
        {
          "category": "General",
          "title": "`js/switch-pending.js` can be taken apart:",
          "description": "rc.2 shipped the switch's announcements, its ignore-while-saving guard and revert-on-failure with a timeout as one indivisible behaviour, so an app that owns the control's position had to drop the helper and re-implement the announcements, which are the hard part (unisdr/undrr-mangrove#1189). Three additions, all opt-in, with every default unchanged. One caveat for existing callers: `timeout: 0` was a silent no-op in rc.2 (it failed a `> 0` check and fell back to the ten-second deadline), so anyone already passing `0`, or a `parseInt` that produced it, loses that safety net and a hung save now leaves the switch busy until `destroy()`. `revert: false` (or `data-mg-switch-revert=\"false\"`) keeps the position the user asked for when a save fails; because a switch that does not match the server is its own problem, the helper sets `aria-invalid=\"true\"` on the input instead, clears it when the next save starts or on `destroy()`, and leaves an `aria-invalid` the page set itself alone. `timeout: 0` or `Infinity` (or `data-mg-switch-timeout`) takes the deadline off for a save that legitimately runs longer than ten seconds; `aria-busy` then stays true until the save settles, so the helper announces \"Still saving…\" once where the deadline would have been rather than leaving a screen reader user on \"Saving…\" against a busy control indefinitely. And `mgSwitchAnnouncer(element, { status, labels })` exports the announcements on their own (region resolution and placement, label resolution, `{label}` and function labels, the re-announce trick and the rate limit), with `pending()`, `stillSaving()`, `settled()`, `failed()`, `announce()`, `label()` and `destroy()`, and no listeners, attributes or state. `mgSwitchPending()` runs it, so there is one implementation of the announcements. **Consumer-visible:** `mg-switch:failed` now carries `reverted`, and its `checked` is the position the switch is left in either way; code that read `detail.checked` as \"the reverted position\" is correct as long as it does not pass `revert: false`. See [Checkbox](https://mangrove.undrr.org/?path=/docs/components-forms-checkbox--docs), \"Pending state\". (unisdr/undrr-mangrove#1213)",
          "pr": 1189,
          "prRef": "unisdr/undrr-mangrove#1189",
          "prUrl": null,
          "raw": "**`js/switch-pending.js` can be taken apart:** rc.2 shipped the switch's announcements, its ignore-while-saving guard and revert-on-failure with a timeout as one indivisible behaviour, so an app that owns the control's position had to drop the helper and re-implement the announcements, which are the hard part (unisdr/undrr-mangrove#1189). Three additions, all opt-in, with every default unchanged. One caveat for existing callers: `timeout: 0` was a silent no-op in rc.2 (it failed a `> 0` check and fell back to the ten-second deadline), so anyone already passing `0`, or a `parseInt` that produced it, loses that safety net and a hung save now leaves the switch busy until `destroy()`. `revert: false` (or `data-mg-switch-revert=\"false\"`) keeps the position the user asked for when a save fails; because a switch that does not match the server is its own problem, the helper sets `aria-invalid=\"true\"` on the input instead, clears it when the next save starts or on `destroy()`, and leaves an `aria-invalid` the page set itself alone. `timeout: 0` or `Infinity` (or `data-mg-switch-timeout`) takes the deadline off for a save that legitimately runs longer than ten seconds; `aria-busy` then stays true until the save settles, so the helper announces \"Still saving…\" once where the deadline would have been rather than leaving a screen reader user on \"Saving…\" against a busy control indefinitely. And `mgSwitchAnnouncer(element, { status, labels })` exports the announcements on their own (region resolution and placement, label resolution, `{label}` and function labels, the re-announce trick and the rate limit), with `pending()`, `stillSaving()`, `settled()`, `failed()`, `announce()`, `label()` and `destroy()`, and no listeners, attributes or state. `mgSwitchPending()` runs it, so there is one implementation of the announcements. **Consumer-visible:** `mg-switch:failed` now carries `reverted`, and its `checked` is the position the switch is left in either way; code that read `detail.checked` as \"the reverted position\" is correct as long as it does not pass `revert: false`. See [Checkbox](https://mangrove.undrr.org/?path=/docs/components-forms-checkbox--docs), \"Pending state\". (unisdr/undrr-mangrove#1213)"
        },
        {
          "category": "General",
          "title": "Manifest and markup gaps reported by a vanilla-HTML consumer:",
          "description": "four findings from the `undrr-risk-resilience-maps` team upgrading rc.1 to rc.2 (unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200).",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null,
          "raw": "**Manifest and markup gaps reported by a vanilla-HTML consumer:** four findings from the `undrr-risk-resilience-maps` team upgrading rc.1 to rc.2 (unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200)."
        },
        {
          "category": "General",
          "title": "StatusLabel indicators carry a shape:",
          "description": "every status label's indicator now has its own shape as well as its own colour: draft a rounded square, waiting for more information a capsule, waiting for validation a rounded diamond, degraded a chamfered triangle, offline an octagon; published and the unmodified base keep the circle. This is a visible change wherever `.mg-status-label` is used, and consumers need change no markup: the shapes come from CSS on the same empty `<span>`. The status name is still always present as text, so this is a redundant cue rather than a WCAG fix; it is there because several of the colours are close under colour vision deficiency (degraded and offline are 3.3 CIE76 / 1.1 dE2000 apart simulated for deuteranopia, against a just-noticeable difference of about 2.3 dE2000; it is the dE2000 figure that is under the threshold, not the CIE76 one), because a CSS-only component gets reused without its label, and because a second cue is faster to scan. The seven marks are drawn as one family (all convex, all softened at one corner radius, all carrying the same ring), and that ring is now exactly `--mg-status-label-indicator-border-width` on every edge of every shape, including the two drawn with a clip path, where it used to vary from the full width down to nothing (unisdr/undrr-mangrove#1208). `.mg-status-label-group` keeps its list semantics in Safari, so VoiceOver announces the item count (unisdr/undrr-mangrove#1209). The indicators also survive Windows High Contrast and printing without background graphics, and each shape still occupies one `--mg-status-label-indicator-size` of inline space, so a column or a table of statuses keeps a single text alignment edge. See [Status label](https://mangrove.undrr.org/?path=/docs/components-status-label--docs), \"Shape as well as colour\". (unisdr/undrr-mangrove#1206)",
          "pr": 1208,
          "prRef": "unisdr/undrr-mangrove#1208",
          "prUrl": null,
          "raw": "**StatusLabel indicators carry a shape:** every status label's indicator now has its own shape as well as its own colour: draft a rounded square, waiting for more information a capsule, waiting for validation a rounded diamond, degraded a chamfered triangle, offline an octagon; published and the unmodified base keep the circle. This is a visible change wherever `.mg-status-label` is used, and consumers need change no markup: the shapes come from CSS on the same empty `<span>`. The status name is still always present as text, so this is a redundant cue rather than a WCAG fix; it is there because several of the colours are close under colour vision deficiency (degraded and offline are 3.3 CIE76 / 1.1 dE2000 apart simulated for deuteranopia, against a just-noticeable difference of about 2.3 dE2000; it is the dE2000 figure that is under the threshold, not the CIE76 one), because a CSS-only component gets reused without its label, and because a second cue is faster to scan. The seven marks are drawn as one family (all convex, all softened at one corner radius, all carrying the same ring), and that ring is now exactly `--mg-status-label-indicator-border-width` on every edge of every shape, including the two drawn with a clip path, where it used to vary from the full width down to nothing (unisdr/undrr-mangrove#1208). `.mg-status-label-group` keeps its list semantics in Safari, so VoiceOver announces the item count (unisdr/undrr-mangrove#1209). The indicators also survive Windows High Contrast and printing without background graphics, and each shape still occupies one `--mg-status-label-indicator-size` of inline space, so a column or a table of statuses keeps a single text alignment edge. See [Status label](https://mangrove.undrr.org/?path=/docs/components-status-label--docs), \"Shape as well as colour\". (unisdr/undrr-mangrove#1206)"
        },
        {
          "category": "General",
          "title": "Font families are role custom properties, and the AI docs now say so once:",
          "description": "`llms.txt` listed font families with breakpoints and `$mg-tabs-border-bottom` as SCSS-only build-time variables, 38 lines above the Typography section that correctly describes the five `--mg-font-family-*` roles. An agent reading top to bottom hit the wrong statement first and hard-coded a typeface, which takes the page out of the Arabic re-pointing in `_fonts.scss`. All five roles are defined on `:root` in the shipped bundle, which reads `var(--mg-font-family-text)` throughout. Breakpoints and `$mg-tabs-border-bottom` genuinely have no custom property (neither appears in the compiled CSS), so they stay, and the `$mg-font-face-*` variables are named separately as what the roles are built from at compile time. The project standards page carried the same claim twice and the brand common-patterns page named the typefaces without saying that CSS must name a role. The same sweep corrected the typeface-to-element mapping on the two brand pages, which said Roboto Condensed sets h1–h6. In Latin it does not: headings inherit Roboto from `body`, and Roboto Condensed is the `display` and `ui` roles. Fixed at the source in `scripts/ai-manifest/generate-ai-manifest.js`. (unisdr/undrr-mangrove#1210)",
          "pr": 1210,
          "prRef": "unisdr/undrr-mangrove#1210",
          "prUrl": null,
          "raw": "**Font families are role custom properties, and the AI docs now say so once:** `llms.txt` listed font families with breakpoints and `$mg-tabs-border-bottom` as SCSS-only build-time variables, 38 lines above the Typography section that correctly describes the five `--mg-font-family-*` roles. An agent reading top to bottom hit the wrong statement first and hard-coded a typeface, which takes the page out of the Arabic re-pointing in `_fonts.scss`. All five roles are defined on `:root` in the shipped bundle, which reads `var(--mg-font-family-text)` throughout. Breakpoints and `$mg-tabs-border-bottom` genuinely have no custom property (neither appears in the compiled CSS), so they stay, and the `$mg-font-face-*` variables are named separately as what the roles are built from at compile time. The project standards page carried the same claim twice and the brand common-patterns page named the typefaces without saying that CSS must name a role. The same sweep corrected the typeface-to-element mapping on the two brand pages, which said Roboto Condensed sets h1–h6. In Latin it does not: headings inherit Roboto from `body`, and Roboto Condensed is the `display` and `ui` roles. Fixed at the source in `scripts/ai-manifest/generate-ai-manifest.js`. (unisdr/undrr-mangrove#1210)"
        },
        {
          "category": "General",
          "title": "Published package surface: Jest specs no longer ship.",
          "description": "`js/__tests__/copy-button.test.js`, `show-more.test.js` and `table-of-contents.test.js` were in the npm tarball of every release up to 2.0.0-rc.2, and are committed under every versioned folder in the CDN asset repository. They travelled because webpack copies `stories/assets` wholesale into `dist/assets`, and `scripts/assemble-npm-package.mjs` then copies `dist/assets/js` into the package: both walk whole directories, so anything beside a source goes with it. `__tests__`, `__snapshots__`, `__mocks__`, `__fixtures__`, `*.test.*` and `*.spec.*` are now excluded at both steps (one shared `DEV_ONLY_GLOBS` / `isDevOnly` rule), and the generated package.json `files` array carries the same exclusions as a second line of defence. Consumers lose three files they could not run; nothing else changes, and no published path moves. A new test in `scripts/__tests__/assemble-npm-package.test.js` fails if a development-only file reaches the assembled package again. (unisdr/undrr-mangrove#1219, unisdr/undrr-mangrove#1218)",
          "pr": 1219,
          "prRef": "unisdr/undrr-mangrove#1219",
          "prUrl": null,
          "raw": "**Published package surface: Jest specs no longer ship.** `js/__tests__/copy-button.test.js`, `show-more.test.js` and `table-of-contents.test.js` were in the npm tarball of every release up to 2.0.0-rc.2, and are committed under every versioned folder in the CDN asset repository. They travelled because webpack copies `stories/assets` wholesale into `dist/assets`, and `scripts/assemble-npm-package.mjs` then copies `dist/assets/js` into the package: both walk whole directories, so anything beside a source goes with it. `__tests__`, `__snapshots__`, `__mocks__`, `__fixtures__`, `*.test.*` and `*.spec.*` are now excluded at both steps (one shared `DEV_ONLY_GLOBS` / `isDevOnly` rule), and the generated package.json `files` array carries the same exclusions as a second line of defence. Consumers lose three files they could not run; nothing else changes, and no published path moves. A new test in `scripts/__tests__/assemble-npm-package.test.js` fails if a development-only file reaches the assembled package again. (unisdr/undrr-mangrove#1219, unisdr/undrr-mangrove#1218)"
        },
        {
          "category": "General",
          "title": "2.0 release notes consolidated and refreshed:",
          "description": "the release guide groups changes by theme, preserves the numbered migration reference, and adds linked upgrade guidance for current package imports, heroes, quote sanitisation, data tables and ShowMore. Changes awaiting the next prerelease are distinguished from rc.2; deployment is not assumed while CI is blocked. Per-prerelease history remains in GitHub Releases. (unisdr/undrr-mangrove#1217)",
          "pr": 1217,
          "prRef": "unisdr/undrr-mangrove#1217",
          "prUrl": null,
          "raw": "**2.0 release notes consolidated and refreshed:** the release guide groups changes by theme, preserves the numbered migration reference, and adds linked upgrade guidance for current package imports, heroes, quote sanitisation, data tables and ShowMore. Changes awaiting the next prerelease are distinguished from rc.2; deployment is not assumed while CI is blocked. Per-prerelease history remains in GitHub Releases. (unisdr/undrr-mangrove#1217)"
        },
        {
          "category": "General",
          "title": "The two icon-only button classes are documented, and the wrong pairing no longer fails contrast:",
          "description": "`.mg-icon-button` shipped in `style-all.css` with its own geometry, ghost reset, hover and focus ring, and was referenced by name in Notice, Drawer and the rc.2 release notes, but it appeared in no manifest entry, no `cssClasses` array and no utility entry, and nothing said it is a standalone primitive. An agent searching for \"icon button\" found only the compiled CSS and wrote `class=\"mg-icon-button mg-button-primary\"`, which rendered a dark glyph on the filled background (2.53:1 on UNDRR primary, 3.23:1 on secondary) because `.mg-button-primary` sets only a background and the readable `--mg-color-button` foreground lives on `.mg-button`. The hover was correct, so it read as a hover bug rather than as wrong markup. The contract is now stated where it is looked for: the Buttons docs, the component's manifest entry and examples, and a new `mg-icon-button` utility entry all say that `.mg-button--icon` is a shape modifier of `.mg-button` and carries the variant colours, and that `.mg-icon-button` is the standalone ghost primitive for dismiss, close and copy controls and takes no `.mg-button-*` class. **Consumer-visible:** a filled `.mg-button-primary` or `.mg-button-secondary` on a `.mg-icon-button` now takes `--mg-color-button` at rest, matching the hover that variant already set and the correct `.mg-button--icon` markup exactly, in all five themes. The combination is still wrong markup (`.mg-button-outline` draws no border on it), but it is no longer an SC 1.4.3 failure. A site that patched around it with its own low-specificity `color` rule on `.mg-icon-button` will now get the library value. Nothing changes for a bare `.mg-icon-button` or for any documented markup. (unisdr/undrr-mangrove#1225, unisdr/undrr-mangrove#1201)",
          "pr": 1225,
          "prRef": "unisdr/undrr-mangrove#1225",
          "prUrl": null,
          "raw": "**The two icon-only button classes are documented, and the wrong pairing no longer fails contrast:** `.mg-icon-button` shipped in `style-all.css` with its own geometry, ghost reset, hover and focus ring, and was referenced by name in Notice, Drawer and the rc.2 release notes, but it appeared in no manifest entry, no `cssClasses` array and no utility entry, and nothing said it is a standalone primitive. An agent searching for \"icon button\" found only the compiled CSS and wrote `class=\"mg-icon-button mg-button-primary\"`, which rendered a dark glyph on the filled background (2.53:1 on UNDRR primary, 3.23:1 on secondary) because `.mg-button-primary` sets only a background and the readable `--mg-color-button` foreground lives on `.mg-button`. The hover was correct, so it read as a hover bug rather than as wrong markup. The contract is now stated where it is looked for: the Buttons docs, the component's manifest entry and examples, and a new `mg-icon-button` utility entry all say that `.mg-button--icon` is a shape modifier of `.mg-button` and carries the variant colours, and that `.mg-icon-button` is the standalone ghost primitive for dismiss, close and copy controls and takes no `.mg-button-*` class. **Consumer-visible:** a filled `.mg-button-primary` or `.mg-button-secondary` on a `.mg-icon-button` now takes `--mg-color-button` at rest, matching the hover that variant already set and the correct `.mg-button--icon` markup exactly, in all five themes. The combination is still wrong markup (`.mg-button-outline` draws no border on it), but it is no longer an SC 1.4.3 failure. A site that patched around it with its own low-specificity `color` rule on `.mg-icon-button` will now get the library value. Nothing changes for a bare `.mg-icon-button` or for any documented markup. (unisdr/undrr-mangrove#1225, unisdr/undrr-mangrove#1201)"
        },
        {
          "category": "General",
          "title": "Every component's CSS custom properties are in the manifest:",
          "description": "each component that exposes any now carries a `customProperties` field in its `ai-components/{id}.json` entry: 105 properties across 20 components, from `--mg-status-label-*` and the `--mg-switch-*` set to `--mg-tab-rail-*`, `--mg-hub-header-*`, `--mg-empty-state-*`, `--mg-tree-*`, `--mg-notice-*` and `--mg-hero-gradient-*`. A component's custom properties are its theming API and `tokens.json` is a theme token dictionary, so until now they appeared nowhere machine-readable and a consuming team had to grep the compiled CSS for them (unisdr/undrr-mangrove#1200). Each record carries the property's name, what it does, the value it holds at rest, and one of two types: `default`, where a plain, unconditional rule already gives it a value, or `hook`, where nothing unconditional does (either no rule at all, or only a modifier or a media query) and a wrapper, an inline style or a React prop supplies one; the distinction `stories/assets/scss/__tests__/custom-properties-defined.test.js` already drew internally. A record also carries `format: \"srgb-channels\"` and `wrapInRgb: true` where the stylesheet wraps the property in `rgb()`, which thirteen of them do: those take channels (`255 255 255`), and a hex or a keyword there makes the declaration invalid and drops it with no warning. That is extracted from the CSS and reported the way `tokens.json` reports the same thing, so an agent can act on it without reading prose. The index entry for such a component carries the count, and `llms.json` and `llms.txt` say where to look. The list is produced hybrid, extraction-first: names, types and defaults are read out of the compiled CSS bundles on every build, so a rename or a changed default reaches consumers with no edit, while the descriptions are written by hand in the new `scripts/ai-manifest/custom-properties.js`. `yarn validate-manifest` fails in both directions: a description for a property the CSS no longer has, a property the CSS exposes that nothing describes, and one that belongs to no component; and a per-component count floor fails it if a property leaves the published set, which `NOT_PUBLIC` could otherwise do in silence. The 143 `--mg-*` properties that are global rather than component-scoped (the data-viz palettes, the Sendai ramps, the five typography roles and the legacy one-off colours, all declared straight in SCSS rather than generated from `tokens/*.yaml`) are in neither `tokens.json` nor this field, and `llms.txt` now names all four groups and says where each is defined instead of naming only the data-viz palette. The seven properties that only a modifier, a state or a media query declares (`--mg-switch-size`, `--mg-cta-arrow-shift`, `--mg-icon-svg`, the two `--mg-mega-*` and the two `--mg-tree-*` guide properties) are typed `hook` rather than `default`, because that is what an unmodified component computes; their reported resting values are unchanged. The interim prose that listed StatusLabel's and the switch's properties inside their manifest descriptions is gone, along with the note in `tokens.json`'s `scope` field saying no such index existed. (unisdr/undrr-mangrove#1223, unisdr/undrr-mangrove#1207)",
          "pr": 1200,
          "prRef": "unisdr/undrr-mangrove#1200",
          "prUrl": null,
          "raw": "**Every component's CSS custom properties are in the manifest:** each component that exposes any now carries a `customProperties` field in its `ai-components/{id}.json` entry: 105 properties across 20 components, from `--mg-status-label-*` and the `--mg-switch-*` set to `--mg-tab-rail-*`, `--mg-hub-header-*`, `--mg-empty-state-*`, `--mg-tree-*`, `--mg-notice-*` and `--mg-hero-gradient-*`. A component's custom properties are its theming API and `tokens.json` is a theme token dictionary, so until now they appeared nowhere machine-readable and a consuming team had to grep the compiled CSS for them (unisdr/undrr-mangrove#1200). Each record carries the property's name, what it does, the value it holds at rest, and one of two types: `default`, where a plain, unconditional rule already gives it a value, or `hook`, where nothing unconditional does (either no rule at all, or only a modifier or a media query) and a wrapper, an inline style or a React prop supplies one; the distinction `stories/assets/scss/__tests__/custom-properties-defined.test.js` already drew internally. A record also carries `format: \"srgb-channels\"` and `wrapInRgb: true` where the stylesheet wraps the property in `rgb()`, which thirteen of them do: those take channels (`255 255 255`), and a hex or a keyword there makes the declaration invalid and drops it with no warning. That is extracted from the CSS and reported the way `tokens.json` reports the same thing, so an agent can act on it without reading prose. The index entry for such a component carries the count, and `llms.json` and `llms.txt` say where to look. The list is produced hybrid, extraction-first: names, types and defaults are read out of the compiled CSS bundles on every build, so a rename or a changed default reaches consumers with no edit, while the descriptions are written by hand in the new `scripts/ai-manifest/custom-properties.js`. `yarn validate-manifest` fails in both directions: a description for a property the CSS no longer has, a property the CSS exposes that nothing describes, and one that belongs to no component; and a per-component count floor fails it if a property leaves the published set, which `NOT_PUBLIC` could otherwise do in silence. The 143 `--mg-*` properties that are global rather than component-scoped (the data-viz palettes, the Sendai ramps, the five typography roles and the legacy one-off colours, all declared straight in SCSS rather than generated from `tokens/*.yaml`) are in neither `tokens.json` nor this field, and `llms.txt` now names all four groups and says where each is defined instead of naming only the data-viz palette. The seven properties that only a modifier, a state or a media query declares (`--mg-switch-size`, `--mg-cta-arrow-shift`, `--mg-icon-svg`, the two `--mg-mega-*` and the two `--mg-tree-*` guide properties) are typed `hook` rather than `default`, because that is what an unmodified component computes; their reported resting values are unchanged. The interim prose that listed StatusLabel's and the switch's properties inside their manifest descriptions is gone, along with the note in `tokens.json`'s `scope` field saying no such index existed. (unisdr/undrr-mangrove#1223, unisdr/undrr-mangrove#1207)"
        },
        {
          "category": "General",
          "title": "The custom-property extractor no longer has four ways to publish a wrong default:",
          "description": "each was found by construction rather than by observation (nothing in the library hit any of them), but each would have published a wrong value into a field agents act on without checking. A declaration made only under `[dir=rtl]`, `[data-theme=dark]`, `.mg-theme-delta`, a `.is-`/`.has-` state class, a `:has()` condition or a numeric BEM modifier such as `.mg-embed-container--1x1` was read as unconditional, so its value was published as everyone's; the fourteen `--mg-tab-*` properties have `.mg-theme-*` declarations and were right only because each also has a generic declaration that wins, which is a result of precedence rather than design. The brace walk was not string-aware, so a `{` in a quoted `url()` would have desynced the stack and mis-typed every declaration after it, a `;` inside a string truncated the value it sat in (`--mg-icon-svg` carries one today and is inert only because the value exceeds the length cap) and a `/*` inside one string paired with a `*/` in a later one deleted the CSS between them. Both walks, and the `var()` fallback scanner, now skip strings. **Three published values change:** `--mg-mega-mobile-viewport`, `--mg-tree-group-indent` and `--mg-tree-guide-offset` no longer carry a `default`. `default` means the value the property resolves to at rest, and for those three it was taken from a modifier's declaration instead: `--mg-mega-mobile-viewport` published the `--progressive` modifier's `90vh`, which `@supports (height: 100dvh)` then re-declares to `90dvh`, so the published figure was the one no current browser uses, and the two tree values apply only under `.mg-tree--guides`, which their descriptions already say. A conditional declaration is no longer a source for `default` at all, so the rule now matches what the field's own `_ai` text promises rather than having an exception. Property count, component count, types and every other value are unchanged. Finally, the count of SCSS-declared global properties in `tokens.json`'s `scope.excludes` is derived from the bundles rather than hand-typed, so it cannot drift from the same figure in `llms.txt`; adding one data-viz colour used to put the two out of step with no check to detect it. (unisdr/undrr-mangrove#1236, unisdr/undrr-mangrove#1229)",
          "pr": 1236,
          "prRef": "unisdr/undrr-mangrove#1236",
          "prUrl": null,
          "raw": "**The custom-property extractor no longer has four ways to publish a wrong default:** each was found by construction rather than by observation (nothing in the library hit any of them), but each would have published a wrong value into a field agents act on without checking. A declaration made only under `[dir=rtl]`, `[data-theme=dark]`, `.mg-theme-delta`, a `.is-`/`.has-` state class, a `:has()` condition or a numeric BEM modifier such as `.mg-embed-container--1x1` was read as unconditional, so its value was published as everyone's; the fourteen `--mg-tab-*` properties have `.mg-theme-*` declarations and were right only because each also has a generic declaration that wins, which is a result of precedence rather than design. The brace walk was not string-aware, so a `{` in a quoted `url()` would have desynced the stack and mis-typed every declaration after it, a `;` inside a string truncated the value it sat in (`--mg-icon-svg` carries one today and is inert only because the value exceeds the length cap) and a `/*` inside one string paired with a `*/` in a later one deleted the CSS between them. Both walks, and the `var()` fallback scanner, now skip strings. **Three published values change:** `--mg-mega-mobile-viewport`, `--mg-tree-group-indent` and `--mg-tree-guide-offset` no longer carry a `default`. `default` means the value the property resolves to at rest, and for those three it was taken from a modifier's declaration instead: `--mg-mega-mobile-viewport` published the `--progressive` modifier's `90vh`, which `@supports (height: 100dvh)` then re-declares to `90dvh`, so the published figure was the one no current browser uses, and the two tree values apply only under `.mg-tree--guides`, which their descriptions already say. A conditional declaration is no longer a source for `default` at all, so the rule now matches what the field's own `_ai` text promises rather than having an exception. Property count, component count, types and every other value are unchanged. Finally, the count of SCSS-declared global properties in `tokens.json`'s `scope.excludes` is derived from the bundles rather than hand-typed, so it cannot drift from the same figure in `llms.txt`; adding one data-viz colour used to put the two out of step with no check to detect it. (unisdr/undrr-mangrove#1236, unisdr/undrr-mangrove#1229)"
        },
        {
          "category": "General",
          "title": "The status indicator palette is documented as measured, and deliberately unchanged:",
          "description": "the seven `--mg-status-label-indicator--*` fills were re-measured for WCAG contrast on each surface they actually appear on, for Oklab perceptual lightness, and pairwise as CIE76 and CIEDE2000 under protanopia, deuteranopia and tritanopia, in two independent simulation models (Machado 2009 and Viénot 1999) applied in linear RGB. Both models agree that degraded and offline are indistinguishable to a deuteranope (1.1 and 0.6 dE2000 against a ~2.3 just-noticeable difference), which is what the shapes added in unisdr/undrr-mangrove#1206 are for. Rebalancing the palette was measured and rejected: holding the hue associations and requiring every fill to clear 3:1 on white, the best worst-pair available from the tokens this system already owns is 2.34 dE2000 (still at the threshold, merely relocated), and it costs all four DELTA status colours, drops the worst normal-vision pair from 13.4 to 6.8 dE2000, and leaves the ring at 1.0–1.8:1 against its own fills. Seven unordered categories is past what colour carries, the same limit `_tokens-data-viz.scss` already records as `--mg-dataviz-categorical-safe-count: 4`. The reasoning and the full tables are in [Status label](https://mangrove.undrr.org/?path=/docs/components-status-label--docs), \"Why the palette is not rebalanced instead\". Also newly documented: none of the five themes overrides any of the seven fills, so the status palette is identical in all of them by design; and dropping the ring with `--mg-status-label-indicator-border-width: 0` removes the only part of the four pale marks that clears 3:1, and used to remove the base indicator altogether in forced colours (no longer the case; see \"The status indicator survives a dropped ring in forced colours\" below) (unisdr/undrr-mangrove#1224). No colour changed.",
          "pr": 1206,
          "prRef": "unisdr/undrr-mangrove#1206",
          "prUrl": null,
          "raw": "**The status indicator palette is documented as measured, and deliberately unchanged:** the seven `--mg-status-label-indicator--*` fills were re-measured for WCAG contrast on each surface they actually appear on, for Oklab perceptual lightness, and pairwise as CIE76 and CIEDE2000 under protanopia, deuteranopia and tritanopia, in two independent simulation models (Machado 2009 and Viénot 1999) applied in linear RGB. Both models agree that degraded and offline are indistinguishable to a deuteranope (1.1 and 0.6 dE2000 against a ~2.3 just-noticeable difference), which is what the shapes added in unisdr/undrr-mangrove#1206 are for. Rebalancing the palette was measured and rejected: holding the hue associations and requiring every fill to clear 3:1 on white, the best worst-pair available from the tokens this system already owns is 2.34 dE2000 (still at the threshold, merely relocated), and it costs all four DELTA status colours, drops the worst normal-vision pair from 13.4 to 6.8 dE2000, and leaves the ring at 1.0–1.8:1 against its own fills. Seven unordered categories is past what colour carries, the same limit `_tokens-data-viz.scss` already records as `--mg-dataviz-categorical-safe-count: 4`. The reasoning and the full tables are in [Status label](https://mangrove.undrr.org/?path=/docs/components-status-label--docs), \"Why the palette is not rebalanced instead\". Also newly documented: none of the five themes overrides any of the seven fills, so the status palette is identical in all of them by design; and dropping the ring with `--mg-status-label-indicator-border-width: 0` removes the only part of the four pale marks that clears 3:1, and used to remove the base indicator altogether in forced colours (no longer the case; see \"The status indicator survives a dropped ring in forced colours\" below) (unisdr/undrr-mangrove#1224). No colour changed."
        },
        {
          "category": "General",
          "title": "The status indicator survives a dropped ring in forced colours:",
          "description": "`--mg-status-label-indicator-border-width: 0` is a documented opt-out, and in Windows High Contrast it used to take the base status indicator with it. That indicator is deliberately the one hollow mark in the set of seven (a `Canvas` centre inside a `CanvasText` ring, so it does not collapse into published once every fill is repainted in one system colour), which means the ring is the whole of its edge; with the ring dropped it was a `Canvas` mark on a `Canvas` background and disappeared from the row at every indicator size. It now carries an inset 1px `CanvasText` outline in forced colours: an outline takes no space in any box model, so it lands exactly where the ring is and is invisible under a ring of any width, the mark keeps its size, fill and text alignment edge, and the clipped triangle and octagon still offset their inner polygons by the author's ring width rather than by a forced minimum. The six named statuses are solid `CanvasText` shapes that survive a missing ring on their own, and are explicitly left without it. Normal rendering is unchanged at every ring width (the rule is inside the `@media (forced-colors: active)` block), and no token changed, so none of the component's fourteen custom properties behaves differently. (unisdr/undrr-mangrove#1224, unisdr/undrr-mangrove#1211)",
          "pr": 1224,
          "prRef": "unisdr/undrr-mangrove#1224",
          "prUrl": null,
          "raw": "**The status indicator survives a dropped ring in forced colours:** `--mg-status-label-indicator-border-width: 0` is a documented opt-out, and in Windows High Contrast it used to take the base status indicator with it. That indicator is deliberately the one hollow mark in the set of seven (a `Canvas` centre inside a `CanvasText` ring, so it does not collapse into published once every fill is repainted in one system colour), which means the ring is the whole of its edge; with the ring dropped it was a `Canvas` mark on a `Canvas` background and disappeared from the row at every indicator size. It now carries an inset 1px `CanvasText` outline in forced colours: an outline takes no space in any box model, so it lands exactly where the ring is and is invisible under a ring of any width, the mark keeps its size, fill and text alignment edge, and the clipped triangle and octagon still offset their inner polygons by the author's ring width rather than by a forced minimum. The six named statuses are solid `CanvasText` shapes that survive a missing ring on their own, and are explicitly left without it. Normal rendering is unchanged at every ring width (the rule is inside the `@media (forced-colors: active)` block), and no token changed, so none of the component's fourteen custom properties behaves differently. (unisdr/undrr-mangrove#1224, unisdr/undrr-mangrove#1211)"
        },
        {
          "category": "General",
          "title": "Hydration lookups are scoped to the element being hydrated:",
          "description": "a sweep of all sixteen `*.fromElement.js` files and their `*.hydrate.js` barrels for lookups that reach past the container they are given (unisdr/undrr-mangrove#1205). The barrels are re-exports and hold no lookups. ScrollContainer's `doc.querySelector('.mg-scroll__content')` turned out not to be the document: `doc` is a throwaway document the same function builds from `container.innerHTML`, so two hydrated containers on a page never read each other's children and the reported symptom does not reproduce. It was still wider than the container being hydrated (a wrapper belonging to a nested scroll container matched), and now reads the live container, with the `DOMParser` round trip gone. The real wrong-instance lookup was in the syndicated search widget's pager, which scrolled to the first `[data-mg-search-widget]` on the page rather than the widget the reader had just paged. Every other document-wide lookup in the library was judged and left: the Drawer's trigger and container lookups already filter on `controlsId`, SkipLink, TableOfContents, `on-this-page-nav.js`, `show-more.js` and the widget's `facetsTarget` / `searchTarget` resolve a target the consumer named and that is deliberately outside the component, `preview-access.js` gates the first gate on the page by documented design, and the Footer, CookieConsentBanner, ShareButtons and `useHashSync` lookups read page-level resources. Two toggles left on `show-more.js`'s default `.mg-show-more--container` target do collide; that sweep concluded scoping was not possible; that conclusion is revised and the collision is fixed in unisdr/undrr-mangrove#1228. The content it reveals need not be a sibling or descendant of the toggle, so there is no container to confine the lookup to, but that rules out confining it, not ordering it, and an ancestor walk turned out to be available and never worse. Per-component detail is in the ScrollContainer and Syndicated search changelogs. (unisdr/undrr-mangrove#1222)",
          "pr": 1205,
          "prRef": "unisdr/undrr-mangrove#1205",
          "prUrl": null,
          "raw": "**Hydration lookups are scoped to the element being hydrated:** a sweep of all sixteen `*.fromElement.js` files and their `*.hydrate.js` barrels for lookups that reach past the container they are given (unisdr/undrr-mangrove#1205). The barrels are re-exports and hold no lookups. ScrollContainer's `doc.querySelector('.mg-scroll__content')` turned out not to be the document: `doc` is a throwaway document the same function builds from `container.innerHTML`, so two hydrated containers on a page never read each other's children and the reported symptom does not reproduce. It was still wider than the container being hydrated (a wrapper belonging to a nested scroll container matched), and now reads the live container, with the `DOMParser` round trip gone. The real wrong-instance lookup was in the syndicated search widget's pager, which scrolled to the first `[data-mg-search-widget]` on the page rather than the widget the reader had just paged. Every other document-wide lookup in the library was judged and left: the Drawer's trigger and container lookups already filter on `controlsId`, SkipLink, TableOfContents, `on-this-page-nav.js`, `show-more.js` and the widget's `facetsTarget` / `searchTarget` resolve a target the consumer named and that is deliberately outside the component, `preview-access.js` gates the first gate on the page by documented design, and the Footer, CookieConsentBanner, ShareButtons and `useHashSync` lookups read page-level resources. Two toggles left on `show-more.js`'s default `.mg-show-more--container` target do collide; that sweep concluded scoping was not possible; that conclusion is revised and the collision is fixed in unisdr/undrr-mangrove#1228. The content it reveals need not be a sibling or descendant of the toggle, so there is no container to confine the lookup to, but that rules out confining it, not ordering it, and an ancestor walk turned out to be available and never worse. Per-component detail is in the ScrollContainer and Syndicated search changelogs. (unisdr/undrr-mangrove#1222)"
        },
        {
          "category": "General",
          "title": "The syndicated search filter drawer mirrors in Arabic:",
          "description": "the mobile filter drawer was the one direction-specific declaration unisdr/undrr-mangrove#1192 left alone, because `left: 0` could not be swapped on its own: the panel's entry is a `translateX` keyframe, which travels on a physical axis, so `inset-inline-start` would have moved the resting edge to the right in Arabic while the panel still slid in from the left, and the shadow would still have pointed away from the page. The panel, its travel and its shadow offset now all follow the inline start, driven by two custom properties flipped under `:dir(rtl)`, the shape `.mg-drawer--start` in Navigation/Drawer already used, so the library has one answer to this rather than two. The slide also honours `prefers-reduced-motion: reduce`, which it did not before. LTR rendering is pixel-identical: same resting edge, width, shadow and 0.3s ease-out entry, verified by a byte-identical screenshot of the open drawer at 390px before and after. (unisdr/undrr-mangrove#1221, unisdr/undrr-mangrove#1204)",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null,
          "raw": "**The syndicated search filter drawer mirrors in Arabic:** the mobile filter drawer was the one direction-specific declaration unisdr/undrr-mangrove#1192 left alone, because `left: 0` could not be swapped on its own: the panel's entry is a `translateX` keyframe, which travels on a physical axis, so `inset-inline-start` would have moved the resting edge to the right in Arabic while the panel still slid in from the left, and the shadow would still have pointed away from the page. The panel, its travel and its shadow offset now all follow the inline start, driven by two custom properties flipped under `:dir(rtl)`, the shape `.mg-drawer--start` in Navigation/Drawer already used, so the library has one answer to this rather than two. The slide also honours `prefers-reduced-motion: reduce`, which it did not before. LTR rendering is pixel-identical: same resting edge, width, shadow and 0.3s ease-out entry, verified by a byte-identical screenshot of the open drawer at 390px before and after. (unisdr/undrr-mangrove#1221, unisdr/undrr-mangrove#1204)"
        },
        {
          "category": "General",
          "title": "Release notes format:",
          "description": "`docs/RELEASES.md` now defines a standard format and template for GitHub Release notes (intro, full-detail link, and fixed section order: breaking and visible changes, new features, bug fixes and hardening, documentation and discoverability, dependencies, CDN), based on the 2.0.0-rc.1 and 2.0.0-rc.2 releases.",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "raw": "**Release notes format:** `docs/RELEASES.md` now defines a standard format and template for GitHub Release notes (intro, full-detail link, and fixed section order: breaking and visible changes, new features, bug fixes and hardening, documentation and discoverability, dependencies, CDN), based on the 2.0.0-rc.1 and 2.0.0-rc.2 releases."
        },
        {
          "category": "General",
          "title": "Four documentation claims corrected, and the ΔE method written down:",
          "description": "each of these was inaccurate, and all four were verified against the source before being changed. `docs/REVIEW-CHECKLIST.md` justified the no-`role=\"list\"` house rule by saying `jsx-a11y/no-redundant-roles` flags the attribute; that rule does not exist in this repo: `eslint.config.mjs` never registers the `jsx-a11y` plugin, every `jsx-a11y/*` rule it names is `off`, and `no-redundant-roles` is absent entirely. The rule itself stands, so only the reason is replaced: the `mg-list-unmarked` mixin keeps the implicit `list` role, which makes the attribute redundant. `_tokens-data-viz.scss` and StatusLabel's MDX disagreed about the categorical palette's worst pair at seven series: ΔE 3.2 against 2.2. Recomputed from the palette in `_tokens-data-viz.scss` with the Viénot 1999 projections applied in linear RGB: the SCSS table is correct at every N, and the MDX is corrected to 3.2. Because there is no generating script, the derivation is now written down in `docs/COLOUR-CONTRAST-METHODOLOGY.md`, \"How colour-vision separation is measured\", including the two steps that change the answer by an order of magnitude and the worst pair at each N. StatusLabel's theming note said consumers redefine \"the four\" indicator fills and that PreventionWeb, IRP and MCR2030 inherit DELTA's; there are seven fills, six suffixed `--*` and one the unsuffixed `--mg-status-label-indicator`, and no theme overrides any of them: the fills ship as Mangrove base tokens in `_variables.scss`, and none of the four `_theme-*.scss` files redefines one, so all five brands render the same status palette. The MDX custom-property table also gains the two service-health fills it was missing. And \"3.3 CIE76 / 1.1 dE2000 apart … against a just-noticeable difference of about 2.3\", in this file and in StatusLabel's MDX, now names the threshold's metric: 2.3 is a dE2000 figure, and dangling it after the CIE76 number read as though the pair cleared it. No colour, token or compiled output changed.",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "raw": "**Four documentation claims corrected, and the ΔE method written down:** each of these was inaccurate, and all four were verified against the source before being changed. `docs/REVIEW-CHECKLIST.md` justified the no-`role=\"list\"` house rule by saying `jsx-a11y/no-redundant-roles` flags the attribute; that rule does not exist in this repo: `eslint.config.mjs` never registers the `jsx-a11y` plugin, every `jsx-a11y/*` rule it names is `off`, and `no-redundant-roles` is absent entirely. The rule itself stands, so only the reason is replaced: the `mg-list-unmarked` mixin keeps the implicit `list` role, which makes the attribute redundant. `_tokens-data-viz.scss` and StatusLabel's MDX disagreed about the categorical palette's worst pair at seven series: ΔE 3.2 against 2.2. Recomputed from the palette in `_tokens-data-viz.scss` with the Viénot 1999 projections applied in linear RGB: the SCSS table is correct at every N, and the MDX is corrected to 3.2. Because there is no generating script, the derivation is now written down in `docs/COLOUR-CONTRAST-METHODOLOGY.md`, \"How colour-vision separation is measured\", including the two steps that change the answer by an order of magnitude and the worst pair at each N. StatusLabel's theming note said consumers redefine \"the four\" indicator fills and that PreventionWeb, IRP and MCR2030 inherit DELTA's; there are seven fills, six suffixed `--*` and one the unsuffixed `--mg-status-label-indicator`, and no theme overrides any of them: the fills ship as Mangrove base tokens in `_variables.scss`, and none of the four `_theme-*.scss` files redefines one, so all five brands render the same status palette. The MDX custom-property table also gains the two service-health fills it was missing. And \"3.3 CIE76 / 1.1 dE2000 apart … against a just-noticeable difference of about 2.3\", in this file and in StatusLabel's MDX, now names the threshold's metric: 2.3 is a dE2000 figure, and dangling it after the CIE76 number read as though the pair cleared it. No colour, token or compiled output changed."
        },
        {
          "category": "General",
          "title": "Hydration never mounts a component into its own output:",
          "description": "`createHydrator` skips a selector match that sits inside an already-hydrated container the same selector also matches. A component is free to render the same `data-mg-*` marker its hydration selector matches (`SyndicationSearchWidget` renders `data-mg-search-widget` on its root so its pager can find the widget to scroll to), and until now a `hydrator.update()` re-scan treated that React-rendered marker as an un-hydrated target and mounted a second widget inside the first, nesting three deep. `update()` is the documented way to hydrate markup added after the first pass, so every consumer calling it duplicated every syndicated search widget on the page. The check is on the ancestor matching the same selector, not on it merely being hydrated: `ScrollContainer` and `Drawer` read the consumer's markup in their `fromElement` and re-emit it through `dangerouslySetInnerHTML`, so a different component's hydration host nested inside one (a scrolling row of hydrated cards, which is `ScrollContainer`'s canonical use, or an author-entered card in a Drupal drawer) comes back out of React verbatim and still hydrates. The walk reads the DOM rather than one hydrator's own bookkeeping, so a page that builds two hydrators for one selector is covered too. Markup added outside a hydrated container hydrates as before; appending into or refilling a hydrated container is a no-op, and the way to replace its contents is `unmountAll()` then `update()`. The rules are stated in [`docs/HYDRATION.md`](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/HYDRATION.md) and the authoring guide so a new component does not reintroduce it (unisdr/undrr-mangrove#1234, unisdr/undrr-mangrove#1227).",
          "pr": 1234,
          "prRef": "unisdr/undrr-mangrove#1234",
          "prUrl": null,
          "raw": "**Hydration never mounts a component into its own output:** `createHydrator` skips a selector match that sits inside an already-hydrated container the same selector also matches. A component is free to render the same `data-mg-*` marker its hydration selector matches (`SyndicationSearchWidget` renders `data-mg-search-widget` on its root so its pager can find the widget to scroll to), and until now a `hydrator.update()` re-scan treated that React-rendered marker as an un-hydrated target and mounted a second widget inside the first, nesting three deep. `update()` is the documented way to hydrate markup added after the first pass, so every consumer calling it duplicated every syndicated search widget on the page. The check is on the ancestor matching the same selector, not on it merely being hydrated: `ScrollContainer` and `Drawer` read the consumer's markup in their `fromElement` and re-emit it through `dangerouslySetInnerHTML`, so a different component's hydration host nested inside one (a scrolling row of hydrated cards, which is `ScrollContainer`'s canonical use, or an author-entered card in a Drupal drawer) comes back out of React verbatim and still hydrates. The walk reads the DOM rather than one hydrator's own bookkeeping, so a page that builds two hydrators for one selector is covered too. Markup added outside a hydrated container hydrates as before; appending into or refilling a hydrated container is a no-op, and the way to replace its contents is `unmountAll()` then `update()`. The rules are stated in [`docs/HYDRATION.md`](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/HYDRATION.md) and the authoring guide so a new component does not reintroduce it (unisdr/undrr-mangrove#1234, unisdr/undrr-mangrove#1227)."
        },
        {
          "category": "General",
          "title": "The vanilla JS modules ship as source, and the docs now say so:",
          "description": "`stories/assets/js/README.md` described a UMD bundle at `dist/js/{name}.min.js` as the build's output for every module in that directory, and `docs/ARCHITECTURE.md` described webpack's first config as producing `dist/js/*.min.js` and `dist/css/`. No such file has ever been emitted: the entry glob in `webpack.entries.js` interpolated `/stories/assets/**/*.js` into `./stories${suggestFiles}`, so it globbed `./stories/stories/assets/**/*.js`, matched nothing, and handed webpack an empty entry object. Nothing consumes a minified variant: the CDN and the npm `js/` directory both serve the verbatim copies CopyPlugin makes at `dist/assets/js/*.js`, which is also why unisdr/undrr-mangrove#1191 had already corrected a CopyButton snippet that pointed at `dist/js/copy-button.min.js`. The documentation is corrected rather than the glob: emitting the bundles would have added nine UMD files nobody loads to every CDN release, and fixing the glob as written would also have swept in the four `stories/assets/js/__tests__/` specs and the five Node-only SCSS token tests under `stories/assets/scss/__tests__/`, the same class of leak as unisdr/undrr-mangrove#1218. `webpack.entries.js` is deleted (its CSS branch had no caller either, because `yarn scss` compiles the stylesheets before webpack runs), and the first config now declares an empty entry with a note that it exists for its CopyPlugin patterns, and loses the `[name].min.js` filename and UMD library target that described the bundle it never emitted. `dist/` is unchanged: a build before and a build after produce 260 identical files, the only difference being the per-build `Compiled on:` timestamp each component bundle carries, which differs between any two builds. That config's `optimization.minimizer` list is load-bearing despite the empty entry, and a comment now explains why: webpack 5's default list minifies JS, CSS **and** HTML, so replacing it with `CssMinimizerPlugin` alone is what keeps `dist/assets/js/*.js` byte for byte what a contributor wrote, keeps the error pages readable, and keeps the CSS header comment. Deleting it would silently start minifying the JS and HTML the CDN serves. (unisdr/undrr-mangrove#1253)",
          "pr": 1191,
          "prRef": "unisdr/undrr-mangrove#1191",
          "prUrl": null,
          "raw": "**The vanilla JS modules ship as source, and the docs now say so:** `stories/assets/js/README.md` described a UMD bundle at `dist/js/{name}.min.js` as the build's output for every module in that directory, and `docs/ARCHITECTURE.md` described webpack's first config as producing `dist/js/*.min.js` and `dist/css/`. No such file has ever been emitted: the entry glob in `webpack.entries.js` interpolated `/stories/assets/**/*.js` into `./stories${suggestFiles}`, so it globbed `./stories/stories/assets/**/*.js`, matched nothing, and handed webpack an empty entry object. Nothing consumes a minified variant: the CDN and the npm `js/` directory both serve the verbatim copies CopyPlugin makes at `dist/assets/js/*.js`, which is also why unisdr/undrr-mangrove#1191 had already corrected a CopyButton snippet that pointed at `dist/js/copy-button.min.js`. The documentation is corrected rather than the glob: emitting the bundles would have added nine UMD files nobody loads to every CDN release, and fixing the glob as written would also have swept in the four `stories/assets/js/__tests__/` specs and the five Node-only SCSS token tests under `stories/assets/scss/__tests__/`, the same class of leak as unisdr/undrr-mangrove#1218. `webpack.entries.js` is deleted (its CSS branch had no caller either, because `yarn scss` compiles the stylesheets before webpack runs), and the first config now declares an empty entry with a note that it exists for its CopyPlugin patterns, and loses the `[name].min.js` filename and UMD library target that described the bundle it never emitted. `dist/` is unchanged: a build before and a build after produce 260 identical files, the only difference being the per-build `Compiled on:` timestamp each component bundle carries, which differs between any two builds. That config's `optimization.minimizer` list is load-bearing despite the empty entry, and a comment now explains why: webpack 5's default list minifies JS, CSS **and** HTML, so replacing it with `CssMinimizerPlugin` alone is what keeps `dist/assets/js/*.js` byte for byte what a contributor wrote, keeps the error pages readable, and keeps the CSS header comment. Deleting it would silently start minifying the JS and HTML the CDN serves. (unisdr/undrr-mangrove#1253)"
        },
        {
          "category": "General",
          "title": "The AI manifest publishes imports that resolve:",
          "description": "every `import` line in `ai-components/index.json` and the per-component detail files named the package root, `import { IconCard } from \"@undrr/undrr-mangrove\"`, which does not resolve on any published version. The generator read `src/index.js` and treated its exports as the consumer surface, but the tarball ships neither `src/` nor `dist/`, so an agent following the manifest wrote an import that throws `ERR_MODULE_NOT_FOUND`. It now reads the export names out of the built bundles in `dist/components/`, which is what the package publishes under `components/`, and emits the subpath, `import { IconCard } from \"@undrr/undrr-mangrove/components/IconCard.js\"`, with a named or default binding to match what the bundle actually exports. 49 components carry an import line, up from the 22 the root export list produced, and 47 of them resolve and bind when checked against a freshly packed tarball; the other two are `ShowMore` and `Tab`, whose bundles read `document` as they load (both import a vanilla module that self-initialises on `DOMContentLoaded`), so they need a browser rather than a resolver. Those two keep their import line, which is correct in a browser and through a bundler, and carry `importRequiresDom: true` in the index with a note in the detail file saying what to do on a server-rendered page: an agent that would otherwise meet `document is not defined` and have nothing in the manifest to explain it now knows before it runs the line. The flag is set by importing every bundle at generation time rather than by guessing from its source, and a missing or stale `dist/components/` is now a hard failure instead of a manifest that quietly loses every import line. A component that ships in no bundle still carries no import line at all instead of an unusable one. `docs/COMPONENT-GUIDE.md`, `docs/ARCHITECTURE.md` and `docs/REVIEW-CHECKLIST.md` no longer present `src/index.js` as the way to publish a component: a webpack entry is, and the entry point export is the repository's own. Whether the package root should be a supported entry point at all is still open on unisdr/undrr-mangrove#1252, and nothing about the published shape changes here: a new test packs the assembled package and records it, including that `main` names a file the tarball does not contain. (unisdr/undrr-mangrove#1252)",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null,
          "raw": "**The AI manifest publishes imports that resolve:** every `import` line in `ai-components/index.json` and the per-component detail files named the package root, `import { IconCard } from \"@undrr/undrr-mangrove\"`, which does not resolve on any published version. The generator read `src/index.js` and treated its exports as the consumer surface, but the tarball ships neither `src/` nor `dist/`, so an agent following the manifest wrote an import that throws `ERR_MODULE_NOT_FOUND`. It now reads the export names out of the built bundles in `dist/components/`, which is what the package publishes under `components/`, and emits the subpath, `import { IconCard } from \"@undrr/undrr-mangrove/components/IconCard.js\"`, with a named or default binding to match what the bundle actually exports. 49 components carry an import line, up from the 22 the root export list produced, and 47 of them resolve and bind when checked against a freshly packed tarball; the other two are `ShowMore` and `Tab`, whose bundles read `document` as they load (both import a vanilla module that self-initialises on `DOMContentLoaded`), so they need a browser rather than a resolver. Those two keep their import line, which is correct in a browser and through a bundler, and carry `importRequiresDom: true` in the index with a note in the detail file saying what to do on a server-rendered page: an agent that would otherwise meet `document is not defined` and have nothing in the manifest to explain it now knows before it runs the line. The flag is set by importing every bundle at generation time rather than by guessing from its source, and a missing or stale `dist/components/` is now a hard failure instead of a manifest that quietly loses every import line. A component that ships in no bundle still carries no import line at all instead of an unusable one. `docs/COMPONENT-GUIDE.md`, `docs/ARCHITECTURE.md` and `docs/REVIEW-CHECKLIST.md` no longer present `src/index.js` as the way to publish a component: a webpack entry is, and the entry point export is the repository's own. Whether the package root should be a supported entry point at all is still open on unisdr/undrr-mangrove#1252, and nothing about the published shape changes here: a new test packs the assembled package and records it, including that `main` names a file the tarball does not contain. (unisdr/undrr-mangrove#1252)"
        }
      ]
    },
    {
      "version": "2.0.0-rc.2",
      "date": "2026-09-17",
      "isPrerelease": true,
      "tag": "v2.0.0-rc.2",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-rc2-2026-09-17",
      "summary": "This section is intentionally brief. Full detail and migration steps are in the [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)).",
      "changes": [
        {
          "category": "General",
          "title": "Buttons:",
          "description": "filled primary, secondary and disabled buttons paint their fill under the border, so they render the same size as outline buttons. New `--mg-border-color-button-primary` and `--mg-border-color-button-secondary` tokens (default `transparent`) colour that stroke; Hero and strong TextCta surfaces set a light one. Icons are vertically centred, and buttons with an icon and a label are 36px tall. A bare `.mg-icon-button` owns its ghost styling, keeps its icon visible on hover in every theme, and no longer picks up `display: flex` and `10px` padding from a MegaMenu rule (see upgrade notes). (unisdr/undrr-mangrove#1173, unisdr/undrr-mangrove#1171)",
          "pr": 1173,
          "prRef": "unisdr/undrr-mangrove#1173",
          "prUrl": null,
          "raw": "**Buttons:** filled primary, secondary and disabled buttons paint their fill under the border, so they render the same size as outline buttons. New `--mg-border-color-button-primary` and `--mg-border-color-button-secondary` tokens (default `transparent`) colour that stroke; Hero and strong TextCta surfaces set a light one. Icons are vertically centred, and buttons with an icon and a label are 36px tall. A bare `.mg-icon-button` owns its ghost styling, keeps its icon visible on hover in every theme, and no longer picks up `display: flex` and `10px` padding from a MegaMenu rule (see upgrade notes). (unisdr/undrr-mangrove#1173, unisdr/undrr-mangrove#1171)"
        },
        {
          "category": "General",
          "title": "Tree hydration:",
          "description": "Tree hydrates from a plain nested list in a `data-mg-tree` container and gains a `toggleIcon` prop. The default export of `components/Tree.js` is now `HydratedTree` (named `Tree` / `TreeItem` exports are unchanged), and item DOM ids are scoped to each tree instead of `mg-treeitem-{id}`. Keyboard, RTL chevron and IRP contrast fixes. (unisdr/undrr-mangrove#1176, unisdr/undrr-mangrove#1172)",
          "pr": 1176,
          "prRef": "unisdr/undrr-mangrove#1176",
          "prUrl": null,
          "raw": "**Tree hydration:** Tree hydrates from a plain nested list in a `data-mg-tree` container and gains a `toggleIcon` prop. The default export of `components/Tree.js` is now `HydratedTree` (named `Tree` / `TreeItem` exports are unchanged), and item DOM ids are scoped to each tree instead of `mg-treeitem-{id}`. Keyboard, RTL chevron and IRP contrast fixes. (unisdr/undrr-mangrove#1176, unisdr/undrr-mangrove#1172)"
        },
        {
          "category": "General",
          "title": "ServiceNotice:",
          "description": "the status link announces that it opens a new tab (translatable `labels.opensInNewTab`) and survives server rendering; the hydration contract is documented and published in a new AI manifest `hydration` field; the `mg-notice--compact` / `mg-notice--overlay` class fallbacks are removed from the container once read; DELTA's status link meets 4.5:1 through `--mg-notice-action-secondary`. (unisdr/undrr-mangrove#1175, unisdr/undrr-mangrove#1178, unisdr/undrr-mangrove#1172)",
          "pr": 1175,
          "prRef": "unisdr/undrr-mangrove#1175",
          "prUrl": null,
          "raw": "**ServiceNotice:** the status link announces that it opens a new tab (translatable `labels.opensInNewTab`) and survives server rendering; the hydration contract is documented and published in a new AI manifest `hydration` field; the `mg-notice--compact` / `mg-notice--overlay` class fallbacks are removed from the container once read; DELTA's status link meets 4.5:1 through `--mg-notice-action-secondary`. (unisdr/undrr-mangrove#1175, unisdr/undrr-mangrove#1178, unisdr/undrr-mangrove#1172)"
        },
        {
          "category": "General",
          "title": "Line heights are public:",
          "description": "`--mg-font-line-height-500` and `--mg-font-line-height-700` custom properties are emitted in every theme; compiled line heights are unchanged. `$mg-font-line-height-*` are deprecated Sass aliases until 3.0. (unisdr/undrr-mangrove#1170, unisdr/undrr-mangrove#1085)",
          "pr": 1170,
          "prRef": "unisdr/undrr-mangrove#1170",
          "prUrl": null,
          "raw": "**Line heights are public:** `--mg-font-line-height-500` and `--mg-font-line-height-700` custom properties are emitted in every theme; compiled line heights are unchanged. `$mg-font-line-height-*` are deprecated Sass aliases until 3.0. (unisdr/undrr-mangrove#1170, unisdr/undrr-mangrove#1085)"
        },
        {
          "category": "General",
          "title": "Hydration `identifierPrefix` is unique page-wide:",
          "description": "roots take a page-wide number shared by every hydrator and runtime copy, so a later `update(context)` no longer reuses a prefix and pages loading two copies of React no longer get duplicate DOM ids. The option and return value are unchanged; only the number in the generated prefix differs. (unisdr/undrr-mangrove#1178)",
          "pr": 1178,
          "prRef": "unisdr/undrr-mangrove#1178",
          "prUrl": null,
          "raw": "**Hydration `identifierPrefix` is unique page-wide:** roots take a page-wide number shared by every hydrator and runtime copy, so a later `update(context)` no longer reuses a prefix and pages loading two copies of React no longer get duplicate DOM ids. The option and return value are unchanged; only the number in the generated prefix differs. (unisdr/undrr-mangrove#1178)"
        },
        {
          "category": "General",
          "title": "Tabs lifecycle:",
          "description": "a vanilla tab set removed from the page without `mgTabsDestroy()` is suspended instead of leaking: its window, `document.fonts` and `ResizeObserver` registrations are released, its markup and selection stay, and it resumes if the same node is re-attached. `mgTabs()` and `mgTabsRuntime()` accept an optional `{ signal }` third argument; aborting it destroys the tab sets that call initialised, and the React `Tab` wrapper passes one. A hash change made while a set was suspended is followed when it resumes or is re-initialised. While any set is tracked, the runtime holds one page-wide window `resize` and one `hashchange` listener to find removed sets. `mgTabsDestroy(scope)` destroys every set in scope even if one throws. (unisdr/undrr-mangrove#1183, unisdr/undrr-mangrove#1180)",
          "pr": 1183,
          "prRef": "unisdr/undrr-mangrove#1183",
          "prUrl": null,
          "raw": "**Tabs lifecycle:** a vanilla tab set removed from the page without `mgTabsDestroy()` is suspended instead of leaking: its window, `document.fonts` and `ResizeObserver` registrations are released, its markup and selection stay, and it resumes if the same node is re-attached. `mgTabs()` and `mgTabsRuntime()` accept an optional `{ signal }` third argument; aborting it destroys the tab sets that call initialised, and the React `Tab` wrapper passes one. A hash change made while a set was suspended is followed when it resumes or is re-initialised. While any set is tracked, the runtime holds one page-wide window `resize` and one `hashchange` listener to find removed sets. `mgTabsDestroy(scope)` destroys every set in scope even if one throws. (unisdr/undrr-mangrove#1183, unisdr/undrr-mangrove#1180)"
        },
        {
          "category": "General",
          "title": "Switch pending state and `js/switch-pending.js`:",
          "description": "`aria-busy=\"true\"` on `.mg-switch__input`, or `.mg-switch--pending` on the label, shows a spinning ring in the thumb (a static dotted ring under reduced motion) over a greyed track, without disabling the input. `aria-disabled=\"true\"` switches are now dimmed like `:disabled` but keep a full-strength focus ring. In forced colours the switch was invisible; it now draws itself in system colours. New `--mg-switch-*` custom properties can be set on the switch or any ancestor. A new dependency-free module, `js/switch-pending.js` (CDN and npm), handles the announcements, ignored presses, timeout and revert for plain HTML and Drupal sites: `mgSwitchPending(input, { save, status, timeout, labels, signal })` returns `{ destroy() }`, `[data-mg-switch-pending]` switches are enhanced on page load and can be answered by one `mg-switch:save` listener through `respondWith()`, `mgSwitchPendingInit()` / `mgSwitchPendingDestroy()` handle dynamic content, and it dispatches `mg-switch:pending`, `mg-switch:settled` and `mg-switch:failed`. See [Checkbox](https://mangrove.undrr.org/?path=/docs/components-forms-checkbox--docs), \"Pending state\". (unisdr/undrr-mangrove#1184)",
          "pr": 1184,
          "prRef": "unisdr/undrr-mangrove#1184",
          "prUrl": null,
          "raw": "**Switch pending state and `js/switch-pending.js`:** `aria-busy=\"true\"` on `.mg-switch__input`, or `.mg-switch--pending` on the label, shows a spinning ring in the thumb (a static dotted ring under reduced motion) over a greyed track, without disabling the input. `aria-disabled=\"true\"` switches are now dimmed like `:disabled` but keep a full-strength focus ring. In forced colours the switch was invisible; it now draws itself in system colours. New `--mg-switch-*` custom properties can be set on the switch or any ancestor. A new dependency-free module, `js/switch-pending.js` (CDN and npm), handles the announcements, ignored presses, timeout and revert for plain HTML and Drupal sites: `mgSwitchPending(input, { save, status, timeout, labels, signal })` returns `{ destroy() }`, `[data-mg-switch-pending]` switches are enhanced on page load and can be answered by one `mg-switch:save` listener through `respondWith()`, `mgSwitchPendingInit()` / `mgSwitchPendingDestroy()` handle dynamic content, and it dispatches `mg-switch:pending`, `mg-switch:settled` and `mg-switch:failed`. See [Checkbox](https://mangrove.undrr.org/?path=/docs/components-forms-checkbox--docs), \"Pending state\". (unisdr/undrr-mangrove#1184)"
        },
        {
          "category": "General",
          "title": "StatusLabel and EmptyState docs:",
          "description": "`--warning` / `--negative` are documented as supported service-health modifiers, EmptyState has heading-level guidance, and a StatusLabel group in RTL no longer picks up the global list padding. (unisdr/undrr-mangrove#1174)",
          "pr": 1174,
          "prRef": "unisdr/undrr-mangrove#1174",
          "prUrl": null,
          "raw": "**StatusLabel and EmptyState docs:** `--warning` / `--negative` are documented as supported service-health modifiers, EmptyState has heading-level guidance, and a StatusLabel group in RTL no longer picks up the global list padding. (unisdr/undrr-mangrove#1174)"
        },
        {
          "category": "General",
          "title": "Docs domain is `mangrove.undrr.org`:",
          "description": "Storybook, `llms.txt`, `llms.json`, `releases.json`, `tokens.json` and `ai-components/` are published at `https://mangrove.undrr.org/`, and documentation links, generated AI manifest URLs and the CSS/JS banners use it. Old `preventionweb.github.io/undrr-mangrove/` URLs redirect to the same path on the new domain. CDN URLs (`assets.undrr.org/mangrove/`) and the npm package are unchanged. (unisdr/undrr-mangrove#1185)",
          "pr": 1185,
          "prRef": "unisdr/undrr-mangrove#1185",
          "prUrl": null,
          "raw": "**Docs domain is `mangrove.undrr.org`:** Storybook, `llms.txt`, `llms.json`, `releases.json`, `tokens.json` and `ai-components/` are published at `https://mangrove.undrr.org/`, and documentation links, generated AI manifest URLs and the CSS/JS banners use it. Old `preventionweb.github.io/undrr-mangrove/` URLs redirect to the same path on the new domain. CDN URLs (`assets.undrr.org/mangrove/`) and the npm package are unchanged. (unisdr/undrr-mangrove#1185)"
        },
        {
          "category": "General",
          "title": "AI manifest:",
          "description": "a new `vanillaModule` field describes components enhanced by a plain module from `/js/` with no React or import map (Checkbox, flagged `vanillaModule: true` in the index). The `vanillaScripts` URLs and the `llms.txt` script glob pointed at `js/*.min.js`, which the CDN does not serve; they now use `js/*.js`. (unisdr/undrr-mangrove#1184)",
          "pr": 1184,
          "prRef": "unisdr/undrr-mangrove#1184",
          "prUrl": null,
          "raw": "**AI manifest:** a new `vanillaModule` field describes components enhanced by a plain module from `/js/` with no React or import map (Checkbox, flagged `vanillaModule: true` in the index). The `vanillaScripts` URLs and the `llms.txt` script glob pointed at `js/*.min.js`, which the CDN does not serve; they now use `js/*.js`. (unisdr/undrr-mangrove#1184)"
        },
        {
          "category": "General",
          "title": "Repo tooling (no change to the published package):",
          "description": "`yarn pack:assemble` / `yarn pack:preview` build and compare the npm package locally with the same script as the publish workflow (unisdr/undrr-mangrove#1169); Storybook \"Show code\" works on every docs page again, with opt-in HTML snippets for CSS-only stories (unisdr/undrr-mangrove#1179, unisdr/undrr-mangrove#1177); and `preview-stats.json` is no longer uploaded with the Storybook Pages site (unisdr/undrr-mangrove#1181).",
          "pr": 1169,
          "prRef": "unisdr/undrr-mangrove#1169",
          "prUrl": null,
          "raw": "**Repo tooling (no change to the published package):** `yarn pack:assemble` / `yarn pack:preview` build and compare the npm package locally with the same script as the publish workflow (unisdr/undrr-mangrove#1169); Storybook \"Show code\" works on every docs page again, with opt-in HTML snippets for CSS-only stories (unisdr/undrr-mangrove#1179, unisdr/undrr-mangrove#1177); and `preview-stats.json` is no longer uploaded with the Storybook Pages site (unisdr/undrr-mangrove#1181)."
        }
      ]
    },
    {
      "version": "2.0.0-rc.1",
      "date": "2026-09-16",
      "isPrerelease": true,
      "tag": "v2.0.0-rc.1",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-rc1-2026-09-16",
      "summary": "This section is intentionally brief. Full detail and migration steps are in the [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)).",
      "changes": [
        {
          "category": "General",
          "title": "Notice foundation:",
          "description": "new `Notice` component (`info`, `warning`, `negative`, `positive`; compact, prominent and overlay modifiers; hydration) with `ServiceNotice` for degraded or offline embeds, including capped automatic retry and UN-language labels. **Snackbar now renders through Notice** (visual change; see upgrade notes). StatusLabel gains `--warning` / `--negative`. (unisdr/undrr-mangrove#1164)",
          "pr": 1164,
          "prRef": "unisdr/undrr-mangrove#1164",
          "prUrl": null,
          "raw": "**Notice foundation:** new `Notice` component (`info`, `warning`, `negative`, `positive`; compact, prominent and overlay modifiers; hydration) with `ServiceNotice` for degraded or offline embeds, including capped automatic retry and UN-language labels. **Snackbar now renders through Notice** (visual change; see upgrade notes). StatusLabel gains `--warning` / `--negative`. (unisdr/undrr-mangrove#1164)"
        },
        {
          "category": "General",
          "title": "Type scale is public:",
          "description": "`--mg-font-size-100` … `--mg-font-size-1100` custom properties are emitted in every theme and read by all components with `var()`; compiled sizes are unchanged. `$mg-font-size-*`, `$mg-font-body` and `$mg-font-tag` are deprecated Sass aliases until 3.0. A new test fails the build on any `var(--mg-*)` that nothing defines. (unisdr/undrr-mangrove#1168, unisdr/undrr-mangrove#1167)",
          "pr": 1168,
          "prRef": "unisdr/undrr-mangrove#1168",
          "prUrl": null,
          "raw": "**Type scale is public:** `--mg-font-size-100` … `--mg-font-size-1100` custom properties are emitted in every theme and read by all components with `var()`; compiled sizes are unchanged. `$mg-font-size-*`, `$mg-font-body` and `$mg-font-tag` are deprecated Sass aliases until 3.0. A new test fails the build on any `var(--mg-*)` that nothing defines. (unisdr/undrr-mangrove#1168, unisdr/undrr-mangrove#1167)"
        },
        {
          "category": "General",
          "title": "New prototype primitives:",
          "description": "Drawer and Tree navigation (unisdr/undrr-mangrove#1158), map and chart Legend (unisdr/undrr-mangrove#1159) and Range slider (unisdr/undrr-mangrove#1160), with hydration for Drawer and full keyboard navigation for Tree (unisdr/undrr-mangrove#1163). Before rc.1 these were hardened: Drawer renders strings as text, is inert when closed, manages focus and hydrates with trigger buttons; Tree mirrors in RTL; Legend and Range gained accessibility fixes and tests. (unisdr/undrr-mangrove#1166)",
          "pr": 1158,
          "prRef": "unisdr/undrr-mangrove#1158",
          "prUrl": null,
          "raw": "**New prototype primitives:** Drawer and Tree navigation (unisdr/undrr-mangrove#1158), map and chart Legend (unisdr/undrr-mangrove#1159) and Range slider (unisdr/undrr-mangrove#1160), with hydration for Drawer and full keyboard navigation for Tree (unisdr/undrr-mangrove#1163). Before rc.1 these were hardened: Drawer renders strings as text, is inert when closed, manages focus and hydrates with trigger buttons; Tree mirrors in RTL; Legend and Range gained accessibility fixes and tests. (unisdr/undrr-mangrove#1166)"
        },
        {
          "category": "General",
          "title": "CopyButton:",
          "description": "new copy-to-clipboard button (unisdr/undrr-mangrove#1160) with React hydration and a zero-dependency vanilla script, `js/copy-button.js` (`window.UNDRR.copyButton`), plus UN-language labels (unisdr/undrr-mangrove#1163). It reports failed copies with a manual-copy hint (unisdr/undrr-mangrove#1166).",
          "pr": 1160,
          "prRef": "unisdr/undrr-mangrove#1160",
          "prUrl": null,
          "raw": "**CopyButton:** new copy-to-clipboard button (unisdr/undrr-mangrove#1160) with React hydration and a zero-dependency vanilla script, `js/copy-button.js` (`window.UNDRR.copyButton`), plus UN-language labels (unisdr/undrr-mangrove#1163). It reports failed copies with a manual-copy hint (unisdr/undrr-mangrove#1166)."
        },
        {
          "category": "General",
          "title": "New styles:",
          "description": "data table, badge, accordion, switch and icon-button styles. (unisdr/undrr-mangrove#1157)",
          "pr": 1157,
          "prRef": "unisdr/undrr-mangrove#1157",
          "prUrl": null,
          "raw": "**New styles:** data table, badge, accordion, switch and icon-button styles. (unisdr/undrr-mangrove#1157)"
        },
        {
          "category": "General",
          "title": "Discoverability:",
          "description": "machine-readable `releases.json` and `tokens.json`, CSS/JS banners and SCSS docblocks linking to Storybook and `llms.txt`, and expanded AI manifest coverage. (unisdr/undrr-mangrove#1156, unisdr/undrr-mangrove#1157, unisdr/undrr-mangrove#1162)",
          "pr": 1156,
          "prRef": "unisdr/undrr-mangrove#1156",
          "prUrl": null,
          "raw": "**Discoverability:** machine-readable `releases.json` and `tokens.json`, CSS/JS banners and SCSS docblocks linking to Storybook and `llms.txt`, and expanded AI manifest coverage. (unisdr/undrr-mangrove#1156, unisdr/undrr-mangrove#1157, unisdr/undrr-mangrove#1162)"
        },
        {
          "category": "General",
          "title": "Storybook organisation and docs:",
          "description": "messaging components are grouped under Components/Notice and page navigation under Components/Navigation, so those Storybook URLs and AI manifest ids changed. Documentation links now point at `preventionweb.github.io/undrr-mangrove`. (unisdr/undrr-mangrove#1164, unisdr/undrr-mangrove#1166)",
          "pr": 1164,
          "prRef": "unisdr/undrr-mangrove#1164",
          "prUrl": null,
          "raw": "**Storybook organisation and docs:** messaging components are grouped under Components/Notice and page navigation under Components/Navigation, so those Storybook URLs and AI manifest ids changed. Documentation links now point at `preventionweb.github.io/undrr-mangrove`. (unisdr/undrr-mangrove#1164, unisdr/undrr-mangrove#1166)"
        }
      ]
    },
    {
      "version": "2.0.0-beta.3",
      "date": "2026-09-15",
      "isPrerelease": true,
      "tag": "v2.0.0-beta.3",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-beta3-2026-09-15",
      "summary": "This section is intentionally brief. Full detail and migration steps are in the [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)).",
      "changes": [
        {
          "category": "General",
          "title": "Sub-brand button tokens",
          "description": "defined secondary button background and hover tokens for PreventionWeb, MCR, and IRP themes. (unisdr/undrr-mangrove#1150)",
          "pr": 1150,
          "prRef": "unisdr/undrr-mangrove#1150",
          "prUrl": null,
          "raw": "**Sub-brand button tokens** defined secondary button background and hover tokens for PreventionWeb, MCR, and IRP themes. (unisdr/undrr-mangrove#1150)"
        },
        {
          "category": "General",
          "title": "Hero title",
          "description": "removed the `max-width: 12ch` constraint from `.mg-hero--immersive:not(.mg-hero--split) .mg-hero__title` so headings flow naturally across the overlay container. (unisdr/undrr-mangrove#1151)",
          "pr": 1151,
          "prRef": "unisdr/undrr-mangrove#1151",
          "prUrl": null,
          "raw": "**Hero title** removed the `max-width: 12ch` constraint from `.mg-hero--immersive:not(.mg-hero--split) .mg-hero__title` so headings flow naturally across the overlay container. (unisdr/undrr-mangrove#1151)"
        },
        {
          "category": "General",
          "title": "Card layout, MegaMenu, and ScrollContainer",
          "description": "gained height fill (`block-size: 100%`) for vertical cards in separate column wrappers, animated active-tab underline transitions for MegaMenu, and themed desktop scrollbars. (unisdr/undrr-mangrove#1153)",
          "pr": 1153,
          "prRef": "unisdr/undrr-mangrove#1153",
          "prUrl": null,
          "raw": "**Card layout, MegaMenu, and ScrollContainer** gained height fill (`block-size: 100%`) for vertical cards in separate column wrappers, animated active-tab underline transitions for MegaMenu, and themed desktop scrollbars. (unisdr/undrr-mangrove#1153)"
        },
        {
          "category": "General",
          "title": "Code style",
          "description": "swept all story and doc files into compliance with Prettier. (unisdr/undrr-mangrove#1152)",
          "pr": 1152,
          "prRef": "unisdr/undrr-mangrove#1152",
          "prUrl": null,
          "raw": "**Code style** swept all story and doc files into compliance with Prettier. (unisdr/undrr-mangrove#1152)"
        }
      ]
    },
    {
      "version": "2.0.0-beta.2",
      "date": "2026-09-15",
      "isPrerelease": true,
      "tag": "v2.0.0-beta.2",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-beta2-2026-09-15",
      "summary": "This section is intentionally brief. Full detail and migration steps are in the [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)).",
      "changes": [
        {
          "category": "General",
          "title": "Translated logos and PageHeader",
          "description": "now resolve locale-specific wordmarks through the shared asset helper, with `crop=\"autocrop\"` kept for the default English wordmark only. (unisdr/undrr-mangrove#1148)",
          "pr": 1148,
          "prRef": "unisdr/undrr-mangrove#1148",
          "prUrl": null,
          "raw": "**Translated logos and PageHeader** now resolve locale-specific wordmarks through the shared asset helper, with `crop=\"autocrop\"` kept for the default English wordmark only. (unisdr/undrr-mangrove#1148)"
        },
        {
          "category": "General",
          "title": "Author image and icons",
          "description": "gained the new stacked/link/no-image layout options plus the higher-value utility and OCHA icon additions. (unisdr/undrr-mangrove#1147, unisdr/undrr-mangrove#1145)",
          "pr": 1147,
          "prRef": "unisdr/undrr-mangrove#1147",
          "prUrl": null,
          "raw": "**Author image and icons** gained the new stacked/link/no-image layout options plus the higher-value utility and OCHA icon additions. (unisdr/undrr-mangrove#1147, unisdr/undrr-mangrove#1145)"
        },
        {
          "category": "General",
          "title": "CTA, AI manifest, and docs cleanup",
          "description": "tightened strong-surface contrast, made the docs-base URL configurable, and trimmed stale release notes noise. (unisdr/undrr-mangrove#1142, unisdr/undrr-mangrove#1143, unisdr/undrr-mangrove#1144)",
          "pr": 1142,
          "prRef": "unisdr/undrr-mangrove#1142",
          "prUrl": null,
          "raw": "**CTA, AI manifest, and docs cleanup** tightened strong-surface contrast, made the docs-base URL configurable, and trimmed stale release notes noise. (unisdr/undrr-mangrove#1142, unisdr/undrr-mangrove#1143, unisdr/undrr-mangrove#1144)"
        }
      ]
    },
    {
      "version": "2.0.0-beta.1",
      "date": "2026-09-11",
      "isPrerelease": true,
      "tag": "v2.0.0-beta.1",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-beta1-2026-09-11",
      "summary": "This section is intentionally brief. Full detail and migration steps are in the [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)).",
      "changes": [
        {
          "category": "General",
          "title": null,
          "description": "Storybook inline Docs canvases now receive the selected locale’s language and direction, fixing Arabic examples that inherited an explicit English canvas boundary and preserving RTL across theme or control changes. (unisdr/undrr-mangrove#1109)",
          "pr": 1109,
          "prRef": "unisdr/undrr-mangrove#1109",
          "prUrl": null,
          "raw": "Storybook inline Docs canvases now receive the selected locale’s language and direction, fixing Arabic examples that inherited an explicit English canvas boundary and preserving RTL across theme or control changes. (unisdr/undrr-mangrove#1109)"
        },
        {
          "category": "General",
          "title": null,
          "description": "CDN references now use the canonical `assets.undrr.org/` root across documentation, examples, font URLs and component defaults. The release URL updater emits canonical paths while continuing to accept legacy paths as migration inputs.",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "raw": "CDN references now use the canonical `assets.undrr.org/` root across documentation, examples, font URLs and component defaults. The release URL updater emits canonical paths while continuing to accept legacy paths as migration inputs."
        },
        {
          "category": "General",
          "title": null,
          "description": "Pre-beta.1 paper-cuts pass: PageHeader adds `languageDisplay=\"links\"` and tighter icon-only accessibility semantics, MegaMenu desktop flyout/refined reduced-motion behavior lands, and Storybook/docs guidance adds screenshot workflow and token-generation reminders. (unisdr/undrr-mangrove#1134)",
          "pr": 1134,
          "prRef": "unisdr/undrr-mangrove#1134",
          "prUrl": null,
          "raw": "Pre-beta.1 paper-cuts pass: PageHeader adds `languageDisplay=\"links\"` and tighter icon-only accessibility semantics, MegaMenu desktop flyout/refined reduced-motion behavior lands, and Storybook/docs guidance adds screenshot workflow and token-generation reminders. (unisdr/undrr-mangrove#1134)"
        }
      ]
    },
    {
      "version": "2.0.0-alpha.4",
      "date": "2026-09-09",
      "isPrerelease": true,
      "tag": "v2.0.0-alpha.4",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#200-alpha4-2026-09-09",
      "summary": "This section is intentionally brief. Full detail and migration steps are in the [v2.0 release notes](https://mangrove.undrr.org/?path=/docs/getting-started-release-notes-v2-0--docs) (source: [`docs/RELEASE-2.0.md`](docs/RELEASE-2.0.md)).",
      "changes": [
        {
          "category": "General",
          "title": null,
          "description": "Arabic typography and font roles were finalized for v2.0 (including removal of legacy font-family SCSS variables). (unisdr/undrr-mangrove#1089, unisdr/undrr-mangrove#1092, unisdr/undrr-mangrove#1098)",
          "pr": 1089,
          "prRef": "unisdr/undrr-mangrove#1089",
          "prUrl": null,
          "raw": "Arabic typography and font roles were finalized for v2.0 (including removal of legacy font-family SCSS variables). (unisdr/undrr-mangrove#1089, unisdr/undrr-mangrove#1092, unisdr/undrr-mangrove#1098)"
        },
        {
          "category": "General",
          "title": null,
          "description": "Tabs and MegaMenu received the alpha.4 navigation refinements. (unisdr/undrr-mangrove#1097, unisdr/undrr-mangrove#1100)",
          "pr": 1097,
          "prRef": "unisdr/undrr-mangrove#1097",
          "prUrl": null,
          "raw": "Tabs and MegaMenu received the alpha.4 navigation refinements. (unisdr/undrr-mangrove#1097, unisdr/undrr-mangrove#1100)"
        },
        {
          "category": "General",
          "title": null,
          "description": "Release tooling updates included `_site` Jest exclusion and prerelease CDN URL updates. (unisdr/undrr-mangrove#1090)",
          "pr": 1090,
          "prRef": "unisdr/undrr-mangrove#1090",
          "prUrl": null,
          "raw": "Release tooling updates included `_site` Jest exclusion and prerelease CDN URL updates. (unisdr/undrr-mangrove#1090)"
        }
      ]
    },
    {
      "version": "1.8.2",
      "date": "2026-08-27",
      "isPrerelease": false,
      "tag": "v1.8.2",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#182-2026-08-27",
      "summary": "",
      "changes": [
        {
          "category": "Features",
          "title": "Grid layout extended to 12 columns with an auto-fit fallback.",
          "description": "Numbered column classes now cover `col-7`–`col-12`, and a new `.mg-grid--auto-fit` modifier lays any larger item count out in a single row. Fixes StatsCard collapsing to a stacked single column past 6 stats. (unisdr/undrr-mangrove#1081)",
          "pr": 1081,
          "prRef": "unisdr/undrr-mangrove#1081",
          "prUrl": null,
          "raw": "**Grid layout extended to 12 columns with an auto-fit fallback.** Numbered column classes now cover `col-7`–`col-12`, and a new `.mg-grid--auto-fit` modifier lays any larger item count out in a single row. Fixes StatsCard collapsing to a stacked single column past 6 stats. (unisdr/undrr-mangrove#1081)"
        },
        {
          "category": "Bug fixes",
          "title": "Card z-index no longer conflicts with the mega menu.",
          "description": "Cards no longer overlap the open mega-menu overlay. (unisdr/undrr-mangrove#1075)",
          "pr": 1075,
          "prRef": "unisdr/undrr-mangrove#1075",
          "prUrl": null,
          "raw": "**Card z-index no longer conflicts with the mega menu.** Cards no longer overlap the open mega-menu overlay. (unisdr/undrr-mangrove#1075)"
        },
        {
          "category": "Bug fixes",
          "title": "Syndication search strips hidden teaser fields.",
          "description": "Hidden teaser fields are now removed programmatically from the React search output. (unisdr/undrr-mangrove#1077)",
          "pr": 1077,
          "prRef": "unisdr/undrr-mangrove#1077",
          "prUrl": null,
          "raw": "**Syndication search strips hidden teaser fields.** Hidden teaser fields are now removed programmatically from the React search output. (unisdr/undrr-mangrove#1077)"
        },
        {
          "category": "Build & tooling",
          "title": null,
          "description": "Major toolchain upgrades: Babel 8, `sass-loader` 17, and `jest-axe` 11. (unisdr/undrr-mangrove#1083)",
          "pr": 1083,
          "prRef": "unisdr/undrr-mangrove#1083",
          "prUrl": null,
          "raw": "Major toolchain upgrades: Babel 8, `sass-loader` 17, and `jest-axe` 11. (unisdr/undrr-mangrove#1083)"
        },
        {
          "category": "Build & tooling",
          "title": null,
          "description": "Storybook to 10.5.10, React to 19.2.8, PostCSS to 8.5.26, and a roundup of patch and minor dependency updates. (unisdr/undrr-mangrove#1078, unisdr/undrr-mangrove#1082, unisdr/undrr-mangrove#1071)",
          "pr": 1078,
          "prRef": "unisdr/undrr-mangrove#1078",
          "prUrl": null,
          "raw": "Storybook to 10.5.10, React to 19.2.8, PostCSS to 8.5.26, and a roundup of patch and minor dependency updates. (unisdr/undrr-mangrove#1078, unisdr/undrr-mangrove#1082, unisdr/undrr-mangrove#1071)"
        }
      ]
    },
    {
      "version": "1.8.1",
      "date": "2026-07-06",
      "isPrerelease": false,
      "tag": "v1.8.1",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#181-2026-07-06",
      "summary": "",
      "changes": [
        {
          "category": "Bug fixes",
          "title": "Syndication search organization routing.",
          "description": "Organization results now always resolve to PreventionWeb instead of whichever domain appears first in `field_domain_access`, fixing incorrect organization links in multi-domain index data. (unisdr/undrr-mangrove#1066)",
          "pr": 1066,
          "prRef": "unisdr/undrr-mangrove#1066",
          "prUrl": null,
          "raw": "**Syndication search organization routing.** Organization results now always resolve to PreventionWeb instead of whichever domain appears first in `field_domain_access`, fixing incorrect organization links in multi-domain index data. (unisdr/undrr-mangrove#1066)"
        },
        {
          "category": "Security",
          "title": "DOMPurify patched to 3.4.11.",
          "description": "Security dependency update merged as part of patch release maintenance. (unisdr/undrr-mangrove#1051)",
          "pr": 1051,
          "prRef": "unisdr/undrr-mangrove#1051",
          "prUrl": null,
          "raw": "**DOMPurify patched to 3.4.11.** Security dependency update merged as part of patch release maintenance. (unisdr/undrr-mangrove#1051)"
        },
        {
          "category": "Build & tooling",
          "title": null,
          "description": "Dependency maintenance updates: `chromaui/action` v17, `actions/checkout` v7, `serialize-javascript` v7.0.6, `babel-plugin-polyfill-corejs3` v1, Yarn v4.17.0, `autoprefixer` v10.5.2, and `postcss` v8.5.16. (unisdr/undrr-mangrove#1042, unisdr/undrr-mangrove#1059, unisdr/undrr-mangrove#1062, unisdr/undrr-mangrove#1063, unisdr/undrr-mangrove#1065, unisdr/undrr-mangrove#1067, unisdr/undrr-mangrove#1068, unisdr/undrr-mangrove#1069)",
          "pr": 1042,
          "prRef": "unisdr/undrr-mangrove#1042",
          "prUrl": null,
          "raw": "Dependency maintenance updates: `chromaui/action` v17, `actions/checkout` v7, `serialize-javascript` v7.0.6, `babel-plugin-polyfill-corejs3` v1, Yarn v4.17.0, `autoprefixer` v10.5.2, and `postcss` v8.5.16. (unisdr/undrr-mangrove#1042, unisdr/undrr-mangrove#1059, unisdr/undrr-mangrove#1062, unisdr/undrr-mangrove#1063, unisdr/undrr-mangrove#1065, unisdr/undrr-mangrove#1067, unisdr/undrr-mangrove#1068, unisdr/undrr-mangrove#1069)"
        },
        {
          "category": "Documentation",
          "title": null,
          "description": "Added Drupal hydration wrapper integration guidance to `README.md` and expanded release-writing guidance in `docs/RELEASES.md` (curated, themed GitHub Release notes format). (unisdr/undrr-mangrove#1016)",
          "pr": 1016,
          "prRef": "unisdr/undrr-mangrove#1016",
          "prUrl": null,
          "raw": "Added Drupal hydration wrapper integration guidance to `README.md` and expanded release-writing guidance in `docs/RELEASES.md` (curated, themed GitHub Release notes format). (unisdr/undrr-mangrove#1016)"
        }
      ]
    },
    {
      "version": "1.8.0",
      "date": "2026-06-19",
      "isPrerelease": false,
      "tag": "v1.8.0",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#180-2026-06-19",
      "summary": "",
      "changes": [
        {
          "category": "Security",
          "title": "Dependency update cooldown.",
          "description": "Renovate now waits until a release is at least 7 days old before it proposes the update (`minimumReleaseAge: \"7 days\"` with `internalChecksFilter: \"strict\"`), so a compromised package version that gets detected and unpublished within its first days never reaches a pull request here. Security advisories (`vulnerabilityAlerts`) are exempt, so genuine fixes are not delayed. Dependabot is retired and Renovate is now the sole dependency bot, which also ends the duplicate update PRs. A separate spike tracks evaluating pnpm for its scripts-off-by-default behaviour (unisdr/undrr-mangrove#1038). (unisdr/undrr-mangrove#1039)",
          "pr": 1038,
          "prRef": "unisdr/undrr-mangrove#1038",
          "prUrl": null,
          "raw": "**Dependency update cooldown.** Renovate now waits until a release is at least 7 days old before it proposes the update (`minimumReleaseAge: \"7 days\"` with `internalChecksFilter: \"strict\"`), so a compromised package version that gets detected and unpublished within its first days never reaches a pull request here. Security advisories (`vulnerabilityAlerts`) are exempt, so genuine fixes are not delayed. Dependabot is retired and Renovate is now the sole dependency bot, which also ends the duplicate update PRs. A separate spike tracks evaluating pnpm for its scripts-off-by-default behaviour (unisdr/undrr-mangrove#1038). (unisdr/undrr-mangrove#1039)"
        },
        {
          "category": "Removed",
          "title": "D3 chart components, `MapComponent`, and `Fetcher` removed.",
          "description": "The npm exports `BarChart`, `MapComponent`, and `Fetcher` are no longer published. The internal-only Storybook stories `Histogram`, `IndexChart`, and `ConnectedScatterplot` are also removed. D3 and Leaflet packages are dropped from the dependency tree. Migrate charting to [Recharts](https://recharts.org/), mapping to [MAPX](https://mapx.org/), and data loading to [react-query](https://tanstack.com/query) / [SWR](https://swr.vercel.app/) or server-side load. (unisdr/undrr-mangrove#1011)",
          "pr": 1011,
          "prRef": "unisdr/undrr-mangrove#1011",
          "prUrl": null,
          "raw": "**D3 chart components, `MapComponent`, and `Fetcher` removed.** The npm exports `BarChart`, `MapComponent`, and `Fetcher` are no longer published. The internal-only Storybook stories `Histogram`, `IndexChart`, and `ConnectedScatterplot` are also removed. D3 and Leaflet packages are dropped from the dependency tree. Migrate charting to [Recharts](https://recharts.org/), mapping to [MAPX](https://mapx.org/), and data loading to [react-query](https://tanstack.com/query) / [SWR](https://swr.vercel.app/) or server-side load. (unisdr/undrr-mangrove#1011)"
        }
      ]
    },
    {
      "version": "1.7.0",
      "date": "2026-05-11",
      "isPrerelease": false,
      "tag": "v1.7.0",
      "tagUrl": null,
      "changelogUrl": "https://github.com/PreventionWeb/undrr-mangrove/blob/main/CHANGELOG.md#170-2026-05-11",
      "summary": "",
      "changes": [
        {
          "category": "Runtime",
          "title": "Node.js 22 is now the declared minimum",
          "description": "(Node 20 reached EOL on 2026-04-30). `engines.node` set to `>=22.0.0`. CI and Docker continue to run Node 24. `.nvmrc` added so `nvm use` picks the CI-pinned version. (unisdr/undrr-mangrove#1003)",
          "pr": 1003,
          "prRef": "unisdr/undrr-mangrove#1003",
          "prUrl": null,
          "raw": "**Node.js 22 is now the declared minimum** (Node 20 reached EOL on 2026-04-30). `engines.node` set to `>=22.0.0`. CI and Docker continue to run Node 24. `.nvmrc` added so `nvm use` picks the CI-pinned version. (unisdr/undrr-mangrove#1003)"
        },
        {
          "category": "Build & tooling",
          "title": null,
          "description": "TypeScript 5 → 6, ESLint 9 → 10 majors (unisdr/undrr-mangrove#977).",
          "pr": 977,
          "prRef": "unisdr/undrr-mangrove#977",
          "prUrl": null,
          "raw": "TypeScript 5 → 6, ESLint 9 → 10 majors (unisdr/undrr-mangrove#977)."
        },
        {
          "category": "Build & tooling",
          "title": null,
          "description": "React 19.2.5 → 19.2.6 (unisdr/undrr-mangrove#991).",
          "pr": 991,
          "prRef": "unisdr/undrr-mangrove#991",
          "prUrl": null,
          "raw": "React 19.2.5 → 19.2.6 (unisdr/undrr-mangrove#991)."
        },
        {
          "category": "Build & tooling",
          "title": null,
          "description": "Yarn 4.14.1 (unisdr/undrr-mangrove#952).",
          "pr": 952,
          "prRef": "unisdr/undrr-mangrove#952",
          "prUrl": null,
          "raw": "Yarn 4.14.1 (unisdr/undrr-mangrove#952)."
        },
        {
          "category": "Build & tooling",
          "title": null,
          "description": "Batch dep updates: `jest` 30.4.x monorepo (unisdr/undrr-mangrove#997), `typescript-eslint` 8.59.2 (unisdr/undrr-mangrove#993), `@babel/preset-env` 7.29.5 (unisdr/undrr-mangrove#961), `stylelint` ~17.11.0 (unisdr/undrr-mangrove#937), `globals` 17.6.0 (unisdr/undrr-mangrove#964), `sass-loader` 16.0.8 (unisdr/undrr-mangrove#990), plus batched minor/patch (unisdr/undrr-mangrove#960, unisdr/undrr-mangrove#984).",
          "pr": 997,
          "prRef": "unisdr/undrr-mangrove#997",
          "prUrl": null,
          "raw": "Batch dep updates: `jest` 30.4.x monorepo (unisdr/undrr-mangrove#997), `typescript-eslint` 8.59.2 (unisdr/undrr-mangrove#993), `@babel/preset-env` 7.29.5 (unisdr/undrr-mangrove#961), `stylelint` ~17.11.0 (unisdr/undrr-mangrove#937), `globals` 17.6.0 (unisdr/undrr-mangrove#964), `sass-loader` 16.0.8 (unisdr/undrr-mangrove#990), plus batched minor/patch (unisdr/undrr-mangrove#960, unisdr/undrr-mangrove#984)."
        },
        {
          "category": "Security",
          "title": null,
          "description": "Patched 17 Dependabot security alerts via yarn resolutions (unisdr/undrr-mangrove#978).",
          "pr": 978,
          "prRef": "unisdr/undrr-mangrove#978",
          "prUrl": null,
          "raw": "Patched 17 Dependabot security alerts via yarn resolutions (unisdr/undrr-mangrove#978)."
        },
        {
          "category": "Security",
          "title": null,
          "description": "Patched `brace-expansion` ReDoS via resolution (unisdr/undrr-mangrove#979).",
          "pr": 979,
          "prRef": "unisdr/undrr-mangrove#979",
          "prUrl": null,
          "raw": "Patched `brace-expansion` ReDoS via resolution (unisdr/undrr-mangrove#979)."
        },
        {
          "category": "Code quality",
          "title": null,
          "description": "New `react-doctor` component-quality linter codified as a project convention. Health score moved 57 → 68 across the sweep, with 66 of the original 398 findings cleared. (unisdr/undrr-mangrove#985, unisdr/undrr-mangrove#987, unisdr/undrr-mangrove#988, unisdr/undrr-mangrove#989, unisdr/undrr-mangrove#996.)",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "raw": "New `react-doctor` component-quality linter codified as a project convention. Health score moved 57 → 68 across the sweep, with 66 of the original 398 findings cleared. (unisdr/undrr-mangrove#985, unisdr/undrr-mangrove#987, unisdr/undrr-mangrove#988, unisdr/undrr-mangrove#989, unisdr/undrr-mangrove#996.)"
        },
        {
          "category": "Code quality",
          "title": null,
          "description": "House conventions for component authors documented in `docs/AI-CODING-AGENTS.md`: no em-dashes or three-period ellipses in JSX text; prefer `use()` over `useContext()` on React 19+; hoist default `[]` / `{}` props to module-level constants; no inline render helpers (extract as named subcomponents); lazy `useState` init; effect cleanup discipline; per-call-site triage for `dangerouslySetInnerHTML` (inline DOMPurify vs documented caller contract). (unisdr/undrr-mangrove#992)",
          "pr": 992,
          "prRef": "unisdr/undrr-mangrove#992",
          "prUrl": null,
          "raw": "House conventions for component authors documented in `docs/AI-CODING-AGENTS.md`: no em-dashes or three-period ellipses in JSX text; prefer `use()` over `useContext()` on React 19+; hoist default `[]` / `{}` props to module-level constants; no inline render helpers (extract as named subcomponents); lazy `useState` init; effect cleanup discipline; per-call-site triage for `dangerouslySetInnerHTML` (inline DOMPurify vs documented caller contract). (unisdr/undrr-mangrove#992)"
        },
        {
          "category": "Code quality",
          "title": null,
          "description": "React Doctor health badge added to `README.md` and the Storybook _Introduction_ page. The badge is a manually-refreshed periodic snapshot; see `docs/AI-CODING-AGENTS.md#refreshing-the-score-badge`. (unisdr/undrr-mangrove#992, unisdr/undrr-mangrove#996.)",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "raw": "React Doctor health badge added to `README.md` and the Storybook _Introduction_ page. The badge is a manually-refreshed periodic snapshot; see `docs/AI-CODING-AGENTS.md#refreshing-the-score-badge`. (unisdr/undrr-mangrove#992, unisdr/undrr-mangrove#996.)"
        },
        {
          "category": "Code quality",
          "title": "Consumer-visible API note:",
          "description": "`Component.defaultProps` has been removed from `FooterIcons`, `FooterConditions`, `FooterConditions2`, `FooterLists`, `Link`, and `ScrollContainer`. React 19 deprecates `defaultProps` on function components; the defaults are now declared via destructured parameters, so component behaviour is unchanged. Only consumers that read `Component.defaultProps` for introspection (rare) need to adjust. (unisdr/undrr-mangrove#985, unisdr/undrr-mangrove#988)",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "raw": "**Consumer-visible API note:** `Component.defaultProps` has been removed from `FooterIcons`, `FooterConditions`, `FooterConditions2`, `FooterLists`, `Link`, and `ScrollContainer`. React 19 deprecates `defaultProps` on function components; the defaults are now declared via destructured parameters, so component behaviour is unchanged. Only consumers that read `Component.defaultProps` for introspection (rare) need to adjust. (unisdr/undrr-mangrove#985, unisdr/undrr-mangrove#988)"
        },
        {
          "category": "Code quality",
          "title": null,
          "description": "`useContext(SearchContext)` swapped to React 19's `use(SearchContext)` in `SyndicationSearchWidget` (unisdr/undrr-mangrove#988). Internal change; identical behaviour at top-level call sites.",
          "pr": 988,
          "prRef": "unisdr/undrr-mangrove#988",
          "prUrl": null,
          "raw": "`useContext(SearchContext)` swapped to React 19's `use(SearchContext)` in `SyndicationSearchWidget` (unisdr/undrr-mangrove#988). Internal change; identical behaviour at top-level call sites."
        },
        {
          "category": "Documentation",
          "title": null,
          "description": "`docs/AI-CODING-AGENTS.md` extended with the _Component quality checks with react-doctor_ section (above), linked from `CONTRIBUTING.md`, `docs/DEVELOPMENT.md`, and `docs/REVIEW-CHECKLIST.md`. (unisdr/undrr-mangrove#992)",
          "pr": 992,
          "prRef": "unisdr/undrr-mangrove#992",
          "prUrl": null,
          "raw": "`docs/AI-CODING-AGENTS.md` extended with the _Component quality checks with react-doctor_ section (above), linked from `CONTRIBUTING.md`, `docs/DEVELOPMENT.md`, and `docs/REVIEW-CHECKLIST.md`. (unisdr/undrr-mangrove#992)"
        },
        {
          "category": "Documentation",
          "title": null,
          "description": "Tracking issue unisdr/undrr-mangrove#986 documents the remaining `react-doctor` work, organised into risk buckets for future sweeps.",
          "pr": 986,
          "prRef": "unisdr/undrr-mangrove#986",
          "prUrl": null,
          "raw": "Tracking issue unisdr/undrr-mangrove#986 documents the remaining `react-doctor` work, organised into risk buckets for future sweeps."
        }
      ]
    }
  ],
  "componentChangelogs": {
    "design-decisions-container": {
      "name": "Container",
      "entries": [
        {
          "version": "1.4",
          "date": "2025-07-01",
          "notes": "Added `.mg-container--padded` variant with vertical padding",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2025-06-12",
          "notes": "Refined padding on `.mg-container` to not set top/bottom padding",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2024-10-28",
          "notes": "Added `.mg-container--slim` variant (limits width to desktop breakpoint)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2024-10-09",
          "notes": "Added `.mg-container--spacer` (`--mg-container-spacer`) for element spacing",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2024-10-09",
          "notes": "Initial release with responsive container component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "design-decisions-grid-layout": {
      "name": "Grid",
      "entries": [
        {
          "version": "1.3",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1266): The CSS section pointed at `.../assets/css/grid.min.css`, which the CDN has never served: no standalone grid stylesheet is emitted, the `assets/` segment is doubled, and nothing under Mangrove is minified. It now names the theme stylesheet the grid rules compile into, and the SCSS partial for Sass consumers. Documentation only; the component is unchanged.",
          "pr": 1266,
          "prRef": "unisdr/undrr-mangrove#1266",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-09-11",
          "notes": "([undrr/web-backlog#3060](https://gitlab.com/undrr/web-backlog/-/work_items/3060)): Added `.mg-grid--article` for reading-layout column splits (flexible content column + fixed rail), extracted for reuse from page-pattern-specific CSS.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-08-27",
          "notes": "Added `.mg-grid__col-7` through `.mg-grid__col-12` and `.mg-grid--auto-fit` to support larger or dynamic item counts.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-buttons-buttons": {
      "name": "CtaButton",
      "entries": [
        {
          "version": "1.5.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1243. PreventionWeb and IRP aimed `color.button-background--hover` and `color.button-outline-primary--hover` at the Sendai orange, so hovering a primary button put a white label on the accent at 2.95:1 in both themes. The hover is now a tonal step inside each brand: PreventionWeb `brand.teal-800` (#0c7575, white on it 5.5:1) and IRP `brand.blue-800` (#0d67a3, 6.02:1), the primitives their secondary buttons already use. `cta-button.scss` did not change: this arrived through the token layer, not the component. UNDRR, MCR2030 and DELTA hovers are unaffected.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.5.2",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1225): Fixes unisdr/undrr-mangrove#1201. Documents the two icon-only button classes and which one to reach for: `.mg-button--icon` is a shape modifier of `.mg-button` and carries the variant colours, and `.mg-icon-button` is the standalone ghost primitive that takes no `.mg-button-*` class. Combining them was not documented as wrong anywhere, and it rendered a dark glyph on the filled background (2.53:1 on UNDRR primary) because `.mg-button-primary` only sets a background and the readable foreground lives on `.mg-button`.",
          "pr": 1225,
          "prRef": "unisdr/undrr-mangrove#1225",
          "prUrl": null,
          "details": [
            "Added a contrast guard so `.mg-icon-button` with a filled `.mg-button-primary` or `.mg-button-secondary` takes the same `--mg-color-button` foreground its own hover already used. The combination is still the wrong markup (`.mg-button-outline` draws no border on it), but it no longer fails SC 1.4.3. Consumers who patched around it with their own low-specificity `color` rule on `.mg-icon-button` will now get the library value instead.",
            "Indexed the icon-only classes in the AI manifest (`components-buttons-buttons` CSS classes and examples, and the `mg-icon-button` utility entry), so an agent searching for \"icon button\" finds the documented pattern instead of only the primitive in the compiled CSS."
          ]
        },
        {
          "version": "1.5.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1173): Fixes unisdr/undrr-mangrove#1171. A standalone `.mg-icon-button` now owns its ghost styling (transparent fill, no border, pointer cursor) and its hover sets a light foreground on the dark interactive fill, so the icon stays visible in every theme (it was 1.75:1 on MCR2030). In forced-colors mode the hover shows an outline. A MegaMenu rule no longer overrides the primitive's `inline-flex` display and zero padding.",
          "pr": 1173,
          "prRef": "unisdr/undrr-mangrove#1173",
          "prUrl": null,
          "details": [
            "Added a \"Standalone icon button\" story that shows a bare `.mg-icon-button` on its own and inline within text.",
            "Filled primary, secondary and disabled buttons now paint their fill under the border, so the stroke matches the fill and they render the same size as outline buttons (previously a 1px white border made the fill look smaller). Themes can colour the stroke with the new `--mg-border-color-button-primary` and `--mg-border-color-button-secondary` tokens, which default to `transparent`.",
            "Icons in buttons are now vertically centred (they sat about 1.5px high). Icon-only buttons use an 18px icon so the glyph stays on the pixel grid, and buttons with an icon and a label are now 36px tall, the same as text-only buttons."
          ]
        },
        {
          "version": "1.5.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1157): Added `.mg-button--icon` and `.mg-icon-button` icon-only button variants with square aspect ratio, centered icons, and `.mg-button--icon--round` circular floating action treatment. Added `.mg-button--responsive-icon` for responsive icon+text buttons that collapse to icon-only on mobile, and added intrinsic `gap` spacing and `.mg-button__icon` rules for buttons with leading or trailing icons.",
          "pr": 1157,
          "prRef": "unisdr/undrr-mangrove#1157",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-15",
          "notes": "Define brand-owned secondary button tokens for PreventionWeb, MCR2030, and IRP themes, resolving an issue where secondary buttons fell back to default UNDRR corporate blue.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-09-10",
          "notes": "(unisdr/undrr-mangrove#1113): The arrow bubble slides a few pixels toward its destination on hover, flipped in RTL and suppressed under `prefers-reduced-motion`, where the shift would be a jump rather than a gesture.",
          "pr": 1113,
          "prRef": "unisdr/undrr-mangrove#1113",
          "prUrl": null
        },
        {
          "version": "1.3.2",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a button whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Refreshed React `CtaButton` instances as compact editorial links with an optically aligned circular direction badge, tactile press state, RTL direction, context-aware hero colours, and safe wrapping for narrow German, French and Arabic labels.",
            "Made the editorial treatment an explicit `Variant=\"CTA\"` option; conventional form and utility actions retain their filled button treatment.",
            "Switched button labels from Roboto Condensed to regular Roboto, strengthened and optically centred the CTA chevron, and kept label/badge geometry stable across hover and focus.",
            "Softened conventional buttons with the shared small-radius token, replaced physical adjacent margins with the RTL-safe `.mg-buttons` gap container, and hardened disabled links so caller props cannot restore navigation.",
            "Added focused behaviour and accessibility coverage, including smoother hover transitions, a clearer token-driven keyboard focus ring, and reduced-motion-aware `scale(0.96)` press feedback.",
            "Kept short editorial labels and their directional badges together by giving the label an explicit wrapper, preventing unnecessary wrapping when the surrounding card has room.",
            "Allowed long translated labels to wrap within narrow containers without pushing the CTA beyond the viewport, while positioning the decorative directional badge independently so it cannot distort label wrapping.",
            "Kept the public anchor markup flat by applying CTA wrapping directly to the link instead of introducing a label-only child element.",
            "Corrected the all-variants story's Hero fixture so its gradient overlay stays behind the dark-context controls instead of covering the Storybook canvas, and added the missing editorial CTA treatment to both light and Hero comparisons."
          ]
        },
        {
          "version": "1.3",
          "date": "2026-08-26",
          "notes": "Add composable `.mg-button-outline` modifier for primary and secondary emphasis variants.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-04-13",
          "notes": "`.mg-button` now declares an explicit `font-family: $mg-font-family-headings;` (was inheriting body Roboto). Aligns with the existing `:lang(ar)` override which already used `$mg-font-family-arabic-headings`; Latin and Arabic now match.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-22",
          "notes": "Add \"All variants\" comparison story (light + dark backgrounds). Fix secondary button contrast to meet WCAG AA (blue-800/blue-700, was blue-600/blue-400). Fix disabled button contrast (neutral-500 bg + white text). Remove dead `mg-button-arrow` class and `For_Primary` prop. Add `:focus-visible` ring for buttons on dark backgrounds. Tokenize secondary button and hero secondary button colors for theme overriding.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-buttons-chips": {
      "name": "Chips",
      "entries": [
        {
          "version": "1.2.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a chip whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Completed the React contract with link destinations, dismissal callbacks, localisable removal labels and composable classes while preserving the existing public class names.",
            "Added a visible dismissal mark, 40px targets, shared focus and surface tokens, reduced-motion-aware press feedback, logical RTL spacing, Arabic typography and safe long-label behaviour.",
            "Replaced the mismatched nested-control documentation with semantic link and single-button examples, and added dismissible, translated and RTL stories."
          ]
        },
        {
          "version": "1.1",
          "date": "2026-03-21",
          "notes": "Renamed CSS classes to `mg-` prefix (`chip` → `mg-chip`, `chip__cross` → `mg-chip__cross`).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-buttons-sharebuttons": {
      "name": "ShareButtons",
      "entries": [
        {
          "version": "1.10",
          "date": "2026-09-25",
          "notes": "Replaced the custom QR overlay with a native dialog. It now has an accessible name and description, browser-managed modal focus and Escape handling, and focus restoration to the QR trigger. Moved dialog styling to themeable tokens and reused the shared close icon button while retaining translated labels, QR copy and download actions, and backdrop dismissal.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.9",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1105): Quiet, unfilled sharing icons and a neutral URL-copy surface replace competing primary-button fills; hover uses a subtle neutral tint. Theme text colours and button radii remain in control; the share row wraps at narrow widths, long URLs and translated feedback wrap at readable text size, and keyboard focus and reduced-motion behaviour are consistent.",
          "pr": 1105,
          "prRef": "unisdr/undrr-mangrove#1105",
          "prUrl": null
        },
        {
          "version": "1.8.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to share-button text whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.8",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Removed redundant per-language Storybook exports; the default story already switches all six translated label sets and Arabic direction through the global locale toolbar.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.7",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Locale wiring extended from 2 keys/3 locales to 15 keys/6 locales. `_labels.js` provides `LABELS_ES/FR/JA/ZH/AR/RU`; locale toolbar now switches all translated strings in stories. Backwards compatible: `DEFAULT_SHARE_LABELS` covers all keys. Co-located `_labels.js` provides `LABELS_ES/FR/JA/ZH/AR/RU`; locale toolbar now switches translated strings in all stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.6",
          "date": "2026-05-11",
          "notes": "Declared PropTypes for `ShareButtons`, `QRCodeModal`, and `CopyButton`, so the AI manifest documents the prop contracts. No runtime change.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.5",
          "date": "2026-04-09",
          "notes": "Fixed invalid `alt` attribute on icon `<span>`: replaced with `aria-hidden=\"true\"` (WCAG 4.1.2)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-03-03",
          "notes": "Added hydration support with `fromElement` prop extraction and `createHydrator` integration",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-02-09",
          "notes": "Migrated icon classes from `fa-*` to `mg-icon-*` namespace",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2025-08-06",
          "notes": "Added QR code sharing with responsive modal, UTM tracking, copy-to-clipboard, and keyboard navigation",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2024-10-15",
          "notes": "Added shortlink support, Web Share API, updated Twitter to X icon, revised icon order and appearance",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-10-02",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-cards-book-card": {
      "name": "BookCard",
      "entries": [
        {
          "version": "1.5.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `mg-card--secondary` no longer paints its title in the orange accent (2.65 on the card surface, 2.37 on DELTA's). The title takes the interactive colour, and the accent becomes a 4px rule on the card's leading edge instead. `--mg-color-secondary` is unchanged. This is a visible change wherever the modifier is used, in all five themes.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): Links inside a card summary (`.mg-card__text-link`, and any anchor in `.mg-card__summary`) are underlined at rest instead of only on `:hover`. At 2.09:1 against the body colour, the interactive blue could not distinguish them on its own (WCAG 1.4.1 Use of Colour), and `link-in-text-block` was the most frequent axe violation in the 2.0 pre-release sweep. The underline thickens on hover. Button-styled links are excluded. Card titles are untouched: a linked title has always been underlined, and the `--plain-title` and empty-`href` opt-outs that remove that underline still do.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.4.0",
          "date": "2026-09-10",
          "notes": "Plain-text titles for unlinked cards, and `card.scss` fixes for empty `href`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3.5",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1109): Responsive collection with varied title lengths.",
          "pr": 1109,
          "prRef": "unisdr/undrr-mangrove#1109",
          "prUrl": null
        },
        {
          "version": "1.3.4",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic titles use `ui` font role.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.3.3",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Language-specific Arabic typography.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.3.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Unified focus states, raised surface, elevation.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-07-08",
          "notes": "Fix z-index conflict with MegaMenu.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-04-13",
          "notes": "Roboto Condensed headings; add `variant` prop.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-03-14",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Themeable card tokens.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-03-12",
          "notes": "Fix chevron orphaning; trim title whitespace.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-05",
          "notes": "Link affordances; `variant` prop; `mg-card--plain-title` modifier.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.2",
          "date": "2025-12-12",
          "notes": "Remove card share support.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2025-01-09",
          "notes": "Responsive fixes.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-03-01",
          "notes": "Initial release.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-cards-horizontal-book-card": {
      "name": "HorizontalBookCard",
      "entries": [
        {
          "version": "1.5.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `mg-card--secondary` no longer paints its title in the orange accent (2.65 on the card surface, 2.37 on DELTA's). The title takes the interactive colour, and the accent becomes a 4px rule on the card's leading edge instead. `--mg-color-secondary` is unchanged. This is a visible change wherever the modifier is used, in all five themes.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): Links inside a card summary (`.mg-card__text-link`, and any anchor in `.mg-card__summary`) are underlined at rest instead of only on `:hover`. At 2.09:1 against the body colour, the interactive blue could not distinguish them on its own (WCAG 1.4.1 Use of Colour), and `link-in-text-block` was the most frequent axe violation in the 2.0 pre-release sweep. The underline thickens on hover. Button-styled links are excluded. Card titles are untouched: a linked title has always been underlined, and the `--plain-title` and empty-`href` opt-outs that remove that underline still do.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.4.0",
          "date": "2026-09-10",
          "notes": "Plain-text titles for unlinked cards, visible labels, `buttonLink` prop, and `card.scss` fixes for empty `href`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3.4",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic titles/labels use `ui` font role.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.3.3",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Language-specific Arabic typography.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.3.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Unified focus states, raised surface, elevation.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-07-08",
          "notes": "Fix z-index conflict with MegaMenu.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-04-13",
          "notes": "Roboto Condensed headings; add `variant` prop.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-03-12",
          "notes": "Fix chevron orphaning; trim title whitespace.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-03-05",
          "notes": "Link affordances; `variant` prop; `mg-card--plain-title` modifier.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2025-12-12",
          "notes": "Remove card share support.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2024-09-20",
          "notes": "Handle missing images.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-09-04",
          "notes": "Initial release.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-cards-horizontal-card": {
      "name": "HorizontalCard",
      "entries": [
        {
          "version": "1.5.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `mg-card--secondary` no longer paints its title in the orange accent (2.65 on the card surface, 2.37 on DELTA's). The title takes the interactive colour, and the accent becomes a 4px rule on the card's leading edge instead. `--mg-color-secondary` is unchanged. This is a visible change wherever the modifier is used, in all five themes.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): Links inside a card summary (`.mg-card__text-link`, and any anchor in `.mg-card__summary`) are underlined at rest instead of only on `:hover`. At 2.09:1 against the body colour, the interactive blue could not distinguish them on its own (WCAG 1.4.1 Use of Colour), and `link-in-text-block` was the most frequent axe violation in the 2.0 pre-release sweep. The underline thickens on hover. Button-styled links are excluded. Card titles are untouched: a linked title has always been underlined, and the `--plain-title` and empty-`href` opt-outs that remove that underline still do.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.4.0",
          "date": "2026-09-10",
          "notes": "Plain-text titles for unlinked cards, visible labels, `buttonLink` prop, and `card.scss` fixes for empty `href`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3.5",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1109): Photos fill frame height when adjacent content is taller.",
          "pr": 1109,
          "prRef": "unisdr/undrr-mangrove#1109",
          "prUrl": null
        },
        {
          "version": "1.3.4",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic titles/labels use `ui` font role.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.3.3",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Language-specific Arabic typography.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.3.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Unified focus states, image hover (book covers excluded), raised surface, elevation.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-07-08",
          "notes": "Fix z-index conflict with MegaMenu.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-04-13",
          "notes": "Roboto Condensed headings; add `variant` prop.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-03-14",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Themeable card tokens.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-03-12",
          "notes": "Fix chevron orphaning; trim title whitespace.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-05",
          "notes": "Link affordances; `variant` prop; `mg-card--plain-title` modifier.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2025-12-12",
          "notes": "Remove card share support.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-01-24",
          "notes": "Initial release.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-cards-icon-card": {
      "name": "IconCard",
      "entries": [
        {
          "version": "1.4.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1247, unisdr/undrr-mangrove#1240): The repository entry point exports `IconCard` correctly. `src/index.js` re-exported a default that this component does not have, so the binding was `undefined` and rendering it threw `Element type is invalid`; it now uses the named form, matching the component and its hydration barrel. A new test asserts that no export from the entry point is `undefined`. Note that the published npm tarball ships neither `src/` nor `dist/` and its `main` points at a file it does not contain, so `import { IconCard } from '@undrr/undrr-mangrove'` does not resolve for an npm consumer either before or after this change; use `@undrr/undrr-mangrove/components/IconCard.js`, or hydrate through `IconCard.hydrate.js`, which was never affected.",
          "pr": 1247,
          "prRef": "unisdr/undrr-mangrove#1247",
          "prUrl": null
        },
        {
          "version": "1.4.2",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200): `.mg-card__icon--bordered` gives `--mg-card-border` a fallback of `rgb(var(--mg-color-interactive))`, so the class paints a border in the theme's accent colour when nothing sets the property. It was the only input hook in `card.scss` without a fallback, and an unset custom property drops the whole `border` declaration: a consumer writing the markup by hand got the extra padding and no border at all. React callers are unaffected: `borderColor` still sets the property inline and still wins. Documented the component's four custom properties and their defaults, including that none of them are theme tokens and none appear in `tokens.json`.",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): Links inside a card summary (`.mg-card__text-link`, and any anchor in `.mg-card__summary`) are underlined at rest instead of only on `:hover`. At 2.09:1 against the body colour, the interactive blue could not distinguish them on its own (WCAG 1.4.1 Use of Colour), and `link-in-text-block` was the most frequent axe violation in the 2.0 pre-release sweep. The underline thickens on hover. Button-styled links are excluded. Card titles are untouched: a linked title has always been underlined, and the `--plain-title` and empty-`href` opt-outs that remove that underline still do.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-09-10",
          "notes": "(unisdr/undrr-mangrove#1113):",
          "pr": 1113,
          "prRef": "unisdr/undrr-mangrove#1113",
          "prUrl": null,
          "details": [
            "Added `orientation=\"horizontal\"`, which places the icon beside the content rather than above it (the arrangement undrr.org already uses for \"what would you like to do\" route rows). It owns the visual's size via `--mg-card-icon-size` (default `3rem`), so `imageScale` does not apply.",
            "Fixed `centered`, which centred the text but left the icon pinned left. The visual is a column flex container, so its horizontal axis is the cross axis; the variant set `justify-content` and needed `align-items`."
          ]
        },
        {
          "version": "1.3.4",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1109): Added the mixed-content card group to the docs with a browser assertion for equal sibling heights. Fixed-size coloured badges no longer reserve an oversized illustration frame above their titles.",
          "pr": 1109,
          "prRef": "unisdr/undrr-mangrove#1109",
          "prUrl": null
        },
        {
          "version": "1.3.3",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic card titles and labels now render in Noto Sans Arabic instead of Noto Kufi Arabic. `.mg-card__title` and `.mg-card__label` read the `ui` font role rather than naming a typeface, and the component's `:lang(ar)` block is gone. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.3.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Unified title, CTA, and visual-link focus states behind the theme-adaptive `mg-focus-ring` mixin with a consistent radius.",
            "Added the shared raised card surface, elevation motion, and improved title/body wrapping through semantic modernisation tokens.",
            "Added an optional item-level `onClick` handler so card links and CTAs can integrate with client-side routers while retaining normal `href` behavior."
          ]
        },
        {
          "version": "1.3.1",
          "date": "2026-07-08",
          "notes": "Fixed z-index conflict with global navigation (MegaMenu) by creating local stacking context via `isolation: isolate` and lowering card-local z-indices from 100/101 to 1/2.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-04-13",
          "notes": "`.mg-card__title` and `.mg-card__label` now use `$mg-font-family-headings` (Roboto Condensed). Added a `:lang(ar)` block routing both to `$mg-font-family-arabic-headings`. Same change applies to all card variants.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-03-14",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Added `visualLabel` field to data items: renders a classification label above the icon/image inside the visual area. This is semantically separate from the existing `label` field (which renders as metadata inside the content area). Both can coexist on the same card. Added `borderColor` data prop for colored card borders. Added `iconFgColor` data prop for icon glyph color (distinct from `iconColor` which is the background). Icon badge border-radius is now a theme token (`$mg-card-icon-badge-radius`, default `20%` rounded square; Delta overrides to `15%`). `iconColor` now works without an `icon` class, rendering a standalone color swatch. Added themeable card container tokens (`$mg-card-background`, `$mg-card-padding`, `$mg-card-border-radius`).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-03-12",
          "notes": "Fix chevron caret orphaning on its own line. Trim trailing whitespace from title text.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-05",
          "notes": "Title links now show blue color, underline, and chevron indicator for better affordance. Added `mg-card--plain-title` CSS opt-out modifier. Added hydration support with `fromElement` prop extraction and `createHydrator` integration.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-01-13",
          "notes": "Initial release with icon/image support, centered variant, negative variant, logo mode with `srOnlyTitle`, and grid layout",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-cards-stats-card": {
      "name": "StatsCard",
      "entries": [
        {
          "version": "1.4.6",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1190): Links inside a card summary (`.mg-card__text-link`, and any anchor in `.mg-card__summary`) are underlined at rest instead of only on `:hover`. At 2.09:1 against the body colour, the interactive blue could not distinguish them on its own (WCAG 1.4.1 Use of Colour), and `link-in-text-block` was the most frequent axe violation in the 2.0 pre-release sweep. The underline thickens on hover. Button-styled links are excluded. Card titles are untouched: a linked title has always been underlined, and the `--plain-title` and empty-`href` opt-outs that remove that underline still do.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.4.5",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1192): The highlighted variant's accent uses `border-inline-start`, so it mirrors on its own; the `[dir=\"rtl\"]` override that re-drew it with physical borders is gone.",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null
        },
        {
          "version": "1.4.4",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1109): Mixed-content example with varied text, icons, labels, and linked cards.",
          "pr": 1109,
          "prRef": "unisdr/undrr-mangrove#1109",
          "prUrl": null
        },
        {
          "version": "1.4.3",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic values/labels use `ui` font role.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.4.2",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Language-specific Arabic typography.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-08-31",
          "notes": "Focus states use shared `mg-focus-ring` mixin.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-08-27",
          "notes": "Stat sets > 12 use auto-fit row fallback.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-04-13",
          "notes": "Roboto Condensed headings; `font-variant-numeric: tabular-nums lining-nums` for value alignment.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-03-04",
          "notes": "Hydration support with `fromElement` and `createHydrator`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-02-09",
          "notes": "Migrate icon classes to `mg-icon-*` namespace.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-01-07",
          "notes": "Initial release.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-cards-vertical-card": {
      "name": "VerticalCard",
      "entries": [
        {
          "version": "1.6.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `mg-card--secondary` no longer paints its title in the orange accent (2.65 on the card surface, 2.37 on DELTA's). The title takes the interactive colour, and the accent becomes a 4px rule on the card's leading edge instead. `--mg-color-secondary` is unchanged. This applies to every card layout that carries the modifier, and is a visible change in all five themes.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.5.5",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1193): The \"In separate columns\" story used a fixed three-column grid, so at 375px the demo pushed the page sideways. The columns now use `auto-fit` and stack on narrow viewports; the equal-height behaviour the story demonstrates is unchanged. Story only, no component change.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.5.4",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1198): Documented the `secondary` variant as a recorded contrast exception: an orange-accent title is 2.65 on the card surface, and no text colour clears AA on that hue. No colour changed; removal is tracked in unisdr/undrr-mangrove#1196.",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null
        },
        {
          "version": "1.5.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1190): Links inside a card summary (`.mg-card__text-link`, and any anchor in `.mg-card__summary`) are underlined at rest instead of only on `:hover`. At 2.09:1 against the body colour, the interactive blue could not distinguish them on its own (WCAG 1.4.1 Use of Colour), and `link-in-text-block` was the most frequent axe violation in the 2.0 pre-release sweep. The underline thickens on hover. Button-styled links are excluded. Card titles are untouched: a linked title has always been underlined, and the `--plain-title` and empty-`href` opt-outs that remove that underline still do.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.5.2",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1192): The card label's trailing gap uses `margin-inline-end`, so consecutive labels space correctly in RTL.",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null
        },
        {
          "version": "1.5.1",
          "date": "2026-09-15",
          "notes": "(unisdr/undrr-mangrove#1153): The card now fills 100% of its container's block size by default (excluding the book variant), not just when a shared flex/grid row's `align-items: stretch` happens to equalise it against sibling cards. Cards placed in their own separate column `<div>`s (a common CMS-templated three-up layout) previously stayed at their own content height even when the column itself was taller, with nothing visually tying same-row cards together. `block-size: 100%` resolves against the nearest ancestor with a real height and falls back to `auto` with none (a lone card with nothing to fill), so this is additive with no opt-in required.",
          "pr": 1153,
          "prRef": "unisdr/undrr-mangrove#1153",
          "prUrl": null
        },
        {
          "version": "1.5.0",
          "date": "2026-09-10",
          "notes": "Plain-text titles for unlinked cards, visible labels, `buttonLink` prop, and `card.scss` fixes for empty `href`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.5",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1109): Stretch to grid/flex row height; keep images intrinsic and CTAs at bottom.",
          "pr": 1109,
          "prRef": "unisdr/undrr-mangrove#1109",
          "prUrl": null
        },
        {
          "version": "1.4.4",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic titles/labels use `ui` font role.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.4.3",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Language-specific Arabic typography.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.4.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Unified focus states, image hover, raised surface, equal-height rows, target/rel fields.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-07-08",
          "notes": "Fix z-index conflict with MegaMenu.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-04-13",
          "notes": "Roboto Condensed headings; add `variant` prop.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-03-14",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Themeable card tokens.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-03-12",
          "notes": "Fix chevron orphaning; trim title whitespace.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-03-05",
          "notes": "Link affordances (color, underline, chevron); `variant` prop; `mg-card--plain-title` modifier.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2025-12-12",
          "notes": "Remove card share support.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2023-08-08",
          "notes": "Handle missing images.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-01-24",
          "notes": "Initial release.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-cite-this": {
      "name": "CitationExample",
      "entries": [
        {
          "version": "1.0.0",
          "date": "2026-09-25",
          "notes": "([web-backlog#334](https://gitlab.com/undrr/web-backlog/-/work_items/334)): Initial vanilla citation button and dialog with editorial, metadata, JSON-LD and page fallbacks, copy feedback, analytics events, labelled Storybook examples, and a zero-configuration HTML marker.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-codeblock": {
      "name": "CodeBlock",
      "entries": [
        {
          "version": "1.2.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1193): Gutter line numbers move from `--mg-color-neutral-400` to `--mg-color-neutral-300`. The old value measured 4.39:1 against the neutral-800 code surface and so failed axe's colour-contrast rule on every line of the two line-number stories; the new value is 6.08:1. The neutral ramp is shared by every theme, so this holds in all five.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.2.0",
          "date": "2026-09-01",
          "notes": "",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "New component extracted from Base Typography",
            "Added `filename` prop for the `figure.mg-code-block` wrapper",
            "Simplified API: `code` (required), `language` (optional), `filename` (optional)",
            "CSS: removed left accent border; clean `border-radius: 6px`; language badge via `data-language` / CSS `:has()`; RTL badge positioning"
          ]
        }
      ]
    },
    "components-cta": {
      "name": "TextCta",
      "entries": [
        {
          "version": "1.3.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `variant=\"secondary\"` is no longer a flat orange panel with a white eyebrow (2.65), headline (2.65) and 90%-white body (2.42). It now takes the `soft` surface (white under an 8% wash of the accent) with dark copy and standard buttons, and keeps the Sendai orange as a 6px leading rule. The variant is a soft surface at both tones, so `tone=\"strong\"` no longer changes its fill. `--mg-color-hero--secondary` is unchanged and now drives the rule. This is a visible change wherever the variant is used, in all five themes.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.2.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1198): Documented the strong `secondary` accent as a recorded contrast exception: white eyebrow, headline and body on the orange accent meet neither contrast measure at any size. No colour changed; removal is tracked in unisdr/undrr-mangrove#1196.",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null
        },
        {
          "version": "1.2.2",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1173): Filled buttons on strong surfaces get a light tinted stroke so they keep a visible edge when their fill matches the banner colour.",
          "pr": 1173,
          "prRef": "unisdr/undrr-mangrove#1173",
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-09-14",
          "notes": "(unisdr/undrr-mangrove#1142): Strong-tone `TextCta` now applies dedicated editorial CTA link colours for `mg-button-cta`, so the label and arrow badge keep proper contrast on dark accent surfaces instead of inheriting generic button link colouring.",
          "pr": 1142,
          "prRef": "unisdr/undrr-mangrove#1142",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-09-09",
          "notes": "Added an optional composed-content slot (for forms and richer CTA layouts), a reusable soft-accent `data-tone`, and consolidated CTA form examples into shared stories. ([web-backlog #2412](https://gitlab.com/undrr/web-backlog/-/work_items/2412))",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Added an optional eyebrow and responsive inline content/action layout for wide promotional banners.",
            "Composed action links through `CtaButton`, including editorial, outline and external-link options; hardened text contrast against surrounding typography and made outline borders reliable on dark CTA surfaces."
          ]
        },
        {
          "version": "1.0",
          "date": "2026-03-14",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): First production release of the `mg-cta` banner with theme variants, custom background color, button arrays, sanitized HTML text, optional image layout, centered mode, and configurable headline size/padding.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.1",
          "date": "2022-12-12",
          "notes": "Initial placeholder (never used in production)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-dataviz-legend": {
      "name": "Legend",
      "entries": [
        {
          "version": "1.0.6",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The usage example imports `@undrr/undrr-mangrove/components/Legend.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. The bundle exports `Legend` as a named export and has no default, so the braces are load-bearing. Documentation only; the component is unchanged.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.0.5",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1197): Documentation only. Says outright that Legend has no vanilla lifecycle module and that none is planned, because a legend is static presentation with no state, no interactive controls and no keyboard model, so a CSS-only adoption is the complete component rather than a partial one. Points anyone who needs interactive legend entries at a Checkbox or switch beside each entry, or a Drawer, kept outside the legend's own `role=\"group\"`. The AI manifest states the same through `vanillaModule: false` and a `vanillaModuleNote`, so it can be read without probing `/js/` for a 404.",
          "pr": 1197,
          "prRef": "unisdr/undrr-mangrove#1197",
          "prUrl": null
        },
        {
          "version": "1.0.4",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200): `.mg-legend__list` hides its markers with `list-style: none` followed by `list-style-type: \"\"` (the new `mg-list-unmarked` mixin). Safari drops the implicit `list` role from a markerless list, so VoiceOver stopped announcing the entry count; an empty string is still a marker, so the role survives and nothing renders. No `role=\"list\"` is needed on the `<ul>`, and no markup or layout changes.",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null
        },
        {
          "version": "1.0.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1193): Removed the \"Dark theme\" story. It wrapped the legend in an `mg-theme-dark` class that the library never defined and set a `Dark` theme override that does not exist, so it rendered labels at 1.2:1 and implied a dark-surface API that Legend does not have. The docs now say plainly that Legend is styled for light surfaces.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1194): Documentation only. Removed a duplicated `Continuous` canvas and expanded the page: when to use each type, React usage with tick positioning, the plain HTML equivalent, a CSS class reference, accessibility notes and theme behaviour.",
          "pr": 1194,
          "prRef": "unisdr/undrr-mangrove#1194",
          "prUrl": null
        },
        {
          "version": "1.0.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1159): Added map and chart legend primitives (`continuous`, `categorical`, `stepped`).",
          "pr": 1159,
          "prRef": "unisdr/undrr-mangrove#1159",
          "prUrl": null,
          "details": [
            "Before rc.1: legend is a group named by its title, stepped colours have text alternatives, a single tick no longer divides by zero, font size uses a real token, and tick placement uses logical properties."
          ]
        }
      ]
    },
    "components-embedcontainer": {
      "name": "EmbedContainer",
      "entries": [
        {
          "version": "1.0",
          "date": "2026-03-10",
          "notes": "Initial release with 16:9 (default), 4:3, 1:1, and 21:9 aspect ratio variants. Replaces the broken `.mg-highlight-box:has(iframe)` approach. [`web-backlog#2672`](https://gitlab.com/undrr/web-backlog/-/work_items/2672)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-empty-state": {
      "name": "Empty state",
      "entries": [
        {
          "version": "1.0.3",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1174): Added guidance and a story on choosing the title's heading level to fit the surrounding outline (unisdr/undrr-mangrove#1172). Markup and styles are unchanged.",
          "pr": 1174,
          "prRef": "unisdr/undrr-mangrove#1174",
          "prUrl": null
        },
        {
          "version": "1.0.2",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic empty-state titles now render in Noto Sans Arabic instead of Noto Kufi Arabic. The title reads the `ui` font role rather than naming a typeface, and the component's `:lang(ar)` block is gone. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to empty-state text whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-02",
          "notes": "Initial release. Layout sourced from the DELTA design system \"Empty State\" boards; text and glyph colours raised to meet WCAG 2.2 AA.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-error-pages": {
      "name": "ErrorPage",
      "entries": [
        {
          "version": "1.4.4",
          "date": "2026-09-23",
          "notes": "Copy no longer uses the first person, following the [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs). Applies to the default copy in the component and to all eleven static templates. The 404 heading is now \"This page can't be found\"; the 500 heading drops \"on our side\" and its description no longer repeats the contact prompt; the 5xx description starts \"Your request couldn't be completed\"; and the contact links read \"contact UNDRR\" instead of \"contact us\".",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.3",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): The UNDRR logo link now carries `aria-label=\"UNDRR home\"`. Its only child is an `aria-hidden` div, so the link had no accessible name at all (axe `link-name`; WCAG 2.4.4 / 4.1.2). Fixed in the component and, separately, in all eleven static templates served by Cloudflare. The two are not generated from one source and their markup differs (the templates inline their own stylesheet and render `mg-error-page__container` where the component renders `mg-error-page__container mg-container--spacer`), so the logo anchor has to be kept in step by hand. Links inside the body copy are also underlined, since colour alone did not distinguish them from the surrounding text (WCAG 1.4.1).",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.4.2",
          "date": "2026-09-09",
          "notes": "Asset URLs in the stylesheet and static error templates now use the canonical CDN root.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Replaced the challenge panel's hard-coded radius and shadow with shared card surface tokens so it follows theme expression.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.4.0",
          "date": "2026-03-21",
          "notes": "Converted inline styles from 10px-root rem values to px. Static templates now work correctly with the standard 1.4 theme (no root font-size override). (unisdr/undrr-mangrove#857)",
          "pr": 857,
          "prRef": "unisdr/undrr-mangrove#857",
          "prUrl": null
        },
        {
          "version": "1.3.0",
          "date": "2026-02-13",
          "notes": "Challenge pages now use neutral background and teal accent to differentiate from error pages ([#2615](https://gitlab.com/undrr/web-backlog/-/issues/2615))",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.0",
          "date": "2025-11-25",
          "notes": "Added 401, 500, 504, 5xx, challenge, managed-challenge static templates; client-side URL display; `vf:page-type` meta tags ([#2556](https://gitlab.com/undrr/web-backlog/-/issues/2556))",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2025-11-25",
          "notes": "Added Cloudflare integration with error tokens and deferred asset loading ([#2556](https://gitlab.com/undrr/web-backlog/-/issues/2556))",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0",
          "date": "2025-08-12",
          "notes": "Initial release with standard error pages and examples",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-footer": {
      "name": "Footer",
      "entries": [
        {
          "version": "1.0.0-beta.6",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic about-footer links now render in Noto Sans Arabic instead of Noto Kufi Arabic. `.mg-footer--about-footer--links` reads the `ui` font role rather than naming a typeface, and the component's `:lang(ar)` block is gone. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.0.0-beta.5",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to about-footer links whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.0.0-beta.4",
          "date": "2026-04-13",
          "notes": "`.mg-footer--about-footer--links` now uses `$mg-font-family-headings` (Roboto Condensed). Added a `:lang(ar)` block routing to `$mg-font-family-arabic-headings`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0-beta.3",
          "date": "2026-03-16",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Added `mg-footer-bar` utility component for sub-branded site footer navigation. Footer social links now inherit color from parent container instead of forcing white (the parent `.mg-footer--about-footer` already sets white link color). Default `includecss` changed to `false` to avoid double-loading CSS that Mangrove provides.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0-beta.2",
          "date": "2026-03-14",
          "notes": "Mobile responsive fixes: flex-wrap on social and about-footer links, grid span reset below tablet, button stacking, image overflow constraint, link-spacer stacking for syndicated content; disabled widget injected CSS (`includecss: false`)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0-beta.1",
          "date": "2025-10-17",
          "notes": "Added `mg-footer` root class, `.mg-footer--about-footer` modifier, `footer.scss`, and Gutenberg spacer reset",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0-alpha.1",
          "date": "2022-12-12",
          "notes": "Initial UNDRR-based release (proof of concept)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-forms-checkbox": {
      "name": "Checkbox",
      "entries": [
        {
          "version": "2.4",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1214): Gave `.mg-switch` an error state and size hooks. A default switch renders exactly as before.",
          "pr": 1214,
          "prRef": "unisdr/undrr-mangrove#1214",
          "prUrl": null,
          "details": [
            "Error state (unisdr/undrr-mangrove#1187): `aria-invalid=\"true\"` on the input, `.mg-switch__input--error`, or `.mg-switch--error` on the label, each giving the track a red boundary, with a `.mg-form-error` message joined by `aria-describedby`: the same pattern, class naming and error summary relationship as the text input, select, radio and checkbox. Unlike the pending state it does not move the thumb, so a controller whose latest intent wins does not contradict the user. `--mg-switch-track-border-color--error` sets the colour.",
            "The track's inset around the thumb is now a transparent border rather than padding. The box, the thumb and the travel are unchanged; the error state has a `border-color` to set, so it cannot displace the focus ring's outline and separator band the way a consumer `box-shadow` did. In forced colours, where the track opts out of colour forcing, the error is `CanvasText` and a dashed edge instead of an author red.",
            "Size hooks (unisdr/undrr-mangrove#1199): `--mg-switch-size` sizes the whole switch, with `--mg-switch-track-inline-size`, `--mg-switch-track-block-size`, `--mg-switch-track-inset` and `--mg-switch-thumb-size` for a single part. The thumb's travel, its RTL counterpart and the pending ring all derive from them, so resizing a switch no longer means re-declaring the `transform`, an override that also beat Mangrove's `[dir=\"rtl\"]` rule and sent the thumb the wrong way in Arabic.",
            "Added `.mg-switch--small` (a 1.125rem switch) for a toolbar, table row or panel header, so a shared size exists instead of each consumer inventing one. The row keeps its 36px minimum height for the touch target.",
            "Forced colours no longer takes 1px off the track's padding to make room for its border; it colours the border the track already has, so the travel holds at any size. The visible boundary is the track's inset rather than a fixed 1px, so at the default size it is 2px rather than 1px.",
            "Added \"Switch error\" and \"Switch sizes\" stories. The error story shows a text input in error beside the switch."
          ]
        },
        {
          "version": "2.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1213): Made revert-on-failure and the timeout optional in `js/switch-pending.js`, and released the announcements on their own (unisdr/undrr-mangrove#1189). Defaults are unchanged. One caveat for existing callers: `timeout: 0` was a silent no-op in rc.2 (it failed a `> 0` check and fell back to the ten-second deadline), so anyone already passing `0`, or a `parseInt` that produced it, loses that safety net and a hung save now leaves the switch busy until `destroy()`.",
          "pr": 1213,
          "prRef": "unisdr/undrr-mangrove#1213",
          "prUrl": null,
          "details": [
            "`revert: false` (or `data-mg-switch-revert=\"false\"`) keeps the position the user asked for when a save fails, for apps that report a failure in the row rather than by moving the control. The helper sets `aria-invalid=\"true\"` on the input instead, and puts back whatever was there when the next save starts or on `destroy()`, so an `aria-invalid` the page set itself survives.",
            "`timeout: 0` or `Infinity` (or `data-mg-switch-timeout`) switches the deadline off for a save that legitimately runs long. `aria-busy` then stays true until the save settles, so the helper announces \"Still saving…\" once at ten seconds, where the deadline would have been.",
            "Added `mgSwitchAnnouncer(element, { status, labels })`: the live region, label resolution, re-announce trick and rate limit, with `pending()`, `stillSaving()`, `settled()`, `failed()`, `announce()`, `label()` and `destroy()`, and no listeners or state. `mgSwitchPending()` runs it, so there is one implementation.",
            "`mg-switch:failed` now carries `reverted`, and its `checked` is the position the switch is left in either way.",
            "The interactive story has `revert` and `timeout` controls."
          ]
        },
        {
          "version": "2.2",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1184): Added a pending state for `.mg-switch`, set with `aria-busy=\"true\"` or `.mg-switch--pending`, with a static indicator for reduced motion. Styled `aria-disabled` switches, made the switch visible in forced colours, and added `--mg-switch-*` custom properties.",
          "pr": 1184,
          "prRef": "unisdr/undrr-mangrove#1184",
          "prUrl": null,
          "details": [
            "`aria-disabled` dims the track with a page-coloured layer instead of opacity, so the focus ring stays at full strength.",
            "Forced colours: disabled switches, on or off, use a `Canvas` track with `GrayText` parts and no opacity; the track boundary is a border, so the focus outline no longer replaces it, and the white focus band is dropped; the `.mg-switch--pending` ring spins with a visible gap when off.",
            "The `--mg-switch-*` properties are read with their defaults where used, so they can be set on any ancestor. Added `--mg-switch-track-overlay--disabled`.",
            "The pending example and interactive story announce \"Saving…\", \"Still saving…\" for ignored presses, and the outcome; guard `change` as well as `click`; ignore stale responses; and time out a request that never answers.",
            "Added `js/switch-pending.js`, a dependency-free helper that does this for plain HTML and Drupal sites, replacing the copy-paste example. The interactive story runs the same helper.",
            "`mgSwitchPending(input, { save, status, timeout, labels, signal })` returns `{ destroy() }`. `mgSwitchPendingInit(scope, options)` and `mgSwitchPendingDestroy(scope)` work on `[data-mg-switch-pending]` inputs or labels.",
            "Without a `save` function, a cancelable `mg-switch:save` event asks a site-wide listener to call `respondWith(promise)`. A listener that awaits first calls `preventDefault()` before its first `await`. If no listener does either, the switch reverts with a console warning.",
            "Marked switches are enhanced on page load unless they carry `data-mg-switch-pending-skip-auto-init`. An explicit call takes over a switch that auto-initialization enhanced. Copies of the script loaded twice share one registry.",
            "Switches can share a live region. The announced label follows the accessible name.",
            "Dispatches `mg-switch:pending`, `mg-switch:settled` and `mg-switch:failed`. Announcements can be translated with `labels` or `data-mg-switch-labels` JSON."
          ]
        },
        {
          "version": "2.1",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1157): Added `.mg-switch` accessible binary toggle switch control (`role=\"switch\"`) with sliding thumb, interactive brand fill on check, focus ring integration, RTL translation support, and reduced-motion support.",
          "pr": 1157,
          "prRef": "unisdr/undrr-mangrove#1157",
          "prUrl": null
        },
        {
          "version": "2.0",
          "date": "2026-02-24",
          "notes": "**Breaking:** Rewritten with BEM classes, PropTypes, and accessibility",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-forms-form-action": {
      "name": "FormAction",
      "entries": [
        {
          "version": "1.0",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Added the generic joined field-and-action composition with logical RTL corners, accessible labels and descriptions, optional mobile stacking, React and vanilla HTML usage, and search, subscribe and select examples.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        }
      ]
    },
    "components-forms-formerrorsummary": {
      "name": "FormErrorSummary",
      "entries": [
        {
          "version": "1.3.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1192): The red accent bar and the error list indent use `border-inline-start` and `padding-inline-start`, so they sit at the start of the text in RTL instead of staying on the physical left.",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Removed redundant per-language story exports; use the global Storybook locale toolbar for translated content and RTL direction.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Locale map extended to all 6 languages (ES, FR, JA, ZH, AR, RU); locale toolbar now switches translated strings in stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-02-24",
          "notes": "Fix: focus now fires only once on initial mount, preventing focus theft when individual errors are cleared",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-02-24",
          "notes": "Initial implementation with linked error list, focus management, and RTL support",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-forms-formgroup": {
      "name": "FormGroup",
      "entries": [
        {
          "version": "1.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Added a soft neutral grouped surface, subtle shared-radius border, deliberate internal spacing, and clearer disabled and error boundaries. The component laboratory now uses FormGroup instead of an unstyled native fieldset.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-02-24",
          "notes": "Initial implementation with fieldset/legend, disabled, error, and hideLegend support",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-forms-radio": {
      "name": "Radio",
      "entries": [
        {
          "version": "2.0",
          "date": "2026-02-24",
          "notes": "**Breaking:** Rewritten with BEM classes, PropTypes, and accessibility",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-forms-segmented-control": {
      "name": "SegmentedControl",
      "entries": [
        {
          "version": "1.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1188): Initial release. Segmented presentation of a radio group, CSS only, with `--small` and `--full-width` modifiers and forced-colours support. Known at release: no error state (use Radio in a FormGroup), `--full-width` shrinks rather than wraps, and the hover fill is 2.95:1 in PreventionWeb and IRP until unisdr/undrr-mangrove#1249 re-points the token.",
          "pr": 1188,
          "prRef": "unisdr/undrr-mangrove#1188",
          "prUrl": null
        }
      ]
    },
    "components-forms-select": {
      "name": "Select",
      "entries": [
        {
          "version": "2.1.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a select whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "2.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Aligned native selects with the softer light filled input surface, subtle neutral border, small radius and token-driven focus ring.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "2.0",
          "date": "2026-02-24",
          "notes": "**Breaking:** Rewritten from stub with options prop, BEM classes, and accessibility",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Initial release (stub)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-forms-text-input": {
      "name": "TextInput",
      "entries": [
        {
          "version": "2.1.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a text input whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "2.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Replaced the stark black outline with a subtle neutral border, light filled surface, small shared radius and clearer token-driven focus ring.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "2.0",
          "date": "2026-02-24",
          "notes": "**Breaking:** Rewritten with BEM classes, accessibility, and PropTypes (replaces InputFields)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Initial release (as InputFields)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-forms-textarea": {
      "name": "Textarea",
      "entries": [
        {
          "version": "2.1.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a textarea whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "2.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Adopted the shared light filled field surface, subtle neutral border, small radius and token-driven focus ring.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "2.0",
          "date": "2026-02-24",
          "notes": "**Breaking:** Rewritten with BEM classes, accessibility, and PropTypes",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-fullwidth": {
      "name": "FullWidth",
      "entries": [
        {
          "version": "1.3.1",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): Corrected the note on the `FullWidth` wrapper component, which said it \"is not exported from `@undrr/undrr-mangrove`, so it is only available inside this repository\". It has had a webpack entry since unisdr/undrr-mangrove#847, so the npm package has been publishing `components/FullWidth.js` all along and the page was telling React consumers a usable component did not exist. The note now gives the working import line. Documentation only; neither the component nor the CSS changed.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1203): Fixed the right-to-left bleed landing about 13px off centre inside a flex parent such as `mg-mega-topbar`, which left an unpainted strip at one edge and 13px of page scroll at 1280. The `[dir=\"rtl\"]` rule now anchors the pseudo-element with `left: 50%` and `margin-left: -50vw` rather than relying on its static position. Left-to-right rendering is untouched: the rule is scoped to `[dir=\"rtl\"]`. Added [page-level setup](#page-level-setup), which documents the half-scrollbar overhang every `100vw` escape leaves on a vertically scrolling page, the `overflow-x: clip` a consuming site adds to fix it, and what that clip costs. No CSS change for the overhang: the library does not ship the page-root rule.",
          "pr": 1203,
          "prRef": "unisdr/undrr-mangrove#1203",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1193): Breaking: deleted the deprecated `FullWidth.scss` alias, which only forwarded to `full-width.scss` and emitted a deprecation `@warn`. The npm package no longer ships `scss/Utilities/FullWidth/FullWidth.scss`; import `scss/Utilities/FullWidth/full-width` instead. No CSS behaviour change.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-09-10",
          "notes": "(unisdr/undrr-mangrove#1118): Documented the ancestor-background constraint, how to diagnose it, the three ways to work around it, and two symptoms that look like it but are not. No CSS behaviour change.",
          "pr": 1118,
          "prRef": "unisdr/undrr-mangrove#1118",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-03-16",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-gallery": {
      "name": "Gallery",
      "entries": [
        {
          "version": "1.4.4",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1247, unisdr/undrr-mangrove#1235): The thumbnail ref callback is a statement body instead of a parenthesised assignment, so it no longer returns the element it assigns. React 19.3 does not reject that return value (it stores it as the ref cleanup, finds it is not a function and still calls `ref(null)` on detach), so this was correct as written; the hazard React 19 introduced is a ref callback returning a _function_, which silently replaces the detach call. Found by widening ESLint coverage to component `.jsx`, which had never been linted. No visual or behavioural change.",
          "pr": 1247,
          "prRef": "unisdr/undrr-mangrove#1247",
          "prUrl": null
        },
        {
          "version": "1.4.3",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): A `type: 'html'` thumbnail preview is now `inert`, and any focusable element inside it is given `tabindex=\"-1\"`. The injected author markup sits inside `<button role=\"tab\">` under `aria-hidden=\"true\"`, so links and buttons in it were focusable children of an aria-hidden node and interactive descendants of a button (axe `aria-hidden-focus` and `nested-interactive`, both serious). The tab itself is unchanged and stays keyboard operable.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.4.2",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic gallery titles now render in Noto Sans Arabic instead of Noto Kufi Arabic. `.mg-gallery__title` reads the `ui` font role rather than naming a typeface, and the component's `:lang(ar)` block is gone. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a gallery title whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Preserved the circular carousel control language while raising main and corner navigation arrows to the recommended 44px interaction target across desktop and mobile. Removed redundant per-language story exports in favour of the global locale toolbar.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Consolidated `galleryAriaLabel`, `prevLabel`, `nextLabel`, `loadingLabel` props into a single `labels` object with `DEFAULT_GALLERY_LABELS` export. Backwards compatible: `DEFAULT_GALLERY_LABELS` covers all keys. Co-located `_labels.js` provides `LABELS_ES/FR/JA/ZH/AR/RU`; locale toolbar now switches translated strings in all stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-04-13",
          "notes": "`.mg-gallery__title` now uses `$mg-font-family-headings` (Roboto Condensed). Added a `:lang(ar)` block routing to `$mg-font-family-arabic-headings`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-04",
          "notes": "Added hydration support with `fromElement` prop extraction and `createHydrator` integration",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-01-07",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "---"
          ]
        }
      ]
    },
    "components-hero-hero": {
      "name": "Hero",
      "entries": [
        {
          "version": "1.9.0",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1264): **Consumer-visible change to the hero's mobile composition.** Below 900px the background layout no longer puts copy over the photograph. The photograph is a banner across the top of the hero, `--mg-hero-banner-block-size` deep (`min(56vw, 20rem)`, so 218px on a 390px phone), and the copy sits beneath it on the theme's hero colour under the same 0.16 scrim the desktop copy band wears. The hero is taller there, because the two are stacked rather than overlaid, and a page whose first screenful was \"image with words on it\" is now \"image, then words\". `size=\"immersive\"` drops its `min-height` below the breakpoint and spends the room on a deeper banner (`min(78vw, 27.5rem)`) instead, because that height existed to give the photograph space _behind_ copy that is no longer there. Nothing changes above 900px. The reason is that the vertical veil could not be made to work. The copy filled the frame while the veil faded toward the top, so the eyebrow label sat where the tint was at its 0.08 end opacity and **failed in all five themes at every width from 320px to the breakpoint**: IRP 2.25–2.43, UNDRR 2.21–2.40, PreventionWeb 2.39–2.58, MCR2030 2.68–3.03, DELTA 2.80–3.15 against the 4.5 bar, with IRP's title also under the 3:1 large-text bar at 320px and 360px (2.91, 2.99). 72 of 210 measured cells failed. With no photograph behind the copy the contrast is a token pair again, so it is solved rather than sampled, and the figure no longer depends on the image: IRP 6.19, PreventionWeb 8.12, UNDRR and DELTA 10.08, MCR2030 13.67, identical to within 0.01 over a near-white wall, a high-key sky, a busy high-frequency pattern and a white-sky landscape. 0 of 210 cells fail, LTR and RTL, at 320, 360, 390, 414, 600, 768 and 900. A horizontal re-sweep at 910, 1164, 1440 and 1920 in both directions returns all 120 cells identical to the figures unisdr/undrr-mangrove#1256 shipped. The two colour options that were costed instead (holding the tint full height, or raising the mobile scrim to about 0.44) were rejected because both buy the copy by spending the photograph at exactly the width where there is least of it; the measurements and the reasoning are in [the hero scrim](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/COLOUR-CONTRAST-METHODOLOGY.md#the-hero-scrim). New property: `--mg-hero-banner-block-size`. CSS only: no new element, no script, so consumers who hand-author the markup get the same composition. One case the CSS cannot decide for itself: a hero with **no** background image has nothing for the banner to inherit, and the reserved band would be a third of a phone screen of flat brand colour above the copy, so `Hero` and `ChildHero` now add `mg-hero--no-image` when `imgback` is absent and the banner collapses to nothing there (the copy keeps the panel and the same figures). The inline `background-image` is dropped with it, rather than emitting the `url(undefined)` every image-less hero used to request and 404. Hand-authored markup without a background image has to add that class itself.",
          "pr": 1264,
          "prRef": "unisdr/undrr-mangrove#1264",
          "prUrl": null
        },
        {
          "version": "1.8.0",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1256): The background layout's gradient gains a neutral scrim above the brand tint, so body copy clears 4.5:1 against a bright photograph in every theme. IRP was failing at 3.91 in both the default and `--secondary` variants; it now measures 5.14, and the four themes that already passed gain between 1.4 and 1.5. Both layers of the veil now hold at full strength across the copy and clear beyond it, reading `--mg-hero-copy-inline-end` rather than a fixed 48%, which also fixes the 910px–1164px band where the copy ran well past the old midpoint. New tokens: `--mg-hero-scrim-opacity` (0.16), `--mg-hero-scrim-fade`, and `--mg-hero-scrim-color`, which a theme may repoint. Visible change in all five themes: the copy band is about 16% deeper, and above 900px the photograph beyond it is not; below 900px the scrim is held flat across the whole frame, so there the photograph is 16% deeper too. Two conditions the band depends on are now enforced rather than assumed: a single unbreakable word (a long German compound, a URL) used to size the title past the copy column and paint over the bare photograph, which `max-width: 100%` and `min-width: 0` stop; and `--contained`, which takes its parent's width rather than the viewport's, now zeroes `--mg-hero-copy-inline-start` with its padding and clamps the band to the element, so a contained hero narrower than the band keeps its photograph instead of being veiled edge to edge. Note also that the before-figures below are a property of the photograph sampled, not of the themes: over a near-white image all five themes failed before this change (IRP 2.51, PreventionWeb 2.91, UNDRR 3.34, MCR2030 4.06, DELTA 4.33). Below 900px the vertical veil still fails in every theme; see [the hero scrim](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/COLOUR-CONTRAST-METHODOLOGY.md#the-hero-scrim) for the measurements and the options.",
          "pr": 1256,
          "prRef": "unisdr/undrr-mangrove#1256",
          "prUrl": null
        },
        {
          "version": "1.7.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1203): Documented the half-scrollbar overhang the `100vw` full-bleed escape leaves on a vertically scrolling page, and pointed at the page-root `overflow-x: clip` a consuming site adds to absorb it. No CSS behaviour change: the rule belongs to the site, not the library, because it clips content Mangrove cannot see.",
          "pr": 1203,
          "prRef": "unisdr/undrr-mangrove#1203",
          "prUrl": null
        },
        {
          "version": "1.7.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `variant=\"secondary\"` left the variant colour map, so its surface is the theme's ordinary hero colour in both the split and background layouts, and its CTA label takes the default hero colour on the white pill. Both pairings were 2.65. The Sendai orange is now an 8px rule on the leading edge, read from `--mg-color-hero--secondary`, so a theme that retargets that token moves the rule with it. The token value is unchanged. This is a visible change wherever the variant is used, in all five themes; the \"Split (1/2 balanced)\" story shows it.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.6.8",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1198): Documented `variant=\"secondary\"` as a recorded contrast exception: white copy on the orange accent meets neither contrast measure at any size. No colour changed; removal is tracked in unisdr/undrr-mangrove#1196.",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null
        },
        {
          "version": "1.6.7",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1192): The full-bleed escape uses `margin-inline-start` / `padding-inline-start` instead of physical left properties, so the hero mirrors in RTL without an override. Split heroes no longer break out of the viewport in Arabic at mobile widths (the page scrolled ~16px wider than the screen). The RTL blocks now only flip the background gradient and the split column order.",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null
        },
        {
          "version": "1.6.6",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1173): Filled buttons on the hero get a light tinted stroke (`--mg-border-color-button-primary`/`-secondary`) so they keep a visible edge when their fill matches the hero colour.",
          "pr": 1173,
          "prRef": "unisdr/undrr-mangrove#1173",
          "prUrl": null
        },
        {
          "version": "1.6.5",
          "date": "2026-09-15",
          "notes": "Removed `max-width: 12ch` constraint from immersive hero title.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.6.4",
          "date": "2026-09-10",
          "notes": "([undrr/web-backlog#3060](https://gitlab.com/undrr/web-backlog/-/work_items/3060)):",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "Removed restrictive title width caps and aligned split-layout spacing to shared container rhythm.",
            "Simplified title markup/styles by removing dead wrappers and applying `mg-hero__title` directly to the heading."
          ]
        },
        {
          "version": "1.6.3",
          "date": "2026-09-10",
          "notes": "(unisdr/undrr-mangrove#1113):",
          "pr": 1113,
          "prRef": "unisdr/undrr-mangrove#1113",
          "prUrl": null,
          "details": [
            "Background gradient now follows `--mg-color-hero` instead of a hardcoded blue, so themed brands render correctly. (unisdr/undrr-mangrove#1115)",
            "Scoped link color rules to hero text regions (`__overlay`/`__content`) so consumer HTML media content is not unintentionally restyled."
          ]
        },
        {
          "version": "1.6.2",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic typography now follows font roles (`display` for title, `ui` for label) with non-Arabic rendering unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.6.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a hero title or label whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.6",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Refreshed directional gradient for better contrast and theme control in LTR/RTL.",
            "Improved CTA spacing/responsiveness while preserving variant behavior.",
            "Added contained/immersive options, logo support, and structured CTA data."
          ]
        },
        {
          "version": "1.5",
          "date": "2026-04-15",
          "notes": "(unisdr/undrr-mangrove#942): Added `layout=\"split\"` with `headingLevel` and `split` controls, plus image/video/html media slot support.",
          "pr": 942,
          "prRef": "unisdr/undrr-mangrove#942",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-04-13",
          "notes": "`.mg-hero__title` and `.mg-hero__label` now use `$mg-font-family-headings` (Roboto Condensed). Added a `:lang(ar)` block routing both to `$mg-font-family-arabic-headings`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-03-14",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Hero title color is now a theme token (`$mg-color-hero-title`, default white). Overlay width increased from `$mg-width-400` (40rem) to `$mg-width-600` (60rem) and padding from `$mg-spacing-200` to `$mg-spacing-400` for better content readability across all themes.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2024-09-20",
          "notes": "Conditional rendering of headline (anchor tag when link is present, header tag otherwise); added HTML support in title and summaryText fields",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2024-09-20",
          "notes": "Locale-based content switching with `getCaptionForLocale`; conditional headline rendering; HTML support in text fields",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-01-24",
          "notes": "Initial release with customizable background images, text, and CTA buttons",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-hero-hero-child": {
      "name": "ChildHero",
      "entries": [
        {
          "version": "1.2",
          "date": "2026-04-15",
          "notes": "(unisdr/undrr-mangrove#942): Marked as deprecated. Planned for removal by end of 2026. Migrate to the main Hero component (`headingLevel=\"h2\"`/`\"h3\"` or `layout=\"split\"`).",
          "pr": 942,
          "prRef": "unisdr/undrr-mangrove#942",
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-04-13",
          "notes": "Inherits the `.mg-hero__title` heading-font and Arabic-override change from Hero (same SCSS file).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-highlightbox": {
      "name": "HighlightBox",
      "entries": [
        {
          "version": "1.2.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1193): The \"With image\" story loaded `via.placeholder.com`, a service that no longer resolves, so the story and the docs page showed a broken image. It now uses `picsum.photos`, as every other story in the library does.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-05-07",
          "notes": "Force descendants of `primary` / `secondary` variants to inherit the white text color so body text (`<p>`, headings, etc.) stays legible when host CSS (including Storybook's docs theme) applies its own bare-element color rules. (unisdr/undrr-mangrove#926)",
          "pr": 926,
          "prRef": "unisdr/undrr-mangrove#926",
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-10",
          "notes": "Removed `.mg-highlight-box:has(iframe)` rule that broke layout on Safari/WebKit mobile. Video embeds now use the [`.mg-embed-container`](?path=/docs/components-embedcontainer--docs) utility wrapper instead of applying aspect ratio to the whole container. [`web-backlog#2672`](https://gitlab.com/undrr/web-backlog/-/work_items/2672)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-02-04",
          "notes": "Initial release with tone variants (default, primary, secondary) and layout variants (full, centered, float-start, float-end)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-icons": {
      "name": "Icon",
      "entries": [
        {
          "version": "2.3",
          "date": "2026-09-25",
          "notes": "(unisdr/undrr-mangrove#1296): Added `plus`, `minus`, `pause`, `play`, `circle-minus`, `expand`, `shrink` and `layers` Lucide icons for map and media controls.",
          "pr": 1296,
          "prRef": "unisdr/undrr-mangrove#1296",
          "prUrl": null
        },
        {
          "version": "2.2.2",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1193): The \"Colored icons\" demo carries the same default-pairing contrast note as the colour utility demos. No icon colours changed.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "2.2.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1192): The legacy `.fa-before` gap uses `padding-inline-end`, so it falls after the icon in RTL.",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null
        },
        {
          "version": "2.2",
          "date": "2026-09-14",
          "notes": "(unisdr/undrr-mangrove#1145): Added a machine-readable icon inventory to the AI manifest and expanded the gallery with high-value utility and OCHA icons, so humans and AI agents can see what’s available at a glance. The gallery table now also shows an availability date/status column for new entries.",
          "pr": 1145,
          "prRef": "unisdr/undrr-mangrove#1145",
          "prUrl": null
        },
        {
          "version": "2.1",
          "date": "2026-04-04",
          "notes": "(undrr-mangrove#906 (unisdr/undrr-mangrove#906)): Icons now render via CSS mask-image backed by Lucide, OCHA, and custom SVG sources. Added `--mg-icon-svg` custom property pattern, `mg-icon--multicolor` modifier, source attribution badges, migration table, and expanded icon set (79 icons including aliases). No breaking change: icon font retained as fallback for `fa-*` selectors.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.0",
          "date": "2026-02-03",
          "notes": "**Breaking:** Icons now use `mg-icon-*` namespaced classes; added Icon React component with automatic class normalization; size modifiers (`.mg-icon--sm`, `.mg-icon--lg`, `.mg-icon--xl`); `fa-*` classes maintained for backwards compatibility",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2025-08-06",
          "notes": "Added `.fa-qrcode` icon",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2025-05-14",
          "notes": "Added `.fa-menu` icon",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2024-10-15",
          "notes": "Added `.fa-left`, `.fa-right`, `.fa-angle-circled-left`, `.fa-angle-circled-right` icons",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Initial icon font from UNDP fork",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-images-author-image": {
      "name": "AuthorImage",
      "entries": [
        {
          "version": "1.3.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. The `secondary` accent keeps the orange avatar ring and hover tint, which carry no copy, but its title now takes `--mg-color-text` (was the orange accent as ink on white, 2.65) and its focus outline takes `--mg-color-focus-ring` (the orange outline was 2.95, under the 3.0 a focus indicator needs). `--mg-color-secondary` is unchanged. This is a visible change wherever the variant is used, in all five themes.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.2.2",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1198): Documented the `secondary` accent as a recorded contrast exception: the orange accent as title ink on white is 2.65, and no text colour clears AA on that hue. No colour changed; removal is tracked in unisdr/undrr-mangrove#1196.",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1192): The horizontal layout aligns its text with `text-align: start`, so it follows the reading direction in RTL.",
          "pr": 1192,
          "prRef": "unisdr/undrr-mangrove#1192",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-09-14",
          "notes": "(unisdr/undrr-mangrove#1147): Reworked the component into a compact author block with horizontal and vertical stacked layouts, optional linking, inverse dark-surface treatment, avatar-only hover tint, and a no-image text-only variant.",
          "pr": 1147,
          "prRef": "unisdr/undrr-mangrove#1147",
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-21",
          "notes": "Renamed CSS classes to `mg-` prefix (`author__img` → `mg-author-image`). Added BEM modifiers for size (`mg-author-image--large`) and hover color (`mg-author-image--yellow`, etc.). Replaced `defaultProps` with destructured defaults.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-images-image-with-credit-caption": {
      "name": "Images",
      "entries": [
        {
          "version": "1.2",
          "date": "2026-03-21",
          "notes": "Removed viewport scroll animations (`data-viewport`, `mg-scale-up`, `mg-opacity-only`). Components now render immediately.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-21",
          "notes": "Renamed CSS classes to `mg-` prefix (`image-figcaption` → `mg-image-figcaption`, `image__caption` → `mg-image-caption`, `credits` → `mg-credits`). Added BEM modifiers for size variants (`mg-image-figcaption--medium`, `mg-image-figcaption--portrait`). Replaced `defaultProps` with destructured defaults.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-loader": {
      "name": "Loader",
      "entries": [
        {
          "version": "1.1",
          "date": "2026-03-21",
          "notes": "Renamed CSS class to `mg-` prefix (`loader` → `mg-loader`).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-logos": {
      "name": "Logo",
      "entries": [
        {
          "version": "1.1",
          "date": "2026-09-14",
          "notes": "Added translated logo assets (ar, es, fr, ru, zh), the `crop=\"autocrop\"` variant (`mg-logo--autocrop`), and the `undrr-logo-assets.js` resolver used by the logo stories and page header. Added `title`, `width`, `height`, `lang`, `crop`, and `className` props.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2024-09-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-navigation-breadcrumbs": {
      "name": "Breadcrumbcomponent",
      "entries": [
        {
          "version": "1.5.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200):",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null,
          "details": [
            "The list inside `nav.mg-breadcrumb` is now styled whether it is a `<ul>` or an `<ol>`. The selector only matched `> ul`, but every curated breadcrumb example in the AI manifest uses `<ol>`, so a consumer copying the documented snippet got default numbering and no flex row. Both arms now share one `> :is(ul, ol)` rule.",
            "Markers are hidden with `list-style: none` followed by `list-style-type: \"\"` (the new `mg-list-unmarked` mixin) instead of `list-style: none` alone. Safari drops the implicit `list` role from a markerless list; an empty string is still a marker, so the role survives and the count is announced again. You do not need `role=\"list\"` on the list: the guidance that asked for it disagreed with the component's own rendered HTML."
          ]
        },
        {
          "version": "1.5",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1105): Quieter link typography distinguishes the current page; logical separators, wrapping and keyboard focus work for both React and legacy list markup, including RTL and inverse variants.",
          "pr": 1105,
          "prRef": "unisdr/undrr-mangrove#1105",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a breadcrumb whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Removed redundant per-language story exports; use the global Storybook locale toolbar for translated content and RTL direction.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-05-15",
          "notes": "Gated hover transition inside `@media (prefers-reduced-motion: no-preference)`; added jest-axe accessibility test suite (unisdr/undrr-mangrove#908)",
          "pr": 908,
          "prRef": "unisdr/undrr-mangrove#908",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-04-09",
          "notes": "Fixed invalid `aria-current` value (now correctly set to `\"page\"` on the active item); removed redundant `aria-label` from non-active breadcrumb links",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-21",
          "notes": "Removed stale `data-viewport` animation reference from documentation.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-navigation-drawer": {
      "name": "Drawer",
      "entries": [
        {
          "version": "1.1.1",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The React example imports `@undrr/undrr-mangrove/components/Drawer.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. Documentation only; the component is unchanged.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1197): Added the vanilla lifecycle module `js/drawer.js`, so a page with no React can open and close a drawer or floating panel. It enhances markup you wrote rather than rendering any, which keeps the close label, title and body (and their translations) on your page; the backdrop is the only element it creates. `mgDrawer(scope)` and `mgDrawerDestroy(scope)` are the lifecycle, with `mgDrawerOpen`, `mgDrawerClose`, `mgDrawerToggle` and `mgDrawerInstance` for one drawer, and the same `mg-drawer:open` / `mg-drawer:close` / `mg-drawer:toggle` command events the hydrated drawer answers, plus `mg-drawer:opened` and `mg-drawer:closed` notifications.",
          "pr": 1197,
          "prRef": "unisdr/undrr-mangrove#1197",
          "prUrl": null,
          "details": [
            "The module marks each enhanced container with `data-mg-js-drawer-initialized` and skips it on a second pass, so a re-scan after inserting markup cannot bind two sets of listeners (unisdr/undrr-mangrove#1234).",
            "It activates on `data-mg-js-drawer`, deliberately not the `data-mg-drawer` hydration marker, so a container cannot be driven by both paths at once. A container carrying both is refused with a console warning and left to hydration.",
            "There is no `js/floating-panel.js`: a floating panel is the same component with a different base class, so `js/drawer.js` drives both.",
            "Accessibility matches the React component: `role=\"dialog\"` and `tabindex=\"-1\"` added when the markup omits them, the dialog named from its own title when nothing else names it (an unnamed dialog announces as just \"dialog\", WCAG 4.1.2, and caught by the module's own jest-axe test; the name is handed back on destroy along with any minted id), a closed drawer left `inert` and `aria-hidden`, focus to the close button on open, Escape to dismiss, and a Tab trap in a modal drawer only. A floating panel stays non-modal and untrapped.",
            "The focus trap counts everything the browser will stop on (`<iframe>`, `contenteditable`, `<summary>`) and discards everything it will skip: unrendered elements, controls in a closed `<details>`, and the radios in a group that are not the group's tab stop. Counting an unrendered element strands Tab either outside the drawer or on an element that cannot take focus; both were reproduced in Chrome. It listens on the document, so focus that has escaped is pulled back on the next Tab, and it wraps Shift+Tab from the dialog container itself.",
            "Focus on close falls back from the opener to a trigger for the drawer and then to the document body, so an opener that was removed or hidden no longer leaves focus on a control inside a drawer that has just been made `inert`.",
            "Escape closes only the innermost open drawer, ignores an Escape that something nearer the user has already answered (a nested `<dialog>`, a date picker), and on a non-modal floating panel answers only from inside the panel or its trigger; the page behind it is still in use.",
            "New \"Vanilla lifecycle (no React)\" story, and the AI manifest now carries a `vanillaModule` contract for Drawer alongside the existing `hydration` one."
          ]
        },
        {
          "version": "1.0.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1194): Documentation only. The docs page now embeds its stories, so the default drawer, the end edge, the bottom sheet and the floating panel each have a live example; `Bottom` and `FloatingPanel` are new story exports. Placeholder body copy is replaced with a publications filter panel and a country report panel; the report panel uses sample data and names no Member State. The AI manifest gained the structured `hydration` contract for Drawer, so agents reading `renderedHtml` are told it cannot open on its own.",
          "pr": 1194,
          "prRef": "unisdr/undrr-mangrove#1194",
          "prUrl": null
        },
        {
          "version": "1.0.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1158): Initial implementation of the off-canvas drawer and floating panel.",
          "pr": 1158,
          "prRef": "unisdr/undrr-mangrove#1158",
          "prUrl": null,
          "details": [
            "Hydration via `Drawer.fromElement.js` and `Drawer.hydrate.js` (unisdr/undrr-mangrove#1163).",
            "Before rc.1: string content renders as text with sanitised `bodyHtml` / `footerHtml` for hydration; closed drawers are inert and hidden; dialog naming, focus management and a modal focus trap; translatable close label; `data-mg-drawer-trigger` and open/close events for hydrated drawers; theme tokens and RTL-aware positioning."
          ]
        }
      ]
    },
    "components-navigation-megamenu": {
      "name": "MegaMenu",
      "entries": [
        {
          "version": "1.4.5",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1247, unisdr/undrr-mangrove#1235): The three section and top-bar ref callbacks are statement bodies instead of parenthesised assignments, so they no longer return the element they assign. React 19.3 does not reject that return value (it stores it as the ref cleanup, finds it is not a function and still calls `ref(null)` on detach), so these were correct as written; the hazard React 19 introduced is a ref callback returning a _function_, which silently replaces the detach call. Found by widening ESLint coverage to component `.jsx`, which had never been linted. No visual or behavioural change.",
          "pr": 1247,
          "prRef": "unisdr/undrr-mangrove#1247",
          "prUrl": null
        },
        {
          "version": "1.4.4",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1258): Documented the banner HTML trust contract. `bannerDescription` is rendered as HTML at three places (the desktop banner, the call-to-action panel a section with no child items renders, and the mobile sidebar), and none of them sanitise it. That was already true and nothing about the rendering changed here; what was missing was any statement of it. The value arrives through the `sections` prop or the `data-sections` attribute, so the consumer that builds the sections array owns sanitising it, and several sibling components (`TextCta`, `Drawer`, the cards) do run DOMPurify internally, which made the silence easy to misread as an oversight. `Section.jsx` now carries the reasoning with a pointer at each render site, `Sidebar.jsx`'s existing one-line reference points at something that exists, the props table and the `sections` propType say so, the MDX has a \"Banner HTML trust contract\" section beside the section object properties, and the AI manifest entry carries it for agents wiring the menu up from `index.json`. Also notes that the `.includes('<')` check at the desktop banner picks between a `<p>` wrapper and raw markup and is not a filter.",
          "pr": 1258,
          "prRef": "unisdr/undrr-mangrove#1258",
          "prUrl": null
        },
        {
          "version": "1.4.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200): The top-bar nav list and the mobile sidebar list hide their markers with `list-style: none` followed by `list-style-type: \"\"` (the new `mg-list-unmarked` mixin), so Safari keeps the implicit `list` role and announces the item count. The treatment is applied to the list items as well as the lists: `.mg-mega-topbar__item`, `.mg-mega-topbar__mobile-trigger` and `.mg-mega-sidebar-section` each hid their own marker, and it is the item's marker Safari inspects, so a bare `list-style: none` there would have undone the fix on the list. Both top-bar shapes are covered: the default variant, where `.mg-mega-topbar` is the `<ul>`, and the branded variant, where it wraps `.mg-mega-topbar__nav`. The content panels are untouched: they carry `role=\"menu\"` on purpose, which replaces list semantics. No markup or layout changes.",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null
        },
        {
          "version": "1.4.2",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1173): Fixes unisdr/undrr-mangrove#1171. Removed an unscoped `.mg-icon-button` rule from the MegaMenu stylesheet. The menu does not use that class (its controls use `.mg-mega-topbar-mobile__icon-button`), but the rule leaked into every icon button on the page and broke the primitive's layout and hover contrast. The menu itself renders the same.",
          "pr": 1173,
          "prRef": "unisdr/undrr-mangrove#1173",
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-15",
          "notes": "(unisdr/undrr-mangrove#1153): The desktop tab active-state underline now transitions in and out (grows from the centre, fades) instead of flipping instantly, so switching between neighbouring tabs no longer looks like an abrupt off/on flicker. Respects `prefers-reduced-motion`.",
          "pr": 1153,
          "prRef": "unisdr/undrr-mangrove#1153",
          "prUrl": null
        },
        {
          "version": "1.4.0",
          "date": "2026-09-10",
          "notes": "(unisdr/undrr-mangrove#1114): Fixed desktop a11y semantics (landmarks, nav roles, and `aria-expanded`) and added a desktop-state axe assertion. Also fixed mobile overlay clipping in constrained containers by anchoring it to the top bar instead of `100vw`.",
          "pr": 1114,
          "prRef": "unisdr/undrr-mangrove#1114",
          "prUrl": null
        },
        {
          "version": "1.3.0",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1099): Added progressive mobile navigation (nested levels, back/close controls, translated labels, focus/scroll restoration, and reduced-motion handling) while keeping existing data/integration contracts compatible.",
          "pr": 1099,
          "prRef": "unisdr/undrr-mangrove#1099",
          "prUrl": null
        },
        {
          "version": "1.2.6",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Fixed Arabic top-bar link typography by switching to the `ui` font role (`Noto Sans Arabic`) instead of a broken `:lang(ar)` class override. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.2.5",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a menu link whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.2.4",
          "date": "2026-08-31",
          "notes": "(unisdr/undrr-mangrove#1086): Refreshed desktop navigation with an underline active state, an opaque high-clarity menu surface, lighter dividers, and restrained panel elevation. Removed redundant per-language story exports in favour of the global locale toolbar.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.2.3",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Consolidated `navLabel` and `closeMobileNavLabel` props into a single `labels` object with `DEFAULT_MEGAMENU_LABELS` export. Backwards compatible: `DEFAULT_MEGAMENU_LABELS` covers all keys. Co-located `_labels.js` provides `LABELS_ES/FR/JA/ZH/AR/RU`; locale toolbar now switches translated strings in all stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.2",
          "date": "2026-04-23",
          "notes": "Declared `title` (required) and `icon` (optional) on the section-object `PropTypes` shape. Both props were already used by `TopBar` and `Sidebar` and documented in the section object properties table, but were absent from runtime `PropTypes` validation, so consumers got no warning when passing sections without a `title`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-04-14",
          "notes": "(unisdr/undrr-mangrove#939 (unisdr/undrr-mangrove#939)): Scoped submenu CSS multicolumn layout to tablet+ viewports. Primarily a Safari mobile fix: third-level menu items rendered invisibly (space reserved, content unpainted) due to a long-standing interaction between multicolumn containers and descendant `opacity < 1`. Other browsers were unaffected.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-04-13",
          "notes": "`.mg-mega-topbar__item-link` migrated from raw `$mg-font-family-condensed` to the semantic `$mg-font-family-headings` alias. Same rendered font; future-proof if a theme overrides the heading token independently of the condensed token.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-04-06",
          "notes": "(unisdr/undrr-mangrove#916): Fixed mobile links non-clickable when using Mangrove nav CSS without the React component, or when JS hydration fails.",
          "pr": 916,
          "prRef": "unisdr/undrr-mangrove#916",
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-03-13",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Optional `icon` property on sections for top-level menu item icons (desktop topbar only; decorative, `aria-hidden`). Added section object properties documentation.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0",
          "date": "2026-03-12",
          "notes": "Branded variant with optional inline logo (`logoSrc`, `logoAlt`, `logoHref`, `logoWidth`, `logoHeight`); logo shrinks on narrow viewports; hamburger floats over logo with shadow on mobile; documented `logoHref` language prefix handling. Added hydration support with `fromElement` prop extraction and `createHydrator` integration.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.7.4",
          "date": "2026-03-10",
          "notes": "Fixed slide-in animation for menus with more than 12 child items (unisdr/undrr-mangrove#825)",
          "pr": 825,
          "prRef": "unisdr/undrr-mangrove#825",
          "prUrl": null
        },
        {
          "version": "0.7.3",
          "date": "2026-02-17",
          "notes": "Topbar expands to extra-large breakpoint (1440px) on wider screens",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.7.2",
          "date": "2026-02-09",
          "notes": "Migrated icon classes from `fa-*` to `mg-icon-*` namespace",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.7.1",
          "date": "2025-12-09",
          "notes": "Refined UX of opening 3rd level menu on desktop (subtle colours and animation)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.7",
          "date": "2025-10-27",
          "notes": "**Breaking:** Converted to mobile-first, CSS-driven responsive architecture; eliminated JS resize handlers; RTL support via CSS logical properties; added `hoverDelay` prop; click-outside-to-close; mobile content hinting",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.6",
          "date": "2025-09-22",
          "notes": "UX improvements for submenu indicators; aligned with UNDRR branding direction",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.5",
          "date": "2025-07-02",
          "notes": "Mobile sidebar slides from top; backdrop overlay; always-visible 3rd-level links on mobile; `mg-` prefixed selectors",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.4",
          "date": "2025-07-02",
          "notes": "Auto-balanced column heights; grouped sub-items; updated font weights; CSS column layout",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.3",
          "date": "2025-05-14",
          "notes": "Enhanced mobile menu accessibility and UX; `fa-menu` icon support; z-index fixes",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.2",
          "date": "2025-02-06",
          "notes": "Support for more diverse menu data structures; theme-level integration",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "0.1",
          "date": "2024-01-24",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-navigation-on-this-page-nav": {
      "name": "OnThisPageNav",
      "entries": [
        {
          "version": "1.5.4",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1267): The React usage snippet imported `@undrr/undrr-mangrove/js/on-this-page-nav` without the extension. Node ESM does not add one, and the package ships no `exports` map that could, so the specifier failed with `ERR_MODULE_NOT_FOUND` against a packed tarball; under a bundler it worked, which is the wrong way round for a vanilla module. It now names `js/on-this-page-nav.js`. Documentation only; the module is unchanged.",
          "pr": 1267,
          "prRef": "unisdr/undrr-mangrove#1267",
          "prUrl": null
        },
        {
          "version": "1.5.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200): `.mg-on-this-page-nav__list` hides its markers with `list-style: none` followed by `list-style-type: \"\"` (the new `mg-list-unmarked` mixin), so the implicit `list` role survives Safari without help from the markup. This closes a split between the two implementations: the vanilla script set `role=\"list\"` on the list, the React component did not, and only one of them announced a count. The script still sets the role (belt and braces now, not a requirement), so nothing about the hydration contract changes.",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null
        },
        {
          "version": "1.5.2",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic navigation links and the CTA now render in Noto Sans Arabic instead of Noto Kufi Arabic. Both classes read the `ui` font role rather than naming a typeface, and the component's `:lang(ar)` block is gone. Arabic labels measure about 10.8% narrower than their Latin equivalents, so the horizontal scroller needed no adjustment. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.5.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a nav link or CTA whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.5",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1089): Arabic navigation links render in Noto Kufi Arabic. The `:lang(ar)` override points at `$mg-font-family-arabic-headings`, which changed from Dubai to Noto Kufi Arabic.",
          "pr": 1089,
          "prRef": "unisdr/undrr-mangrove#1089",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-08-31",
          "notes": "(unisdr/undrr-mangrove#1086): Replaced the heavy drop shadow with a crisp divider and aligned link, CTA, and scroll-control transitions and focus states with the shared modernisation tokens. Removed redundant per-language story exports in favour of the global locale toolbar.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Consolidated `label` prop into a `labels` object with `DEFAULT_ON_THIS_PAGE_LABELS` export. Pass `labels={{ label: '...' }}` instead of `label` directly. Backwards compatible: `DEFAULT_ON_THIS_PAGE_LABELS` covers all keys. Co-located `_labels.js` provides `LABELS_ES/FR/JA/ZH/AR/RU`; locale toolbar now switches translated strings in all stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-04-13",
          "notes": "`.mg-on-this-page-nav__link` and `.mg-on-this-page-nav__cta` now use `$mg-font-family-headings` (Roboto Condensed via the semantic alias rather than the raw `-condensed` token). Both font-sizes bumped from `$mg-font-size-250` (14px) to `$mg-font-size-300` (16px) for legibility.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-04-07",
          "notes": "([web-backlog#337](https://gitlab.com/undrr/web-backlog/-/issues/337)): Tabs integration: nav links to headings inside hidden tab panels now automatically open the correct tab before scrolling",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-28",
          "notes": "Added `data-mg-on-this-page-nav-skip-auto-init` attribute, single-element scope support; scroll arrow buttons for horizontal overflow",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-03-26",
          "notes": "Initial release (auto-detect, explicit, CTA, scroll-spy, RTL, a11y) ([web-backlog#337](https://gitlab.com/undrr/web-backlog/-/issues/337))",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-navigation-pager": {
      "name": "Pager",
      "entries": [
        {
          "version": "1.1.2",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The React example imports `@undrr/undrr-mangrove/components/Pager.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. Documentation only; the component is unchanged.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200): `.mg-pager__list` hides its markers with `list-style: none` followed by `list-style-type: \"\"` (the new `mg-list-unmarked` mixin). Safari drops the implicit `list` role from a markerless list, so the page count stopped being announced; an empty string is still a marker, so the role survives and nothing renders. This reaches the hand-written `<ul class=\"mg-pager__list\">` in the docs and the AI manifest too, which is why it is a CSS fix rather than `role=\"list\"` in the examples.",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Refreshed the default pager with borderless 44px circular targets, softly tinted current and directional controls, stable token-driven hover/focus/press states, and visually compact arrow-only previous/next controls that retain their accessible labels.",
            "Completed the bar layout refresh with soft-filled jump controls, shared button and field radii, 44px targets, token-driven focus rings and reduced-motion-aware press feedback."
          ]
        },
        {
          "version": "1.0",
          "date": "2026-02-13",
          "notes": "Initial release; standalone pager extracted from SyndicationSearchWidget with bar layout, range display, jump-to-page, mini-pager mode, and empty state",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-navigation-skip-link": {
      "name": "SkipLink",
      "entries": [
        {
          "version": "1.0.1",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The React example imports `@undrr/undrr-mangrove/components/SkipLink.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. Documentation only; the component is unchanged.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-10",
          "notes": "(unisdr/undrr-mangrove#1117): New component. Visually hidden anchor that reveals on focus in normal flow, moves focus to its target, takes its label from props and works in right-to-left. `PageHeader` gained optional `skipLinkTarget` and `skipLinkLabel` props. The content hub and landing page patterns moved off their local `mg-demo-skip-link` class, which is gone.",
          "pr": 1117,
          "prRef": "unisdr/undrr-mangrove#1117",
          "prUrl": null
        }
      ]
    },
    "components-navigation-table-of-contents": {
      "name": "TableOfContents",
      "entries": [
        {
          "version": "1.9",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1193): Breaking: deleted the deprecated `TableOfContents.scss` alias, which only forwarded to `table-of-contents.scss` and emitted a deprecation `@warn`. The npm package no longer ships `scss/Components/TableOfContents/TableOfContents.scss`; import `scss/Components/TableOfContents/table-of-contents` instead. No CSS behaviour change.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.8",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1105): Added divider/rhythm/focus refinements and aligned React anchor scrolling with reduced-motion behavior.",
          "pr": 1105,
          "prRef": "unisdr/undrr-mangrove#1105",
          "prUrl": null
        },
        {
          "version": "1.7",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Removed redundant per-language story exports; use the global Storybook locale toolbar for translated content and RTL direction.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.6",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Locale map extended to all 6 languages (ES, FR, JA, ZH, AR, RU); locale toolbar now switches translated strings in stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.5",
          "date": "2026-04-02",
          "notes": "([web-backlog#2739](https://gitlab.com/undrr/web-backlog/-/issues/2739)): Published vanilla JS as standalone asset in `js/table-of-contents.js` with auto-init support and `data-mg-table-of-contents-content` selector",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2025-06-17",
          "notes": "Fixed anchor links to work in Storybook/React environments (smooth scrolling, prevent full page navigation)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2024-11-15",
          "notes": "Support passing the title name for React usage",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2024-11-15",
          "notes": "Added optional title via `data-mg-table-of-contents-title`; removed excessive CSS; excluded `.mg-u-sr-only` headers",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2024-10-25",
          "notes": "Added `showNumbers` prop to support numbered lists",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2024-10-17",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-navigation-tree": {
      "name": "Tree",
      "entries": [
        {
          "version": "1.1.3",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The usage example imports `@undrr/undrr-mangrove/components/Tree.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. Both `Tree` and `TreeItem` come from that one bundle. Documentation only; the component is unchanged.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.1.2",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1197): Documentation only. New \"When a treeview is the right pattern\" section, which says what the roving tabindex costs: a `treeitem` is a composite widget item, so a switch, menu or separate expand button inside one is nested interactive content, and a nested hierarchy of rows with per-row controls is not a treeview however much it looks like one. It names grouped `<details>`/`<summary>` as the fallback for that case, with the trade it makes, and says that moving the control out of the item's ancestry is the fix for a `nested-interactive` finding rather than more ARIA. Written from a consuming team's report of clearing eight violations of that exact shape.",
          "pr": 1197,
          "prRef": "unisdr/undrr-mangrove#1197",
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1194): Documentation only. The docs page now embeds its stories, so the default, linked, custom-toggle-icon and hydrated trees each have a live example. The placeholder \"Item 1 / Item 2\" labels are replaced with a knowledge base section tree, and the three React stories now give the tree an `aria-label`, which `HydratedFromHtml` already had through `data-aria-label`.",
          "pr": 1194,
          "prRef": "unisdr/undrr-mangrove#1194",
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1176): Hydration from a nested list with `Tree.fromElement.js`, `Tree.hydrate.js` and `HydratedTree`, and a `toggleIcon` prop on `Tree` and `TreeItem` (`data-toggle-icon` when hydrated).",
          "pr": 1176,
          "prRef": "unisdr/undrr-mangrove#1176",
          "prUrl": null,
          "details": [
            "The default export of `components/Tree.js` is now `HydratedTree` (unisdr/undrr-mangrove#1172), which takes the same props and children as `Tree`; import `{ Tree }` if you need the component itself.",
            "`TreeItem` takes a `current` prop that sets `aria-current` on its link. Hydration keeps `aria-current` from the source link and expands the selected item's parents so it is visible on load.",
            "The AI manifest describes the hydration contract in the structured `hydration` field, and Tree is flagged `hydration: true` in the component index.",
            "The indentation guide now runs directly under the parent's toggle arrow instead of about 8px before it. Child items keep their position.",
            "Hydration reads links wrapped in one element (`<li><span><a>`), labels image-only links from `aria-label` or the image `alt`, and also drops `vbscript:` URLs.",
            "Fixed the chevron in RTL: it pointed the wrong way when collapsed and up when expanded. It now points to the inline end when collapsed and down when expanded, for any `toggleIcon`.",
            "Fixed Enter and Space on an expanded parent without a link: they followed the first child's link. They now select the parent and expand or collapse it.",
            "Tabbing into the tree focuses the selected item, or its nearest visible parent, instead of always the first item.",
            "Item DOM ids are scoped to each tree, so two trees with the same item ids no longer repeat DOM ids. Ids are no longer `mg-treeitem-{id}`; use `data-mg-treeitem-id` to find an item.",
            "The selected label uses the text colour on the interactive tint, which fixes a 4.02:1 contrast failure in the IRP theme. New `--mg-tree-item-background--hover`, `--mg-tree-item-background--selected` and `--mg-tree-item-color--selected` component tokens.",
            "Link items are underlined, with a thicker underline on hover, and leaf labels line up with sibling parents."
          ]
        },
        {
          "version": "1.0.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1158): Initial implementation with expand/collapse and optional link support.",
          "pr": 1158,
          "prRef": "unisdr/undrr-mangrove#1158",
          "prUrl": null,
          "details": [
            "Full ARIA APG keyboard navigation (arrow keys, Home, End, Enter, Space), roving tabindex, controlled `expandedIds` / `selectedId`, colour tokens and `mg-icon` chevron (unisdr/undrr-mangrove#1163).",
            "Before rc.1: arrow keys and chevron mirror in RTL, logical properties, shared inset focus ring, and the chevron button is presentational so `aria-expanded` is not repeated and no English label is hardcoded."
          ]
        }
      ]
    },
    "components-normalize": {
      "name": "Normalize",
      "entries": [
        {
          "version": "1.0",
          "date": "2023-02-02",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-notice-cookieconsentbanner": {
      "name": "CookieConsentBanner",
      "entries": [
        {
          "version": "1.4.2",
          "date": "2026-09-23",
          "notes": "Banner and preferences copy no longer uses the first person, following the [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs). The banner description now reads \"Cookies help ensure you get the best experience on this website.\" In the fallback config, \"our website(s)\" becomes \"this website\" or \"UNDRR websites\", \"my policy on cookies\" becomes \"the UNDRR cookie policy\", the contact link reads \"contact UNDRR\", and \"behavior\" is spelled \"behaviour\".",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1244): The \"Rendering check\" status line now reports what the play function asserts. Documentation and stories only; the component is unchanged.",
          "pr": 1244,
          "prRef": "unisdr/undrr-mangrove#1244",
          "prUrl": null,
          "details": [
            "The status line read \"Bar rendered\" off a single measurement (the bar's height), so it could report a working banner while the assertions it appeared to summarise had already failed. It now reports every runtime condition the play function checks but one, and names the ones that failed rather than saying only that nothing rendered.",
            "Most pointedly, it checks visibility and not just the box. The play function's own comment records why: the library builds the bar hidden and reveals it a frame later, and a `visibility: hidden` element still reports a box. Measuring the box alone was therefore exactly the case that could read \"Bar rendered\" for a bar the play function fails on.",
            "The one condition it still does not cover is `CookieConsent.show(true)` not throwing. That is a call rather than a reading, and making it four times a second would rebuild the DOM every other check measures.",
            "This story's assertions now run in CI. The Storybook test runner executes them against the live CDN on every push to a pull request, so a banner that stops rendering fails the build instead of being noticed by whoever next opened the story."
          ]
        },
        {
          "version": "1.4.0",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1245, unisdr/undrr-mangrove#1246): Unmount cleanup stops reaching outside the banner, and the load guard stops defeating itself.",
          "pr": 1245,
          "prRef": "unisdr/undrr-mangrove#1245",
          "prUrl": null,
          "details": [
            "Unmount no longer sweeps `[id^=\"cc-\"]` across the whole document. That selector removed **any** element on the page whose id begins with `cc-`, including consumer markup the component never created. It now removes `#cc-main`, the single element vanilla-cookieconsent v3 creates, and only as a fallback for the case where the library never ran.",
            "Removed the `destroy?.()` branch from cleanup. There is no `destroy` anywhere in the v3 bundle (the export surface is `acceptCategory, acceptService, acceptedCategory, acceptedService, eraseCookies, getConfig, getCookie, getUserPreferences, hide, hidePreferences, loadScript, reset, run, setCookieData, setLanguage, show, showPreferences, validConsent, validCookie`), so the branch never ran and `reset()` was always the real teardown. It is now the only path.",
            "**Unmount no longer erases the visitor's consent cookie.** This is a behaviour change. Cleanup called `reset(true)`, and in the 3.1.0 bundle that argument gates exactly one thing: erasing the stored `cc_cookie` (`e && (l ? De(c) : Pe(c, r, i))`). Detaching the listeners, removing the `#cc-main` root, clearing the modal classes, resetting internal state and clearing `window._ccRun` are all unconditional. So `reset(false)` is the complete teardown and the `true` bought nothing but the cookie wipe, which meant a route change that unmounted the banner destroyed the visitor's recorded consent and prompted them again. Cleanup now passes `false`. If you were relying on unmount to clear consent, call `eraseCookies()` yourself.",
            "CDN URLs no longer carry `?cacheBuster=YYYYMMDDHHMM`. The \"already loaded\" checks in `loadScript` and `loadCSS` compared full URLs, so the buster made every check miss across a minute boundary. `cookieconsent-undrr.js` declares `const currentScript` and `const configUrl` at classic-script top level and throws a redeclaration error when it runs twice, so a mount and a remount a minute apart broke the page. Both checks now compare origin and path only, which also closes the same hole for any caller-supplied query string.",
            "A re-render that passes an equal `config` object no longer tears the banner down and reloads it. The effect compared `config` by identity, and a config built inline in the caller's render is a new object every time. It is now compared by value: the key is built by a serialiser that sorts keys, so `{a, b}` and `{b, a}` are the same config (which matters whenever a caller assembles one by spreading a varying override set) and records function-valued properties as a marker, so _whether_ a callback is present registers while its identity does not. Configs that cannot be serialised (a circular reference) still fall back to identity comparison.",
            "**Changing a callback now takes effect immediately, with no reload.** The library captures its callbacks when `run()` is handed a configuration and holds them for the lifetime of that run, so it is never given the consumer's function directly; it gets a trampoline per callback (`onFirstConsent`, `onConsent`, `onChange`, `onModalShow`, `onModalHide`, `onModalReady`) that looks the current handler up at call time. A consumer who gates an analytics loader on `onConsent` and ships a new handler gets the new handler, where a config made only of callbacks previously kept the old one live silently and indefinitely.",
            "The effect cancels its own in-flight work. Loading three CDN assets and waiting 300ms outlives a route change or a re-render with new props, so the chain from a discarded mount could still reach `run()` after cleanup had torn the banner down. Every `await` in both the CDN and fallback paths is now followed by a cancellation check, and the settle timer is cleared on cleanup.",
            "The de-duplication key collapses repeated slashes, so a `cdnBaseUrl` passed with a trailing slash produces a key that matches the same file loaded without one. The requested URLs drop the trailing slash too.",
            "The last-resort fallback guards `CookieConsent.run` before calling it. It previously did not, so when the library had failed to load (the most likely way to reach that handler), it threw a `TypeError` out of an un-awaited promise instead of reporting anything."
          ]
        },
        {
          "version": "1.3.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1242): The stories can now tell a working bar from a broken one.",
          "pr": 1242,
          "prRef": "unisdr/undrr-mangrove#1242",
          "prUrl": null,
          "details": [
            "Added a \"Rendering check\" story whose `play` function asserts that the CDN library and configuration script ran, that the CDN stylesheet actually applied, that the library built its DOM rather than merely defining `window.CookieConsent`, and that the bar is present and visible in the document body where the library appends it. `#cc-main` lands on `document.body`, outside the story canvas, so the assertions start from `canvasElement.ownerDocument`. The story also reports the result in the canvas, so a failure is visible without opening a panel.",
            "The stylesheet is asserted by its effect, not by its URL. A `<link>` whose request 404s or is blocked is still enumerated in `document.styleSheets` with its `href`, and the sheet is cross-origin so its `cssRules` cannot be read, so neither the element nor the `styleSheets` entry proves anything. An unstyled bar is also still a visible element with a non-zero box, so every other assertion in the story passes without any CSS at all. The check reads the computed `position` of `#cc-main` instead: `#cc-main{position:fixed}` is the CDN sheet's first rule and an unstyled `div` is `static`.",
            "The story runs the live CDN configuration with `hideFromBots: false`, which is not the configuration production ships. The play function pins that difference instead of describing it: it asserts that the override is exactly one flag and that the configuration handed to the library differs from the live `config.json` by exactly that flag.",
            "The story erases the stored consent cookie directly before the library runs. It previously relied on `CookieConsent.reset(true)`, which exists only once another story has loaded the library; on a fresh load of this story with a stored `cc_cookie`, the library had nothing to ask and, with `lazyHtmlGeneration` on by default, never built the bar, so the check failed on a perfectly healthy banner.",
            "Removed the false note on the \"Default\" story claiming the bar does not appear in Storybook. It does. What it does not do is appear in an automated browser, because the library's `hideFromBots` default suppresses it whenever `navigator.webdriver` is true, while still reporting success.",
            "Fixed the \"Reset banner\" story. It guarded on `window.CookieConsent.showConsentModal`, which does not exist in vanilla-cookieconsent v3, so the guard silently skipped and the button did nothing. It now erases the stored consent with `eraseCookies()` and rebuilds the bar with `show(true)`.",
            "Documented the v3 consent API and the `hideFromBots` behaviour, and corrected the reset example that used the same non-existent method."
          ]
        },
        {
          "version": "1.2.3",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1193): The \"Default\" story canvas now explains that the component renders nothing itself and that the consent bar is drawn by the CDN library. It also records an open question: the bar does not appear in Storybook even with no stored consent (`CookieConsent.validConsent()` returns `false` and a forced `show(true)` produces no DOM), so the empty canvas is not yet confirmed to be expected behaviour. Story copy only, no component change.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.2.2",
          "date": "2026-09-09",
          "notes": "The default cookie-banner CDN base now uses the canonical assets root. Custom CDN bases remain supported.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a consent button whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Added an imported Mangrove theme bridge for the CDN consent interface. Banner surfaces, text, links, buttons, toggles, separators, focus, motion, and RTL fonts now follow semantic Mangrove tokens while consent actions retain a clear filled-button hierarchy.",
            "Aligned consent-action typography with the shared button foundation by using regular Roboto and the Arabic body family."
          ]
        },
        {
          "version": "1.1",
          "date": "2026-05-11",
          "notes": "Return an empty Fragment instead of `null` so Storybook's `react-docgen` pass can classify the component and extract its `propTypes` for the AI manifest. No runtime change (empty Fragments render nothing in the DOM). unisdr/undrr-mangrove#1006.",
          "pr": 1006,
          "prRef": "unisdr/undrr-mangrove#1006",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2025-06-17",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-notice-notice": {
      "name": "Notice",
      "entries": [
        {
          "version": "1.2.1",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The React example imports `@undrr/undrr-mangrove/components/Notice.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. Documentation only; the component is unchanged. The Drupal hydration snippet is corrected in the same pass: it reached into `@undrr/undrr-mangrove/src/hydrate` and `@undrr/undrr-mangrove/stories/…/Notice.hydrate`, and the tarball ships no `src/` or `stories/` directory at all; `components/hydrate.js` and `components/Notice.js` are the published files, and the latter carries `fromElement` alongside the component.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.2.0",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): Dismissing a notice no longer drops focus on `<body>` (WCAG 2.4.3 Focus Order). Focus returns to whatever was focused when the notice appeared, or failing that to the nearest focusable neighbour, searched outwards one ancestor at a time so it lands near where the user was. Matches the focus handling Snackbar already had.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1175): Outline secondary actions inside a notice read the new `--mg-notice-action-secondary` token, so a theme can darken them for the tinted surfaces. DELTA sets it to its darker teal, which lifts the offline notice's status link from 4.26:1 to 5.44:1 (WCAG 1.4.3). Other themes keep their outline secondary colour.",
          "pr": 1175,
          "prRef": "unisdr/undrr-mangrove#1175",
          "prUrl": null
        },
        {
          "version": "v1.0.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1164): Initial implementation of the `Notice` / alert banner component (`mg-notice`) with `info`, `warning`, `negative` and `positive` variants, compact, prominent and overlay modifiers, self-managed dismissal, configurable heading level, and Layer 2 hydration (unisdr/undrr-mangrove#1155).",
          "pr": 1164,
          "prRef": "unisdr/undrr-mangrove#1164",
          "prUrl": null
        }
      ]
    },
    "components-notice-service-notice": {
      "name": "ServiceNotice",
      "entries": [
        {
          "version": "1.1.2",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The React example imports `@undrr/undrr-mangrove/components/ServiceNotice.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. Documentation only; the component is unchanged. The Drupal hydration snippet is corrected in the same pass: it reached into `@undrr/undrr-mangrove/src/hydrate` and `@undrr/undrr-mangrove/stories/…/ServiceNotice.hydrate`, and the tarball ships no `src/` or `stories/` directory at all; `components/hydrate.js` and `components/ServiceNotice.js` are the published files, and the latter carries `fromElement` alongside the component.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1178): The Drupal behavior example uses `once()` from `core/once`, so repeated attaches add one `mg-service-notice:retry` listener per container instead of stacking duplicates.",
          "pr": 1178,
          "prRef": "unisdr/undrr-mangrove#1178",
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1175): Fixes integration gaps reported in unisdr/undrr-mangrove#1172. The status link now includes visually hidden \"(opens in a new tab)\" text, translatable with the new `labels.opensInNewTab` key. Server-rendered output now keeps the status link. Documented the vanilla hydration contract (data attributes and the `mg-service-notice:retry` event) with a CDN example, and published it in the AI manifest's `hydration` field. Examples use `https://messaging.undrr.org/` instead of the redirecting `status.undrr.org`.",
          "pr": 1175,
          "prRef": "unisdr/undrr-mangrove#1175",
          "prUrl": null,
          "details": [
            "The status page link on DELTA's offline notice meets WCAG 1.4.3 contrast (5.44:1, was 4.26:1), through the Notice `--mg-notice-action-secondary` token.",
            "The `mg-notice--compact` and `mg-notice--overlay` class fallbacks no longer style the hydration container as a second notice. The classes are removed once read; `data-is-compact` and `data-is-overlay` are preferred.",
            "The CDN example pins React 19.3.0 to match the build, and its retry handler is a placeholder that no longer throws."
          ]
        },
        {
          "version": "1.0.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1164): Initial implementation of the `ServiceNotice` component (rendered through Notice, no stylesheet of its own) with degraded and offline states, retry actions, capped automatic retries with backoff, overlay mode, Layer 2 hydration with a retry event, and UN translations (unisdr/undrr-mangrove#1155).",
          "pr": 1164,
          "prRef": "unisdr/undrr-mangrove#1164",
          "prUrl": null
        }
      ]
    },
    "components-notice-snackbar": {
      "name": "ShowOffSnackbar",
      "entries": [
        {
          "version": "2.0.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1164): **Breaking (visual):** the toast now renders through Notice instead of its own filled colour styles, so it matches Notice and ServiceNotice. Hardcoded severity colours, `SnackbarIcons.jsx` and the custom close-button styles are removed. The body mounts only while open (staying hidden and inert through the slide-out), focus returns to its previous element on close, auto-dismissing toasts no longer take focus, and the redundant `aria-live` wrapper is gone. The props API is unchanged; `severity` now defaults to `info`, and `info`/`success` toasts announce politely (`role=\"status\"`) instead of as alerts.",
          "pr": 1164,
          "prRef": "unisdr/undrr-mangrove#1164",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Aligned the action with the shared 44px target, button radius and motion tokens; replaced physical message spacing with logical spacing for RTL; removed a duplicate error-state rule and redundant per-language story exports in favour of the global locale toolbar.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Consolidated `closeLabel` and `closeAriaLabel` props into a single `labels` object with `DEFAULT_SNACKBAR_LABELS` export. Backwards compatible: `DEFAULT_SNACKBAR_LABELS` covers all keys. Co-located `_labels.js` provides `LABELS_ES/FR/JA/ZH/AR/RU`; locale toolbar now switches translated strings in all stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-02-23",
          "notes": "Fix: `onClose` stored in ref to prevent re-firing timer/keyboard/focus effects on re-renders; close button focus only triggers on `opened` transition",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2025-04-28",
          "notes": "Accessibility improvements (screen reader announcements, ARIA attributes, focus management, Escape key); mobile-responsive layout; updated severity colors for contrast; modernized Sass functions",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-12-20",
          "notes": "Initial release",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-pageheader": {
      "name": "PageHeader",
      "entries": [
        {
          "version": "1.8.1",
          "date": "2026-09-24",
          "notes": "(unisdr/undrr-mangrove#1291): The custom-languages example replaces a placeholder label with Portuguese and lists its languages (Arabic, French, Portuguese, Spanish) in United Nations order.",
          "pr": 1291,
          "prRef": "unisdr/undrr-mangrove#1291",
          "prUrl": null
        },
        {
          "version": "1.8",
          "date": "2026-09-23",
          "notes": "`languageDisplay=\"links\"` falls back to the icon dropdown below the tablet breakpoint (replacing the two-letter codes, which wrapped under the logo with longer lists) and when there are more than the new `maxLanguageLinks` (default 6) languages. Default and example languages are now ordered alphabetically by English name, per UN convention.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.7",
          "date": "2026-09-14",
          "notes": "The logo now renders via the [Logo](?path=/docs/components-logos--docs) atom (`crop=\"autocrop\"`) instead of a raw `<img>`. Added `logoWidth`, `logoHeight`, `logoLang`, `logoCrop`, and `locale` props so translated or differently proportioned logos fit the header correctly.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.6.1",
          "date": "2026-09-11",
          "notes": "(PR unisdr/undrr-mangrove#1134):",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "Restored pre-2.0 logo crop behavior on tablet/mobile breakpoints (instead of shrinking with `object-fit: contain`), retuned for the current logo asset.",
            "Updated toolbar controls to icon-only at all widths, enlarged and aligned icon sizing, and moved account-language accessible names to `mg-u-sr-only` text.",
            "Added `languageDisplay=\"links\"` for short language lists (2-4), while keeping `dropdown` as the progressive-enhancement default.",
            "Removed the `Narrow header` story and set the stories to `layout: 'fullscreen'` so docs previews match full-bleed header usage."
          ]
        },
        {
          "version": "1.6.0",
          "date": "2026-09-10",
          "notes": "(unisdr/undrr-mangrove#1114): Fixed duplicate hard-coded IDs by adding `idPrefix` namespacing for header/language elements. Default output remains unchanged when `idPrefix` is omitted, preserving existing Drupal selectors.",
          "pr": 1114,
          "prRef": "unisdr/undrr-mangrove#1114",
          "prUrl": null
        },
        {
          "version": "1.5.1",
          "date": "2026-09-09",
          "notes": "The default logo URL now uses the canonical assets CDN root. Custom logo URLs remain supported.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.5",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1105): Refined toolbar spacing/dividers, improved visible focus and control sizing, and corrected logo breakpoint behavior without cropping. RTL direction handling and existing decoration/background remain unchanged.",
          "pr": 1105,
          "prRef": "unisdr/undrr-mangrove#1105",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-05-15",
          "notes": "Replaced bare `<i>` icon element with `<Icon>` component for correct `aria-hidden` semantics (unisdr/undrr-mangrove#908)",
          "pr": 908,
          "prRef": "unisdr/undrr-mangrove#908",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-03-14",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): Added `showLogo`, `showAccount`, and `showLanguage` boolean props to toggle individual toolbar sections. All default to `true` for backward compatibility. Useful for sub-branded sites (e.g., DELTA) that provide their own navigation and only need the decoration stripe.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-02-09",
          "notes": "Migrated icon classes from `fa-*` to `mg-icon-*` namespace",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2025-10-27",
          "notes": "Improved responsiveness of UNDRR logo",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2025-06-04",
          "notes": "Initial release with decoration stripe, UNDRR logo, language switcher, Google Analytics integration, and decoration-only variant",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-preview-access": {
      "name": "PreviewAccess",
      "entries": [
        {
          "version": "1.0.3",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1267): The Option B React snippet imported `@undrr/undrr-mangrove/js/preview-access` without the extension, which fails with `ERR_MODULE_NOT_FOUND` in Node ESM: the package ships no `exports` map to fill it in. It now names `js/preview-access.js`. Documentation only; the module is unchanged.",
          "pr": 1267,
          "prRef": "unisdr/undrr-mangrove#1267",
          "prUrl": null
        },
        {
          "version": "1.0.2",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Fixed Arabic typography coverage in gate headings, button, and input by using `ui`/`text` font roles and removing the component-level `:lang(ar)` block. Latin rendering is unchanged except the submit button now follows the standard button text font role.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to gate text whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-04-30",
          "notes": "Initial release. Vanilla JS and CSS, body-level gate, `sessionStorage`-scoped unlock, RTL and Arabic font swap, `inert`-based focus trap, override-able copy via data attributes.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-quotehighlight": {
      "name": "QuoteHighlight",
      "entries": [
        {
          "version": "1.4.1",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The usage example imports `@undrr/undrr-mangrove/components/QuoteHighlight.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. The binding also changes: the published bundle exports this component as its **default**, with no named `QuoteHighlight`, so `import QuoteHighlight from …` is correct and the braced form would give `undefined`. Documentation only; the component is unchanged.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1259): `quote`, `attribution` and `attributionTitle` now run through DOMPurify before they are rendered, the same call `TextCta` makes on its `text` prop. The divergence from `TextCta` was never a decision (nothing in the repo stated a reason for it), and these three fields are the closest match in the library to the content `TextCta` already sanitises: short authored strings, hydration-fed from `data-quote`, `data-attribution` and `data-attribution-title` on a CMS-rendered container. **Consumer-visible:** the default allow-list keeps everything these props exist to carry (`<cite>`, `<em>`, `<br>`, and a link on an attribution name, verified by test), but markup that used to render will now be filtered, including `<script>`, event-handler attributes such as `onclick` and `onerror`, `javascript:` URLs, and `<iframe>` and other embeds. It also drops `target` and `referrerpolicy` from an anchor, so an attribution link authored as `target=\"_blank\"` now opens in the same tab (the one removal here that is not an attack shape, and DOMPurify's library-wide default rather than a choice made for this component). `href`, `rel`, `class`, `id`, `title`, `hreflang`, `lang`, `dir`, `aria-*` and `data-*` survive, so styled or Linkit-referenced anchors are unaffected. Panel-sized content belongs in `Tab` or a consumer wrapper, not in a pull quote. Sanitising upstream is still worth keeping and is no longer required. The `<` check on `quote` stays, now stated for what it is: it picks the wrapper element, a `<p>` for a plain quote and a `<span>` for one with markup, and plays no part in the safety. Reverses the \"you must sanitise this yourself\" wording added in unisdr/undrr-mangrove#1258 across the component comment, the three render sites, the propTypes, this page, the CDN attribute table and the AI manifest entry. One knock-on: DOMPurify needs a DOM, and the AI manifest renders components in plain Node, so it now skips this one the way it already skips `VerticalCard` and the other sanitising components. The `renderedHtml` a vanilla-HTML consumer reads from `ai-components/components-quotehighlight.json` is therefore curated in `scripts/ai-manifest/component-data.js` instead of auto-rendered, and has to be kept in step with the JSX by hand. It carries an `aria-label` the component does not emit by itself: the unnamed `<section>` is a known limitation, and unknown props pass through to it, so a React caller can set the same label. That caveat is now in the example's published name and in the hydration note rather than only in a source comment, since the manifest is read by agents that copy the markup. The manifest's own drift guard, `checkCuratedDrift`, cannot re-render a sanitising component and had been swallowing that failure silently; it now reports each such entry as unverifiable, so the hand-maintained entries are visible as such.",
          "pr": 1259,
          "prRef": "unisdr/undrr-mangrove#1259",
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1258): Documented the quote HTML trust contract. `quote`, `attribution` and `attributionTitle` are rendered as HTML and none of them is sanitised. That was already true and nothing about the rendering changed; this only writes it down where a consumer will meet it. The AI manifest hydration note had carried this for some time, and it was the only place it appeared: the component source said nothing, the propTypes said only \"The quote text to display\", and the MDX said nothing at all, so a React consumer reading the documentation had no way to learn it. The component now carries a block comment with the reasoning and a pointer at each of the three render sites, the propTypes say which fields take the HTML path, the MDX has a \"Quote HTML trust contract\" section after the usage example with a DOMPurify snippet, and the CDN attribute table repeats it on the three affected attributes. The per-prop asymmetry is stated: `attribution` and `attributionTitle` always render as HTML, while `quote` renders as text unless it contains a `<`, a substring test that picks a rendering path rather than a safe one.",
          "pr": 1258,
          "prRef": "unisdr/undrr-mangrove#1258",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1198): Made the `dark` variant surface opaque UNDRR blue instead of a 70% wash, so white quote, name and title text meet WCAG AA (8.31:1) on any backdrop rather than 3.99:1 on white",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2026-03-04",
          "notes": "Added hydration support with `fromElement` prop extraction and `createHydrator` integration",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2025-04-23",
          "notes": "Added `text-wrap: balance` for better text flow; removed bold font weight from quotes",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2025-03-24",
          "notes": "Initial release with line and image variants, light/dark/bright backgrounds, portrait/side image support, full/left/right alignment",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-reading-column": {
      "name": "Readingcolumn",
      "entries": [
        {
          "version": "1.1",
          "date": "2026-09-11",
          "notes": "([undrr/web-backlog#3060](https://gitlab.com/undrr/web-backlog/-/work_items/3060)): Added `--mg-reading-with-contents-width` and moved sidebar column shape to shared `mg-grid--article` (this file now handles placement only).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-10",
          "notes": "Added `mg-reading` as a distributed layout utility. (unisdr/undrr-mangrove#1112)",
          "pr": 1112,
          "prRef": "unisdr/undrr-mangrove#1112",
          "prUrl": null
        }
      ]
    },
    "components-scrollcontainer": {
      "name": "ScrollContainer",
      "entries": [
        {
          "version": "1.3.5",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1273): The overflow region is now reachable by Tab with a visible focus indicator, so keyboard users can use the browser's native horizontal scroll keys even when its items contain no links or controls. Arrow navigation now has an accessible name derived from the new `ariaLabel` / `data-aria-label` option. Give each container a unique label on pages with more than one so landmark navigation can distinguish them.",
          "pr": 1273,
          "prRef": "unisdr/undrr-mangrove#1273",
          "prUrl": null
        },
        {
          "version": "1.3.4",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1222, unisdr/undrr-mangrove#1205): `fromElement` reads its content wrapper from the container it is hydrating instead of re-parsing `innerHTML` with `DOMParser` and querying the copy. The copy only ever held one container's markup, so two hydrated containers on a page never did read each other's children (the reported symptom does not reproduce), but the lookup still matched a `.mg-scroll__content` belonging to a **nested** scroll container, so an outer container with no wrapper of its own rendered the nested container's cards instead of its own content. A direct-child wrapper now wins, otherwise the first descendant wrapper with no nested scroll container between it and this container. Dropping the `DOMParser` round trip also stops the server-rendered markup being re-serialised on the way through. A new \"Two hydrated on one page\" story hydrates two rows through `createHydrator`; each keeps its own cards.",
          "pr": 1222,
          "prRef": "unisdr/undrr-mangrove#1222",
          "prUrl": null
        },
        {
          "version": "1.3.3",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1193): The \"With minimum width\" story asked for `minWidth=\"1500px\"` in an unbounded parent, so at 375px the demo pushed the whole page sideways to 1516px. The story now uses a realistic value inside a parent that contains the overflow, and the docs spell out that `min-width` wins over the container's `max-width: 100%` and that `itemWidth` is the lever for widening the scrollable track. Story and documentation only: `minWidth` keeps its documented meaning, a minimum width for the container, and no component behaviour changed.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.3.2",
          "date": "2026-09-15",
          "notes": "(unisdr/undrr-mangrove#1153): The desktop scrollbar now has a themed appearance instead of the unstyled browser default: a fixed blue thumb (calm at rest, full colour on hover) and a barely-there translucent track, on `scrollbar-color`/`scrollbar-width` (Firefox) and `::-webkit-scrollbar-*` (Chromium/Safari). Uses fixed literal colours rather than `var(--mg-color-*)` tokens, since at least one WebKit version silently drops the whole declaration if it contains a `var()` value there. Desktop-only in practice; mobile browsers generally hide or ignore scrollbar styling regardless. Known pre-existing limitation noted: macOS Safari below 100% zoom can fail to register clicks on this component's arrow buttons and its native scrollbar, due to a longstanding WebKit viewport-coordinate bug unrelated to this component.",
          "pr": 1153,
          "prRef": "unisdr/undrr-mangrove#1153",
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1109): Card stories enable the existing `stretchItems` option to demonstrate equal-height rows. The component default remains `false`.",
          "pr": 1109,
          "prRef": "unisdr/undrr-mangrove#1109",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Moved navigation below the collection, refreshed arrows as subtle circular controls with tactile states, and added proximity scroll snapping while preserving mouse drag and touch scrolling.",
            "Consolidated spacing into one moderate `spacing-150` flex gap and removed physical per-item margins, eliminating doubled whitespace, keeping collection edges flush, and preserving rhythm in RTL layouts.",
            "Reworked documentation examples with varied images, headlines, summaries, and calls to action so demos exercise realistic mixed-length card content.",
            "Added `stretchItems` / `data-stretch-items` for equal-height mixed-content rails without consumer CSS.",
            "Raised arrow controls to the recommended 44px target, removed unintended vertical overflow, tokenised scrollbar radii, respected reduced-motion preferences, and recalculated arrow state when container or content dimensions change.",
            "Removed redundant per-language story exports in favour of the global locale toolbar."
          ]
        },
        {
          "version": "1.2",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Consolidated `scrollLeftLabel` and `scrollRightLabel` props into a single `labels` object with `DEFAULT_SCROLL_LABELS` export. Backwards compatible: `DEFAULT_SCROLL_LABELS` covers all keys. Co-located `_labels.js` provides `LABELS_ES/FR/JA/ZH/AR/RU`; locale toolbar now switches translated strings in all stories.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-03-03",
          "notes": "Added hydration support with `fromElement` prop extraction and `createHydrator` integration",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2025-03-27",
          "notes": "Initial release with responsive design, mouse drag scrolling, and optional navigation arrows",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-showmore": {
      "name": "ShowMore",
      "entries": [
        {
          "version": "1.6",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1239): Added translated per-instance React labels with generic defaults, scoped per-item enhancement, dynamic-item support and lifecycle cleanup. The default story's locale toolbar previews both translated toggle labels. Regression tests cover late items, translation changes and StrictMode cleanup.",
          "pr": 1239,
          "prRef": "unisdr/undrr-mangrove#1239",
          "prUrl": null
        },
        {
          "version": "1.5",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1238, unisdr/undrr-mangrove#1228): Two toggles sharing a target selector each reveal their own content. `mgShowMore()` resolved the target with `document.querySelector`, so two toggles left on the default `.mg-show-more--container` both drove the first block on the page. The target is now resolved nearest scope first: the script climbs the toggle's ancestors and takes the first match inside one, falling back to the document if the walk reaches `<html>` without matching. Revealed content need not be a sibling or descendant of its toggle, so the lookup cannot be confined to a container, but it can be ordered, and ordering it is never worse. Canonical markup finds the adjacent wrapper, two blocks in separate subtrees each find their own, and a genuinely detached target resolves exactly as before.",
          "pr": 1238,
          "prRef": "unisdr/undrr-mangrove#1238",
          "prUrl": null,
          "details": [
            "The React `ShowMore` wrapper pointed each toggle at `.${collapsable_wrapper_class}`. Its items are flat siblings, so they share a parent and the ancestor walk cannot separate them: two items given the same wrapper class still collided. Each item now carries a minted id (`mg-show-more-{useId}-{index}`) and its toggle targets that id. `collapsable_wrapper_class` goes back to being what its documentation always said it was (a styling hook), and it no longer has to be unique. Consumers reading the class off the rendered markup are unaffected; the wrapper keeps it.",
            "A new \"Two on one page\" story renders two vanilla toggles on the default selector and two React items sharing a wrapper class. Tests cover two instances on one page for both paths."
          ]
        },
        {
          "version": "1.4",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1193): Breaking: deleted the deprecated `ShowMore.scss` alias, which only forwarded to `show-more.scss` and emitted a deprecation `@warn`. The npm package no longer ships `scss/Utilities/ShowMore/ShowMore.scss`; import `scss/Utilities/ShowMore/show-more` instead. No CSS behaviour change.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.3",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1190): **Visible change to the documented markup.** The toggle is now `<button type=\"button\">` rather than `<a href=\"#\">`, and `mgShowMore()` maintains `aria-controls` on it, assigning the target an id when it has none. The anchor announced as a link and offered a dead destination if the script failed to load (WCAG 4.1.2). An anchor still present in consumer markup keeps working and is given `role=\"button\"`, so copied markup does not break, but new markup should use a button.",
          "pr": 1190,
          "prRef": "unisdr/undrr-mangrove#1190",
          "prUrl": null,
          "details": [
            "An anchor toggle now also gets a `keydown` handler, so Space activates it as `role=\"button\"` promises. The click handler already suppressed the `href=\"#\"` jump.",
            "The toggle does **not** carry `aria-expanded`, and the docs now say why. The collapse is `max-height` plus `overflow: hidden` plus a `mask-image` fade, all of which clip visually while leaving the content in the accessibility tree and the tab order, so there is no hidden content for `aria-expanded` to describe. An earlier draft of this change added it; it was removed rather than shipped as a claim the CSS does not deliver. Turning the utility into a real disclosure means hiding whole elements and losing the mid-line fade, which is a visual change held for its own decision.",
            "Focus entering a collapsed container now expands it. `overflow: hidden` left Tab landing on links clipped out of view in a box that cannot scroll to reveal them (WCAG 2.4.7, 2.4.11).",
            "The counter behind the generated `mg-show-more-target-N` ids moved from module scope to a `globalThis` symbol and now skips ids already present in the document, so two loaded copies of the script cannot mint the same id (the hazard fixed for `createHydrator` in unisdr/undrr-mangrove#1178)."
          ]
        },
        {
          "version": "1.2",
          "date": "2026-03-27",
          "notes": "Added `data-mg-show-more-skip-auto-init` attribute, `type=\"module\"` self-init, idempotency guard, single-element scope support",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-03-05",
          "notes": "Fixed event listener leak: target click handler now registers once at init instead of on every toggle; clicking expanded content no longer inadvertently collapses it",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-02-23",
          "notes": "Replaced white gradient overlay with CSS `mask-image` for background-agnostic fade effect",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2023-08-24",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-status-label": {
      "name": "Status label",
      "entries": [
        {
          "version": "1.2.3",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1233): Three documentation corrections. No colour, token, markup or compiled CSS changed.",
          "pr": 1233,
          "prRef": "unisdr/undrr-mangrove#1233",
          "prUrl": null,
          "details": [
            "**Theming.** The note said consumers redefine \"the four\" `--mg-status-label-indicator--*` properties and that PreventionWeb, IRP and MCR2030 \"inherit DELTA's\". Both were wrong. There are seven fills: six carrying the `--*` suffix (`--draft`, `--waiting-validation`, `--waiting-information`, `--published`, `--warning`, `--negative`) and the unsuffixed `--mg-status-label-indicator`, the neutral base, which is the easy one to miss. And no theme overrides any of them: all seven are Mangrove base tokens in `_variables.scss`, and none of `_theme-delta.scss`, `_theme-preventionweb.scss`, `_theme-irp.scss` or `_theme-mcr.scss` redefines one, so all five brands render the same status palette by design. The four workflow colours come from the DELTA design board, but they do not ship as DELTA's tokens.",
            "**Custom-property table.** It listed five of the seven fills; `--mg-status-label-indicator--warning` and `--negative`, added in 1.1.0 and documented everywhere else, were missing.",
            "**The data-viz cross-reference.** \"Falls to 2.2 dE2000 at seven\" contradicted `_tokens-data-viz.scss`, which publishes ΔE 3.2 for the same pair by the same method. Recomputed from the categorical palette with the Viénot 1999 projections in linear RGB: the SCSS table is right at every N and this page was wrong, so it now says 3.2. The derivation is written down in [`COLOUR-CONTRAST-METHODOLOGY.md`](https://github.com/PreventionWeb/undrr-mangrove/blob/main/docs/COLOUR-CONTRAST-METHODOLOGY.md), \"How colour-vision separation is measured\", so the next person does not have to redo it.",
            "The 1.2.0 entry below is corrected in place for a unit that dangled: 2.3 is a dE2000 just-noticeable difference, and quoting it straight after a CIE76 figure read as though 3.3 cleared it, which is the opposite of the finding."
          ]
        },
        {
          "version": "1.2.2",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1224, unisdr/undrr-mangrove#1211): The base indicator survives `--mg-status-label-indicator-border-width: 0` in forced colours. It is the one hollow mark in the set (a `Canvas` centre inside a `CanvasText` ring), so with the ring dropped it was a `Canvas` mark on a `Canvas` background and vanished from the row, at every indicator size. It now carries an inset 1px `CanvasText` outline in forced colours, which coincides with the ring at any width and is the whole of its edge at `0`. Nothing else moves: an outline takes no space in any box model, so the mark keeps its size, its fill and its text alignment edge, and the clipped triangle and octagon still offset their inner polygons by the author's ring width rather than by a forced minimum. The six named statuses are solid `CanvasText` shapes, survive a missing ring on their own and are explicitly left without the outline. Normal rendering is untouched: the rule lives inside the `@media (forced-colors: active)` block, and a pixel diff of all seven marks at three indicator sizes and both ring widths is identical before and after, so the opt-out still removes the ring where it is meant to.",
          "pr": 1224,
          "prRef": "unisdr/undrr-mangrove#1224",
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1216, unisdr/undrr-mangrove#1200): `.mg-status-label-group` now gets its markerless-but-still-a-list treatment from the shared `mg-list-unmarked` mixin instead of its own copy of the two declarations. Same compiled CSS. The pattern this component introduced in 1.2.0 is now the house rule and is applied to Breadcrumbs, Pager, OnThisPageNav, Legend, HubHeader, MegaMenu and the search widget's filter list.",
          "pr": 1216,
          "prRef": "unisdr/undrr-mangrove#1216",
          "prUrl": null
        },
        {
          "version": "1.2.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1206): Each status now has a shape as well as a colour: draft a rounded square, waiting for more information a capsule, waiting for validation a rounded diamond, degraded a chamfered triangle, offline an octagon. Published and the base indicator keep the circle. This is a visible change on every existing status label, and it needs no markup change: the shapes come from CSS on the same empty `<span>`.",
          "pr": 1206,
          "prRef": "unisdr/undrr-mangrove#1206",
          "prUrl": null,
          "details": [
            "The shapes are a second cue, not a fix for a WCAG failure: the status name is still always present as text. They matter because several of the colours are close under colour vision deficiency (degraded and offline are 3.3 CIE76 / 1.1 dE2000 apart simulated for deuteranopia, against a just-noticeable difference of about 2.3 dE2000; it is the dE2000 figure that is under the threshold, not the CIE76 one), because a CSS-only component gets reused without its label, and because a redundant cue is faster to scan.",
            "The palette itself is unchanged, and that is a measured decision rather than an omission (see \"Why the palette is not rebalanced instead\"). The seven fills were re-measured in two simulation models applied in linear RGB, and no palette assembled from the tokens this system owns does better than 2.34 dE2000 at the worst pair while holding 3:1 on white and the hue associations, which is no real improvement and costs the DELTA colours, the normal-vision separation and the ring.",
            "The seven marks are drawn as one family: all convex, all softened at one corner radius proportional to the indicator size, all carrying the same ring. Five of them are a rounded rectangle underneath (the diamond is the draft square rotated 45°), so they keep a real CSS `border`; only the triangle and the octagon need a `clip-path`.",
            "The ring is now exactly `--mg-status-label-indicator-border-width` on every edge of every shape (unisdr/undrr-mangrove#1208). The two clipped marks clip their fill to an inner polygon offset along each vertex's angle bisector instead of insetting the box and rescaling the same polygon, which used to give a different ring width on every edge and none at all on the two palest statuses.",
            "`.mg-status-label-group` hides its bullet with `list-style-type: \"\"` instead of `list-style: none`, so Safari keeps the list role and VoiceOver announces the item count the docs promise (unisdr/undrr-mangrove#1209). No `role=\"list\"` is needed on the `<ul>`.",
            "In forced colours the indicators now draw themselves in system colours instead of disappearing into the forced background, and a `@media print` block keeps the background-drawn marks on the page when a browser prints without background graphics.",
            "The shapes have different bounding boxes, so each one carries a compensating `margin-inline` and still occupies exactly `--mg-status-label-indicator-size` of inline space: a column or a table of statuses keeps one text alignment edge.",
            "`--mg-status-label-indicator-radius` still shapes the base indicator and the published circle; the five status shapes set their own geometry and ignore it. Every other `--mg-status-label-*` token behaves as before, including `--mg-status-label-indicator-border-width: 0` to drop the ring.",
            "New \"In a dense list\" story, where scanning is what the second cue buys."
          ]
        },
        {
          "version": "1.1.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1174): Added a service health story and AI manifest coverage for the `--warning` and `--negative` modifiers (unisdr/undrr-mangrove#1172).",
          "pr": 1174,
          "prRef": "unisdr/undrr-mangrove#1174",
          "prUrl": null,
          "details": [
            "In right-to-left layouts, `.mg-status-label-group` no longer picks up inline padding from the global list rules, so labels sit flush with the inline start."
          ]
        },
        {
          "version": "1.1.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1164): Added `--warning` and `--negative` service health modifiers and their indicator tokens, used by ServiceNotice.",
          "pr": 1164,
          "prRef": "unisdr/undrr-mangrove#1164",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a status label whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-02",
          "notes": "Initial release. Colours, geometry and status names sourced from the DELTA design system board \"Status Labels for Records and Events\".",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-syndicated-search": {
      "name": "SyndicationSearchWidget",
      "entries": [
        {
          "version": "1.4.15",
          "date": "2026-09-29",
          "notes": "([undrr/web-backlog#3109](https://gitlab.com/undrr/web-backlog/-/work_items/3109)): Added the [Editor configuration](?path=/docs/components-syndicated-search-editor-configuration--docs) sub-page, which moves the editor-facing search guidance for the Drupal Gutenberg block (block settings, practical examples, search syntax, `field:value` filters and taxonomy term IDs) into Mangrove, and links it from this page. Documentation only; the component is unchanged.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.14",
          "date": "2026-09-29",
          "notes": "(mangrove#1312 (unisdr/undrr-mangrove#1312)): OR and NOT typed by a reader work as documented. \"or\" was on the stop word list and removal compared words in lowercase, so `earthquake OR tsunami` was sent as `earthquake tsunami` and required both terms; \"not\" was not a stop word, so `disaster NOT earthquake` was fuzzed into `disaster~1 NOT~1 earthquake~1` and the search failed. Uppercase `AND`, `OR` and `NOT` are now kept as operators, never removed or fuzzed, and an operator left dangling by stop word removal (`earthquake OR the`) is dropped rather than sent as invalid syntax. Lowercase \"and\" and \"or\" are still removed as stop words. Operators are also left out of phrase boosting, which scores the words, not the syntax.",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "`queryAppend` is a requirement: every result must match it. The reader's query and `queryAppend` were joined with a space, so a reader's OR could group with the appended terms (`a OR b field_theme:339`). They are now combined as `(reader query) AND (queryAppend)`. Each side is also processed on its own, so a `field:value` in `queryAppend` no longer switches off stop word removal and fuzziness for what the reader typed. A widget with no reader query sends `queryAppend` exactly as before."
          ]
        },
        {
          "version": "1.4.13",
          "date": "2026-09-19",
          "notes": "(mangrove#1252 (unisdr/undrr-mangrove#1252)): The usage example imports `@undrr/undrr-mangrove/components/SyndicationSearchWidget.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. Documentation only; the component is unchanged.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.12",
          "date": "2026-09-19",
          "notes": "(mangrove#1255 (unisdr/undrr-mangrove#1255)): The Drupal integration snippet names the attribute the component actually hydrates on. It said `data-undrr-search-widget` in both the sentence and the copy-pasteable markup, so a consumer who followed it got a container nothing ever mounted into; the hydration selector is `[data-mg-search-widget]`, which this file already used correctly in every later example. Nothing in the library ever read `data-undrr-search-widget`, so it was never a legacy alias and no fallback is needed. The same wrong spelling in the `CustomEndpoint` story's Drupal example is corrected too; that story renders React directly from `args` and never hydrated, so it was not broken, only modelling markup that would not work. Documentation only; no behaviour change.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.11",
          "date": "2026-09-18",
          "notes": "(mangrove#1254 (unisdr/undrr-mangrove#1254)): Documented the endpoint trust contract. `ResultItem` renders three pieces of the search response as HTML (the pre-rendered teaser, and the Elasticsearch `highlight` fields for the title and the snippet) and does not sanitise any of them, because the `<em>` wrappers highlighting puts around matched terms are the feature and escaping the response would print them as text. That was true before this change and stays true after it; no rendering behaviour changed. What was missing was anyone saying so: `searchEndpoint` is set by the consumer, through the config prop or `data-search-endpoint`, and a consumer who repoints it at their own service inherits whatever that service returns, on a page that, this being a syndication widget, is frequently not UNDRR's. The three render sites now carry the reasoning, the MDX has an \"Endpoint trust contract\" section beside the endpoint documentation, and the AI manifest entry carries it too. The search query is not part of this: highlights come from indexed documents, not from the query string, so nothing typed in the search box is reflected back as markup. Also corrects the documented default for `searchEndpoint`, which the props table gave as `/search-endpoint` where the code has always used `https://www.undrr.org/search-endpoint`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.10",
          "date": "2026-09-18",
          "notes": "(mangrove#1234 (unisdr/undrr-mangrove#1234), mangrove#1227 (unisdr/undrr-mangrove#1227)): `hydrator.update()` no longer mounts a second widget inside the first. The widget root renders `data-mg-search-widget`, the same marker its hydration host carries and the documented hydration selector matches, and `data-mg-hydrated` was set only on the host, so a re-scan read the React-rendered root as an un-hydrated target and hydrated it, nesting three deep. The fix is in the shared hydration runtime rather than in this component: `createHydrator` now skips a match that sits inside a hydrated container the same selector also matches. It is scoped that way so a different component's hydration host nested inside a hydrated `ScrollContainer` or `Drawer` still hydrates. The marker stays on the widget root because a self-identifying root is a reasonable convention and the runtime should tolerate it, not because anything depends on it: `SearchPager`'s `[data-mg-search-widget]` fallback is unreachable inside this widget, which always passes a `widgetId`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.9",
          "date": "2026-09-18",
          "notes": "(mangrove#1222 (unisdr/undrr-mangrove#1222), mangrove#1205 (unisdr/undrr-mangrove#1205)): Changing page scrolls the widget that was paged. The pager resolved its scroll target with `document.querySelector('[data-mg-search-widget]')`, so on a page carrying two widgets, paging the second one sent the reader to the top of the first, away from the results they had just asked for. The widget root now carries its `widgetId` as its DOM id and the pager scrolls to that element. `SearchResults` and `Pager` both default `widgetId` to an empty string, and with no id to resolve the pager keeps the previous `[data-mg-search-widget]` lookup. (Corrected in 1.4.10: that fallback was described here as covering public exports used in custom layouts. `src/index.js` exports only `SyndicationSearchWidget`, `DEFAULT_LABELS` and `interpolateLabel`, and the `exports` map in `package.json` makes the deep path unreachable, so the fallback is dead code; it is kept as cheap insurance, not as a supported entry point.) One widget on a page behaves exactly as before.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.8",
          "date": "2026-09-18",
          "notes": "(mangrove#1221 (unisdr/undrr-mangrove#1221), mangrove#1204 (unisdr/undrr-mangrove#1204)): The mobile filter drawer is anchored to the inline start rather than the physical left, so in Arabic it rests on the right edge, slides in from the right and casts its shadow to the left, over the page content. `left: 0` became `inset-inline-start: 0`, but the slide is a `translateX` keyframe, which works on a physical axis and would have fought that change: the panel would have rested on the right in Arabic while still entering from the left. Both the travel and the shadow offset are now custom properties that `:dir(rtl)` flips, the shape `.mg-drawer--start` already used in [Drawer](https://mangrove.undrr.org/?path=/docs/components-navigation-drawer--docs). LTR rendering is pixel-identical: same resting edge, same 320px width, same `4px 0 20px` shadow, same 0.3s ease-out entry.",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "The slide now honours `prefers-reduced-motion: reduce`, which it did not before; the drawer appears in place instead of travelling. Closing is unaffected in both directions: `MobileFilterDrawer` unmounts on close, so there has never been a close animation to mirror.",
            "The keyframe is renamed: `@keyframes mg-search-slide-in-left` is now `mg-search-slide-in-start`, because it no longer travels on a fixed physical axis. The old name is gone from the published CSS, so anything that referenced it by name (an `animation-name` override or a redefined `@keyframes` block) must use the new one."
          ]
        },
        {
          "version": "1.4.7",
          "date": "2026-09-18",
          "notes": "(mangrove#1216 (unisdr/undrr-mangrove#1216), mangrove#1200 (unisdr/undrr-mangrove#1200)): `.mg-search__active-filters-list` hides its markers with `list-style: none` followed by `list-style-type: \"\"` (the new `mg-list-unmarked` mixin), so it keeps its list role in Safari from CSS rather than relying on the `role=\"list\"` attribute the component renders. The attribute stays; the list no longer depends on it. The results grid and list keep their explicit `role=\"list\"` because they are `<div>`s, not lists, and the select dropdown keeps `role=\"listbox\"`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.6",
          "date": "2026-09-18",
          "notes": "(mangrove#1193 (unisdr/undrr-mangrove#1193)): Breaking: deleted the deprecated `SyndicationSearchWidget.scss` alias, which only forwarded to `syndication-search-widget.scss` and emitted a deprecation `@warn`. The npm package no longer ships `scss/Components/SyndicationSearchWidget/SyndicationSearchWidget.scss`; import `scss/Components/SyndicationSearchWidget/syndication-search-widget` instead. That partial `@extend`s `%mg-form-input-base`, so `scss/Components/Forms/form-base` must be imported before it (unchanged from 1.x, and equally true of the alias). No CSS behaviour change.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.5",
          "date": "2026-09-17",
          "notes": "(mangrove#1192 (unisdr/undrr-mangrove#1192)): The search field's clear button is a 36x36 target, clear of the 24x24 minimum in WCAG 2.5.8, and is positioned with `inset-inline-end`, so it sits at the end of the field in RTL instead of over the start of the Arabic text. The field's reserved space grew to match.",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "The close glyph inside it is sized at 18px, the icon-only button convention shared with CTA buttons and chips, instead of the 10px `--mg-font-size-100`, so the visible mark matches the larger target.",
            "The rest of the widget's direction-specific spacing is logical too: the custom select trigger aligns with `text-align: start` and its chevron gap uses `margin-inline-start`, and the active-filter label, chip connector, result `•` separator, facet checkbox label indent and subtype option indent use `margin-inline-end` / `padding-inline-start`. They previously stayed on the physical left or right in Arabic."
          ]
        },
        {
          "version": "1.4.4",
          "date": "2026-09-01",
          "notes": "(mangrove#1086 (unisdr/undrr-mangrove#1086)): Aligned native and custom search selects with Mangrove's softer form language: light neutral fills, subtle borders, small shared radii and token-driven focus rings. Removed redundant per-language story exports; the shared story decorator continues to supply translated labels through the global locale toolbar.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.3",
          "date": "2026-07-24",
          "notes": "(mangrove#1077 (unisdr/undrr-mangrove#1077)): Hidden teaser fields configured via `visibleTeaserFields` are now programmatically stripped from pre-rendered teaser HTML via `stripHiddenTeaserFields` and conditionally omitted in fallback React rendering mode. This prevents unnecessary image asset downloads (`<img>` tags) when hidden and eliminates CSS specificity conflicts with `.mg-search--hide-*` classes (which are deprecated and scheduled for removal in November 2026).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.2",
          "date": "2026-07-06",
          "notes": "(web-backlog#2906): Organization results now always resolve to `https://www.preventionweb.net` regardless of `field_domain_access`. Organization nodes are indexed against multiple domains, so `field_domain_access[0]` can be arbitrary; this pins organization links to their authoritative domain in both teaser and fallback result rendering.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.1",
          "date": "2026-06-19",
          "notes": "(mangrove#774 (unisdr/undrr-mangrove#774)): Aligned search widget form elements with Mangrove base styles. `.mg-search__input` now extends `%mg-form-input-base` (2px solid black border, `$mg-radius-form-input`, `$mg-font-size-300`, standard focus ring). `.mg-select__trigger` and `.mg-select__search-input` updated to match: 2px `$mg-color-black` border, `$mg-radius-form-input`, `$mg-font-size-300`, `$mg-color-white` background, `focus-visible` ring using `$mg-color-form-focus`. Custom indicator size reduced 18px to 16px, recoloured with `$mg-color-form-check` token family. `accent-color` for native checkbox/radio inputs updated to `$mg-color-form-check--checked`.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.4.0",
          "date": "2026-06-18",
          "notes": "Full UI string translation support via `labels` prop and `data-labels` HTML attribute. Every user-visible string is overridable without forking the component. Ships with complete label sets for Spanish, French, Japanese, Simplified Chinese, Arabic (6-form CLDR plurals via `Intl.PluralRules`), and Russian (3-form). Standalone `Pager` component extended with `prevLabel`, `nextLabel`, `goPrevLabel`, `goNextLabel`, `pageLabel`, `currentPageLabel`, and `pageOfLabel` props. `DEFAULT_LABELS` exported from `SearchContext.jsx` as the single source of truth for all English defaults.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-05-23",
          "notes": "(mangrove#1031 (unisdr/undrr-mangrove#1031)): Fixed programmatic default filters being dropped on facet-less widgets. `buildQuery` skipped `post_filter` entirely when the facet UI was hidden, so any `defaultFilters` (or filters injected via the `configModifiers` hook) never reached Elasticsearch and the widget returned an unfiltered search. `post_filter` now applies whenever there are active facet or custom-facet values, even with facets hidden; aggregations stay skipped, so the 1.2.1 payload optimization is unchanged. Regression introduced in 1.2.1.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.3.0",
          "date": "2026-05-07",
          "notes": "(mangrove#981 (unisdr/undrr-mangrove#981)):",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "New `facets` config prop (`false | 'sidebar' | 'horizontal'`, default `'sidebar'`) replaces the boolean `showFacets` for layout control; `'horizontal'` renders the facets as a strip above the results region instead of a sidebar. Legacy `showFacets: false` continues to work as a backwards-compatible alias for `facets: false`. Drupal sites can pass the layout via `data-facets=\"horizontal\"`",
            "New `facetsTarget` config prop (CSS selector) renders the facets UI into an external DOM region via React portal, letting consuming pages place facets in a left rail or other region outside the widget's own container while results render where the widget is mounted. Search context spans the portal so facets and results stay in sync without extra wiring. Drupal sites pass the selector via `data-facets-target`. If the selector doesn't match an element at mount time, the widget falls back to the in-widget `facets` layout and logs a console warning",
            "New `searchTarget` config prop (CSS selector) renders the search input into an external DOM region via React portal, mirroring `facetsTarget`. Used together they let consumers freely arrange search input / facets / results across page regions while keeping a single widget instance and shared `SearchContext`. Drupal sites pass the selector via `data-search-target`. Falls back to in-widget rendering (with a console warning) if the target element is not present at mount time. For **cross-page** hero patterns the recommended approach is a plain HTML `<form action=\"/search\">` submitting to the results page with a `#query=` fragment",
            "Storybook stories regrouped under `Components/Syndicated search/{Widget,Bar}` and split into category files (`Layouts`, `Display modes`, `Filters`, `Integrations`, `Toggles`) so the sidebar reflects the family rather than a flat list of 26 entries. Pure reorganisation: no story content removed, just moved. The `QuerySyntax` and `StaticMockup` reference stories are now reference sections in this MDX page",
            "`SearchForm` submit and clear buttons now use the standard `mg-button mg-button-primary` and `mg-icon-search` / `mg-icon-close` primitives instead of a bespoke variant with an inline Lucide SVG; `.mg-search__input` aligns to the same padding, border-width and border-radius tokens as `mg-button` so the input and submit button share the same natural height and corner radius; clear button is a 20×20 hit target with darker default color and brand-color hover",
            "Removed the in-input loading spinner (`.mg-search__loading`) and the submit-button spinner (`.mg-search__submit-spinner` / `.mg-search__submit--loading`) so loading state is conveyed by a single indicator: the widget-level progress strip plus `aria-busy` on the results region; the submit button now keeps a stable \"Search\" label and search icon while a search runs",
            "Fixed URL hash query being stomped on initial mount: the `deferredQuery` effect was unconditionally dispatching `setQuery('')` on first render, racing `useHashSync`'s hash-loaded query before INITIALIZE could preserve it; the effect now skips its first render",
            "Fixed `enableHashSync: 'true'` (string) silently disabling hash sync: Drupal data attributes always arrive as strings, but the hook used a strict `=== true` comparison; now accepts both boolean `true` and string `'true'`"
          ]
        },
        {
          "version": "1.2.6",
          "date": "2026-05-05",
          "notes": "Fixed incorrect CSS selector for `contentType` teaser field: `.mg-card__tag` is not a BEM class; corrected to `.mg-card__label` in `TEASER_FIELDS`, the hide rule in `syndication-search-widget.scss`, and the story comment",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.5",
          "date": "2026-05-04",
          "notes": "([web-backlog#1479](https://gitlab.com/undrr/web-backlog/-/work_items/1479)): PW publication subtypes: `CONTENT_SUBTYPES.publication` expanded to support three independent subtype fields (`field_undrr_publication_types`, `field_publication_type`, `field_irp_publication_subtype`); fixed silent aggregation failure caused by incorrect field key `undrr_publication_type` (missing `field_` prefix and plural suffix); all array-iteration sites normalised for backwards compatibility with single-config subtypes on other content types",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.4",
          "date": "2026-04-09",
          "notes": "(web-backlog#919 (unisdr/undrr-mangrove#919)): `fromElement` now parses `data-custom-filters`, `data-custom-facets`, and `data-visible-teaser-fields` as JSON, consistent with `defaultFilters` and `allowedTypes`",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.3",
          "date": "2026-03-19",
          "notes": "([web-backlog#2630](https://gitlab.com/undrr/web-backlog/-/work_items/2630)): When both `siteName` and `contentType` are hidden via `visibleTeaserFields`, the `.mg-search__result-badges` wrapper div is no longer injected into the DOM. Previously the empty wrapper was still rendered and hidden by CSS.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.2",
          "date": "2026-03-19",
          "notes": "Fixed search input UI bounce: `useDeferredValue` stale flag was triggering heavy visual feedback (progress bar, button text swap, pulsing overlay, opacity drop) on every keystroke; now only `isLoading` and `isPending` drive visual indicators; increased default `debounceDelay` from 300ms to 500ms to reduce wasted API calls while staying under the 1-second perceived delay threshold; fixed `?text=` and `?query=` URL parameters being ignored due to race condition where `INITIALIZE` action overwrote the query set by `useHashSync`",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.1",
          "date": "2026-03-13",
          "notes": "([web-backlog#2660](https://gitlab.com/undrr/web-backlog/-/issues/2660)) Native `requireImage`, `interestingnessTiers`, `longevityTiers` config options (moved from Drupal wrapper); `TEASER_FIELDS` constant and `allTeaserFieldsVisible()` as single source of truth for teaser field visibility; ES query optimization (skip aggs/highlights/post_filter for syndication embeds); card-aware loading skeletons; SyndicatedCards story pattern",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2.0",
          "date": "2026-03-04",
          "notes": "Added hydration support with `fromElement` prop extraction and `createHydrator` integration",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-02-26",
          "notes": "",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "([web-backlog#1992](https://gitlab.com/undrr/web-backlog/-/issues/1992)) Taxonomy term results: search results now include taxonomy terms (Hazard, Country, Theme) alongside content nodes; `vid:` prefix in type filter for vocabulary filtering; configurable `searchEndpoint` with CustomEndpoint story documenting override patterns",
            "([web-backlog#2636](https://gitlab.com/undrr/web-backlog/-/issues/2636)) Card display modes (`displayMode`: list/card/card-book) with `gridColumns` control; teaser field visibility (`visibleTeaserFields`) with CSS-based hiding for 7 toggleable fields; Drupal image style rewriting for card aspect ratios"
          ]
        },
        {
          "version": "1.0.0",
          "date": "2026-02-13",
          "notes": "Pager styles extracted to standalone `Components/Pager` component; internal `Pager.jsx` is now a thin wrapper; no API changes",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0-alpha.2",
          "date": "2026-01-01",
          "notes": "Mobile filter drawer; custom SelectDropdown replacing Chosen.js (searchable, keyboard navigable, touch-friendly); loading state improvements; compact mobile filter button",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0-alpha.1",
          "date": "2025-12-12",
          "notes": "Initial React 19 implementation with faceted filtering, URL hash sync, Elasticsearch teaser support, taxonomy facet labels, custom facets, Google Analytics integration, skeleton loading",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-table": {
      "name": "TableTag",
      "entries": [
        {
          "version": "2.4.1",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1215): Fixed data-table body and code-cell font sizes losing to the base cell rule. Combined small/data tables retain compact headers and smaller code cells. Numeric cell alignment now also wins over the base start alignment.",
          "pr": 1215,
          "prRef": "unisdr/undrr-mangrove#1215",
          "prUrl": null
        },
        {
          "version": "2.4",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1157): Added the `.mg-table--data` high-density data table variant, sticky table headers (`.mg-table__th--sticky`), sortable header buttons with accessible indicator rotation (`.mg-table__th--sortable`), right-aligned tabular numeric cells (`.mg-table__td--numeric`), and monospace code/path cells (`.mg-table__td--code`).",
          "pr": 1157,
          "prRef": "unisdr/undrr-mangrove#1157",
          "prUrl": null
        },
        {
          "version": "2.3",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1105): Improved cell spacing with a distinct compact density and natural text wrapping. The React scroll option now creates a labelled, keyboard-focusable scroll region while preserving native table semantics; existing table modifiers remain supported.",
          "pr": 1105,
          "prRef": "unisdr/undrr-mangrove#1105",
          "prUrl": null
        },
        {
          "version": "2.2",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Made logical start alignment the table default and added explicit start, centre and end cell modifiers for textual, status and numeric columns across LTR and RTL layouts.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "2.1",
          "date": "2026-04-05",
          "notes": "(undrr-mangrove#907 (unisdr/undrr-mangrove#907)): Fixed `mg-table--striped` stripe color from `$mg-color-neutral-200` (`#b3b3b3`) to `$mg-color-neutral-25` (`#f2f2f2`); the previous value was far too dark",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.0",
          "date": "2025-06-12",
          "notes": "**Breaking:** All `table` elements must now include the `.mg-table` class for proper styling and functionality",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-tabs": {
      "name": "Tab",
      "entries": [
        {
          "version": "2.11.2",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1276): React tab panels now carry the opt-in `mg-content` boundary, so paragraph links in caller-supplied long-form panel HTML have the accessible underline treatment. The vanilla `mgTabs()` initializer leaves consumer markup unchanged; add `mg-content` to a hand-authored panel when it contains prose.",
          "pr": 1276,
          "prRef": "unisdr/undrr-mangrove#1276",
          "prUrl": null
        },
        {
          "version": "2.11.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1258): Documented the panel HTML trust contract. `TabPanel` renders a tab object's `data` string as HTML and does not sanitise it, which was already true and is unchanged here; no rendering behaviour was touched. Sanitising is the wrong answer for this prop rather than a missing one: a panel carries whole page sections, including the embeds and iframes a default DOMPurify pass would strip, so the filtering belongs in the CMS output filter that decides what an author may publish. The `data` propType said \"consumers must sanitise\" and nothing else did; the component now carries a block comment with the reasoning and a pointer at the render site, the MDX has a \"Panel HTML trust contract\" section beside the tab object table, and the AI manifest entry states it, scoped to the React path. Sibling components that do sanitise (`TextCta`, `Drawer`, the cards) are named so the difference reads as a decision. Also labels the component's other `dangerouslySetInnerHTML` (the no-data placeholder) as a fixed string literal rather than caller input, so an audit of the repo's injection sites does not have to work that out twice.",
          "pr": 1258,
          "prRef": "unisdr/undrr-mangrove#1258",
          "prUrl": null
        },
        {
          "version": "2.11",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1183): `mgTabs()` and `mgTabsRuntime()` accept an optional `{ signal }` third argument; aborting it destroys the tab sets that call initialized. For unisdr/undrr-mangrove#1180, a tab set removed from the page without `mgTabsDestroy()` is suspended: its window, font and `ResizeObserver` registrations are released when a resize observer notification, outside event or later `mgTabs()`/`mgTabsRuntime()` call finds it detached, and it resumes with its selection on interaction, window resize or hash change, or initialization if it is re-attached. A hash change made while a tab set was suspended is followed when it resumes or is re-initialized; after a pointer press or key press, that and any `stackOnMobile` layout switch wait until the interaction has been handled, and a tab the interaction selects wins. Resuming after an interaction also resumes other re-attached tab sets, including nested ones. While any tab set is initialized, the runtime keeps one window `resize` and one `hashchange` listener that check its tab sets. When the URL hash changed after a selection was retained, the hash now wins on re-initialization. `mgTabsDestroy(scope)` destroys every tab set in scope even if one throws, and an error in one tab set no longer stops `mgTabs()` from initializing others. The React wrapper passes a signal. New \"Removing tabs\" docs with a Drupal `detach` example.",
          "pr": 1183,
          "prRef": "unisdr/undrr-mangrove#1183",
          "prUrl": null
        },
        {
          "version": "2.10.2",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic tab links now render in Noto Sans Arabic instead of Noto Kufi Arabic. `.mg-tabs__link` reads the `ui` font role rather than naming a typeface, and the component's `:lang(ar)` block is gone. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "2.10.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a tab link whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "2.10",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1094): Refined horizontal tabs with centered groups, mobile scrolling, overflow fades, and RTL-aware keyboard behavior. Improved tablist/panel semantics and lifecycle cleanup; React updates preserve selection/disclosure/filter state. `stackOnMobile` remains available for disclosure-style mobile behavior.",
          "pr": 1094,
          "prRef": "unisdr/undrr-mangrove#1094",
          "prUrl": null,
          "details": [
            "Arabic typography (unisdr/undrr-mangrove#1089): Arabic tab links render in Noto Kufi Arabic. The `:lang(ar)` override on `.mg-tabs__link` points at `$mg-font-family-arabic-headings`, which changed from Dubai to Noto Kufi Arabic, so the override once again selects a different family from Arabic body text."
          ]
        },
        {
          "version": "2.9",
          "date": "2026-08-31",
          "notes": "(unisdr/undrr-mangrove#1086): Updated horizontal and stacked visual treatments (baseline/active state, disclosure rows, hit areas, and motion tokens) and removed per-language story duplication in favor of the global locale toolbar.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        },
        {
          "version": "2.8",
          "date": "2026-06-19",
          "notes": "(mangrove#1056 (unisdr/undrr-mangrove#1056)): Consolidated `filterPlaceholder` into `labels` with `DEFAULT_TAB_LABELS` (backwards compatible), and added shared locale label sets for ES/FR/JA/ZH/AR/RU.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.7",
          "date": "2026-04-13",
          "notes": "`.mg-tabs__link` now uses `$mg-font-family-headings` (Roboto Condensed) instead of `$mg-font-family` (Roboto). The `:lang(ar)` override switched from `$mg-font-family-arabic-body` (Dubai) to `$mg-font-family-arabic-headings` (then Noto Kufi Arabic) to match. Since 2.0.0-alpha.3 both Arabic tokens resolve to Dubai, so this override no longer changes the family.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.6",
          "date": "2026-03-27",
          "notes": "Added `data-mg-js-tabs-skip-auto-init` attribute, `type=\"module\"` self-init, single-element scope support (`mgTabs` wrapper already accepted scope; this release adds the manual-init check and single-element handling)",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.5",
          "date": "2026-03-20",
          "notes": "",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "Added `defaultOpen`, `singleOpen`, and `filterable` props (React) and corresponding `data-mg-js-tabs-*` attributes (vanilla JS)",
            "ARIA disclosure pattern for stacked tabs (`role=\"button\"`, `aria-expanded`, `aria-controls`, `hidden=\"until-found\"`)",
            "Keyboard: Space/Enter to toggle, Arrow Up/Down with wrapping, Home/End",
            "Focus indicator: explicit `:focus-visible` outline on tab triggers",
            "RTL: chevron and padding flipped for Arabic/RTL layouts",
            "**Migration:** trigger elements now have ID `{panelId}--trigger` instead of sharing the panel's ID. Update any CSS/JS that selects triggers by `#mg-tabs__section-{id}` to use `#mg-tabs__section-{id}--trigger` or the class `.mg-tabs__link`",
            "Exported `setDisclosureState()` and `mgTabsApplyStackedDefaults()` helpers"
          ]
        },
        {
          "version": "2.4.1",
          "date": "2026-03-02",
          "notes": "Rewrote usage docs with vanilla JS and React examples, CDN links, HTML markup, and API reference",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.4",
          "date": "2026-02-23",
          "notes": "Removed deprecated `Version1Tabs` story",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.3",
          "date": "2025-09-15",
          "notes": "New `mgTabsRuntime()` function; multi-tab-set support; default tab selection via `data-mg-js-tabs-default`; smooth scroll deep linking; stacked open state tracking",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.2",
          "date": "2025-02-05",
          "notes": "Append URL hash on clicking a tab",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.1",
          "date": "2024-11-07",
          "notes": "Allow stacked tabs to be more accordion-like",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "2.0",
          "date": "2024-10-18",
          "notes": "**Breaking:** Added `mg-tabs--stacked` and `mg-tabs--horizontal` variants; tab content now inside parent `li`",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-tag": {
      "name": "Tag",
      "entries": [
        {
          "version": "1.4.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `.mg-tag--accent` no longer sets a white label on the orange accent (2.95 resting, 2.65 on hover). The chip is `orange-50`, hover `orange-100`, with a `--mg-color-text` label at 15.7:1, and the Sendai orange moves to the border with a 4px leading edge. `--mg-color-tag-accent` keeps its value and now paints that border, held steady across states; `--mg-color-tag-accent--hover` is `orange-800`, lighter than the resting `orange-900`, so swapping it in on hover would have dropped the rule to 2.15 on the darker fill. The hover is the fill step and the label's underline. This is a visible change wherever `--accent` is used, in all five themes. A theme overriding `--tag-color-background-accent` with a dark fill should set `--mg-tag-foreground` too.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.3.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1198): Documented that `--accent` paints a white label on the orange accent, which no text colour clears at any size, and that the variant is decorative emphasis. No colour changed; removal is tracked in unisdr/undrr-mangrove#1196.",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null
        },
        {
          "version": "1.3.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1157): Added `.mg-tag--subtle` and `.mg-badge` for non-interactive secondary metadata, along with monospace uppercase format badge variants (`.mg-badge--code`, `.mg-tag--code`) for technical extensions (PDF, JSON, SVG).",
          "pr": 1157,
          "prRef": "unisdr/undrr-mangrove#1157",
          "prUrl": null
        },
        {
          "version": "1.2.0",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1105): Refreshed v2 spacing/geometry and aligned auto-styled tags with UI font/modifier rules. Added stronger focus/forced-colour handling for links, disabled hover treatment on static tags, and improved RTL/long-label wrapping behavior.",
          "pr": 1105,
          "prRef": "unisdr/undrr-mangrove#1105",
          "prUrl": null
        },
        {
          "version": "1.1.2",
          "date": "2026-09-09",
          "notes": "(unisdr/undrr-mangrove#1098): Arabic tag typography now follows the `ui` font role (`Noto Sans Arabic`), and the component-level `:lang(ar)` override was removed. Latin rendering is unchanged.",
          "pr": 1098,
          "prRef": "unisdr/undrr-mangrove#1098",
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to a tag whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2026-04-13",
          "notes": "`.mg-tag` now uses `$mg-font-family-headings` (Roboto Condensed) and a matching `:lang(ar)` override to `$mg-font-family-arabic-headings`. Brings tags into alignment with the wider compact-UI-label font treatment.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-02-26",
          "notes": "Initial release; replaces legacy `.st-tag--spl` class with Mangrove-native BEM pattern, hover state, `--secondary`/`--outline`/`--accent` variants, and `.mg-tag-container` for auto-styling children",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-typography": {
      "name": "Typography",
      "entries": [
        {
          "version": "2.4.1",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1273): Added `.mg-content` as the opt-in long-form and WYSIWYG typography boundary. Paragraph links inside it are underlined at rest; blockquote and cite styling now supports it alongside the existing `.mg-body` alias. Navigation, buttons and card links are not affected.",
          "pr": 1273,
          "prRef": "unisdr/undrr-mangrove#1273",
          "prUrl": null
        },
        {
          "version": "2.4.0",
          "date": "2026-09-16",
          "notes": "(unisdr/undrr-mangrove#1157): Added the `.mg-accordion` and `.mg-accordion--flush` patterns for grouped `<details>` disclosures with flush border dividers, animated rotating chevrons, and nested layer support.",
          "pr": 1157,
          "prRef": "unisdr/undrr-mangrove#1157",
          "prUrl": null
        },
        {
          "version": "2.3.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography now follows the computed language of each element rather than the page root, so a `lang=\"en\"` island inside an Arabic page renders in the Latin families.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "2.3",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Aligned the enhanced `.mg-details` disclosure marker with stacked tabs by replacing directional triangles with tokenised circular plus/minus controls, logical RTL positioning, a 48px summary target, and tactile hover/press states. Bare `<details>` retains its native marker.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Refined inline code as a compact theme-aware technical surface with interactive-colour tinting, shared radius semantics, a modern system-monospace stack, disabled ligatures, and coherent decoration when snippets wrap."
          ]
        },
        {
          "version": "1.2",
          "date": "2025-07-14",
          "notes": "(unisdr/undrr-mangrove#865):",
          "pr": 865,
          "prRef": "unisdr/undrr-mangrove#865",
          "prUrl": null,
          "details": [
            "Refactored `details`/`summary` styles to opt-in `mg-details` class; bare `<details>` receives a light baseline (pointer cursor, branded focus ring) without altering the native disclosure marker",
            "Replaced `outline: 0` on `summary` with explicit `:focus-visible` style for keyboard accessibility",
            "Improved `hr` with brand-blue top border and vertical breathing room",
            "Improved `dt` with bold weight for clear labelling without link ambiguity; added `dd + dt` group spacing",
            "Redesigned code block: removed left accent border in favour of clean full-radius dark block; added language badge (top-right) via `data-language` attribute or CSS `:has()` for syntax-highlighted output; added `figure.mg-code-block` + `figcaption` wrapper for Vercel-inspired filename headers"
          ]
        },
        {
          "version": "1.1",
          "date": "2026-03-05",
          "notes": "([web-backlog#2661](https://gitlab.com/undrr/web-backlog/-/issues/2661)):",
          "pr": null,
          "prRef": null,
          "prUrl": null,
          "details": [
            "Added optional `size` prop to Paragraph component for applying font-size utility classes",
            "Fixed Paragraph `label` prop support: callers passing `label` instead of `detail` now render correctly",
            "Added whitelist validation for `size` prop values"
          ]
        },
        {
          "version": "1.0",
          "date": null,
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-typography-links": {
      "name": "Link",
      "entries": [
        {
          "version": "1.0",
          "date": "2022-12-12",
          "notes": "Released component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "components-typography-typography-integration-example": {
      "name": "TypographyIntegrationExample",
      "entries": [
        {
          "version": "1.0.2",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1096): Centralised the Arabic tracking reset with script routing. Browser checks attached to the existing language-boundary example cover headings, component labels, table headers, generated code labels and nested language islands.",
          "pr": 1096,
          "prRef": "unisdr/undrr-mangrove#1096",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1276): The typography example opts into `mg-content`, so its prose links use the accessible inline-link treatment without making the boundary a global element default.",
          "pr": 1276,
          "prRef": "unisdr/undrr-mangrove#1276",
          "prUrl": null
        }
      ]
    },
    "components-user-feedback": {
      "name": "UserFeedback",
      "entries": [
        {
          "version": "1.0.2",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1252): The React example imports `@undrr/undrr-mangrove/components/UserFeedback.js` instead of the package root. The root has never resolved on any published version (the tarball ships no `dist/` and no `src/`, and its `main` named a file it does not contain), and 2.0.0 stops advertising it, so the subpath is the only form that works. The binding also changes: the published bundle exports this component as its **default**, with no named `UserFeedback`, so `import UserFeedback from …` is correct and the braced form would give `undefined`. Documentation only; the component is unchanged.",
          "pr": 1252,
          "prRef": "unisdr/undrr-mangrove#1252",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-08",
          "notes": "(unisdr/undrr-mangrove#1092): Arabic typography no longer applies to the feedback question whose own language is not Arabic.",
          "pr": 1092,
          "prRef": "unisdr/undrr-mangrove#1092",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086): Introduced the standalone pre-footer page-feedback component with progressive static HTML guidance, Drupal-friendly hydration, product-owned response callbacks, seven locales, RTL support, long-label handling, and accessible confirmation focus.",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null
        }
      ]
    },
    "patterns-article-story": {
      "name": "ArticleStory",
      "entries": [
        {
          "version": "1.0.2",
          "date": "2026-09-23",
          "notes": "The embedded video title reads \"This is how timely early warnings save lives, UNDRR\" instead of setting \"UNDRR\" off with an em dash, following the dash rules in the [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs#punctuation).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1276): The reading column opts into `mg-content`, the rich-content boundary. Paragraph links are therefore visibly underlined without changing the separately styled navigation and sharing links.",
          "pr": 1276,
          "prRef": "unisdr/undrr-mangrove#1276",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-10",
          "notes": "Initial article-story reference design with `imageProminence` and `heroImage` controls in English and Arabic, then iterated with split-hero support and shared-style extraction. ([undrr/web-backlog#3060](https://gitlab.com/undrr/web-backlog/-/work_items/3060))",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "patterns-content-hub": {
      "name": "ContentHub",
      "entries": [
        {
          "version": "1.2.0",
          "date": "2026-09-24",
          "notes": "`js/hub-header.js` enhances static hub header markup without React: it scrolls the marked section into view on load and once web fonts arrive (a later resize only updates the fades, so a reader's own scrolling is kept), drives the edge fades and, with `data-mg-hub-header-detect-current`, marks the current section from the URL. `HubHeader` runs the same script, so the scrolling and fade logic that lived in the ContentHub story now ships with the component.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.1",
          "date": "2026-09-23",
          "notes": "The hero video title reads \"This is how timely early warnings save lives, UNDRR\" instead of setting \"UNDRR\" off with an em dash, following the dash rules in the [Editorial manual](https://mangrove.undrr.org/?path=/docs/contributing-editorial-manual--docs#punctuation).",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1249): Fixes unisdr/undrr-mangrove#1196. `HubHeader`'s `surface=\"secondary\"` no longer retargets the bar and banner to the orange accent, which put every link and heading on it at 2.65. Both keep the theme's hero colour, and the accent is now a 4px full-bleed rule under the bar. `--mg-color-hero--secondary` is unchanged and drives that rule. This is a visible change wherever the surface is used, in all five themes.",
          "pr": 1249,
          "prRef": "unisdr/undrr-mangrove#1249",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-18",
          "notes": "(unisdr/undrr-mangrove#1198): Documented `HubHeader`'s `secondary` surface as a recorded contrast exception: white header text on the orange accent is 2.65, and no text colour clears AA on that hue. No colour changed; removal is tracked in unisdr/undrr-mangrove#1196.",
          "pr": 1198,
          "prRef": "unisdr/undrr-mangrove#1198",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-10",
          "notes": "Content hub pattern: hub home, detail and deeper pages in English and Arabic, under the standard UNDRR header and global navigation. (unisdr/undrr-mangrove#1112)",
          "pr": 1112,
          "prRef": "unisdr/undrr-mangrove#1112",
          "prUrl": null,
          "details": [
            "`HubHeader` owns hub identity and section navigation, with compact and expressive variants and four surface options, and ships in the stylesheet.",
            "Adds `mg-reading` as a distributed layout utility.",
            "Section links are marked as links rather than tabs, and only the exact current page is announced.",
            "Breadcrumbs appear only where they add ancestry the strip cannot show.",
            "Variations for a detached header and vertical route cards."
          ]
        }
      ]
    },
    "patterns-landing-pages": {
      "name": "LandingPage",
      "entries": [
        {
          "version": "1.0.3",
          "date": "2026-09-29",
          "notes": "([undrr/web-backlog#3109](https://gitlab.com/undrr/web-backlog/-/work_items/3109)): The structure now cites [Building landing pages](?path=/docs/patterns-building-landing-pages--docs), which replaces the Gutenberg content guide on assets.undrr.org, and a new section maps each example onto the Drupal Gutenberg blocks that build it. Documentation only.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.2",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1276): The report reading column opts into `mg-content`, so its inline report link is visibly distinguished without changing the navigation links around it.",
          "pr": 1276,
          "prRef": "unisdr/undrr-mangrove#1276",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1273): Added a real inline report link to the publication-page example, so the foundational link treatment can be reviewed in a realistic reading-column context.",
          "pr": 1273,
          "prRef": "unisdr/undrr-mangrove#1273",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-09",
          "notes": "Added topic, report and collection index landing page patterns with English and Arabic content. (unisdr/undrr-mangrove#1112)",
          "pr": 1112,
          "prRef": "unisdr/undrr-mangrove#1112",
          "prUrl": null
        }
      ]
    },
    "example-boilerplate-component": {
      "name": "Boilerplate",
      "entries": [
        {
          "version": "1.1",
          "date": "2026-03-05",
          "notes": "Added optional `href` prop for linked titles with focus-visible styles. Updated docs to match component library conventions.",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": null,
          "notes": "Initial boilerplate template",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "example-newsletter-promotion": {
      "name": "Newsletterpromotion",
      "entries": [
        {
          "version": "1.0.2",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1273): Lightened the promotion surface from blue-100 to blue-50. This keeps form help text at 4.86:1 contrast against the surface, meeting WCAG AA without changing the shared help-text colour used by forms on white.",
          "pr": 1273,
          "prRef": "unisdr/undrr-mangrove#1273",
          "prUrl": null
        },
        {
          "version": "1.0.1",
          "date": "2026-09-19",
          "notes": "(unisdr/undrr-mangrove#1244): Fixed the \"Compact signup\" story's confirmation-focus assertion, which could never have passed in a real browser. The confirmation heading appears in one commit and is focused by a passive effect in the next, so the DOM query resolved on the first of those and the focus check ran a frame early; it only ever passed under jsdom, where `act()` flushes both together. It now waits for focus. Stories only; the pattern is unchanged. The assertion matters because it is what holds the component to moving focus to the confirmation (a screen reader user otherwise gets no announcement that the sign-up succeeded), and it now runs in CI on every push.",
          "pr": 1244,
          "prRef": "unisdr/undrr-mangrove#1244",
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2026-09-09",
          "notes": "Added article placement, preferences, simulated failure and confirmation, and an interactive validation check. Consolidated visual CTA examples into the CTA component docs. ([web-backlog #2412](https://gitlab.com/undrr/web-backlog/-/work_items/2412))",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "example-page-template-example": {
      "name": "PageTemplateExample",
      "entries": [
        {
          "version": "1.4.1",
          "date": "2026-09-20",
          "notes": "(unisdr/undrr-mangrove#1276): The editorial content section opts into `mg-content`, so its paragraph links have the rich-content underline treatment without affecting the surrounding controls.",
          "pr": 1276,
          "prRef": "unisdr/undrr-mangrove#1276",
          "prUrl": null
        },
        {
          "version": "1.4",
          "date": "2026-09-01",
          "notes": "(unisdr/undrr-mangrove#1086):",
          "pr": 1086,
          "prRef": "unisdr/undrr-mangrove#1086",
          "prUrl": null,
          "details": [
            "Expanded the default story into a broad component showcase with editorial cards, horizontal collection, search, responsive table, subscription form, and closing CTA. Corrected the collection example to use `itemWidth` and a fixed scroll step so arrow controls move the internal viewport instead of widening the page.",
            "Added native disclosure examples and the complete Pager bar, plus a dedicated component laboratory covering deterministic syndicated search, responsive data, validation states, a styled FormGroup, radio and textarea controls, grouped actions and pagination.",
            "Applied the reusable FormAction composition to the subscription example so the email field and its key action share one continuous, responsive surface."
          ]
        },
        {
          "version": "1.3",
          "date": "2025-10-17",
          "notes": "Enabled footer",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.2",
          "date": "2025-09-15",
          "notes": "Enhanced example with table of contents",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.1",
          "date": "2025-06-04",
          "notes": "Integrated PageHeader component replacing placeholder section",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0",
          "date": "2024-10-21",
          "notes": "Initial page template with Hero, Tab, VerticalCard, and Footer components",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    },
    "example-typescript-component": {
      "name": "TypeScriptExampleComponent",
      "entries": [
        {
          "version": "1.1.1",
          "date": "2026-09-17",
          "notes": "(unisdr/undrr-mangrove#1193): The rendered button adds `max-width: 100%` alongside the `width` prop, so a width wider than the viewport no longer makes the page scroll horizontally.",
          "pr": 1193,
          "prRef": "unisdr/undrr-mangrove#1193",
          "prUrl": null
        },
        {
          "version": "1.1.0",
          "date": "2026-03-12",
          "notes": "Updated to CSF3 story format, added JSDoc comments, typed `type` prop as union, added explicit return type",
          "pr": null,
          "prRef": null,
          "prUrl": null
        },
        {
          "version": "1.0.0",
          "date": null,
          "notes": "Initial TypeScript demo component",
          "pr": null,
          "prRef": null,
          "prUrl": null
        }
      ]
    }
  }
}