Skip to content

feat(browser): meeting type badge + Type facet + place-led card headline#46

Merged
ronaldtse merged 6 commits into
mainfrom
feat/meeting-type-facet
Jul 22, 2026
Merged

feat(browser): meeting type badge + Type facet + place-led card headline#46
ronaldtse merged 6 commits into
mainfrom
feat/meeting-type-facet

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Summary

MeetingType is a first-class concept in the gem (Edoxen::Enums::MEETING_TYPE, 17 values) but the browser was rendering it raw as a plain string on the detail page and ignoring it entirely on cards + the search island. This PR promotes it to a proper i18n'd, filterable first-class facet.

Data

  • MeetingListItem carries type; MeetingListFacets exposes types[]. /data/meetings.json now includes the enum value per item.

Config + i18n

  • terminology.meetingTypes override (per-locale, e.g. { eng: { plenary: "Plénière CIML" } }).
  • Built-in meeting.type.* strings in src/i18n/ui.ts for all 17 values, English + French. Other locales fall back to English (existing pattern).
  • meetingTypeLabel(type, locale, terminology, customStrings) helper — override → i18n table → humanize fallback.

UI

  • MeetingCard meta row gains a small-caps type badge (label via helper).
  • MeetingDetail uses the helper instead of raw {meeting.type}.
  • Search island adds a Type facet between Year and Location, with hash round-trip (#types=plenary,working_group).
  • Meeting result rows in the island also gain the type badge for consistency with the card.

Card headline composition

Headline is composed from place (flag + "City, Country" or "🌐 Virtual") instead of the entity title. The title previously restated dates + committee the meta row already carried, producing 15-word headlines ("Resolutions of the plenary meeting of ISO/TC 184/SC 4, Stavanger, Norway, October 18, 2024 -- October 25, 2024"). Full title now rides as the link title attribute (tooltip) and remains the detail page h1 + JSON-LD + sitemap value. Falls back to title when no place data.

Drift guardrail

src/i18n/meeting-types.spec.ts asserts every canonical MeetingType value has an English meeting.type.* entry + a few required French translations + helper behavior (override, fallback, nullish). Catches the canonical "gem added an enum value, browser i18n didn't" drift.

Test plan

  • pnpm -F @edoxen/browser test — 177 passed (was 171; +6 new i18n drift/helper spec).
  • pnpm -F @edoxen/browser test:e2e — 51 passed.
  • pnpm -F @edoxen/browser typecheck — clean.
  • New e2e assertions: type badge visible on meetings index card, Type facet group visible, Plenary chip narrows to 3 fixture matches, hash round-trip verified.
  • Manual check: fixture meeting titles now appear as title attribute on the link, place is the visible headline.

Changeset

.changeset/meeting-card-place-headline.md — minor bump (new config knob + new UI feature).

MeetingType is a first-class concept in the gem (Edoxen::Enums::MEETING_TYPE, 17 values) but the browser was rendering it raw as a plain string on the detail page and ignoring it entirely on cards + the search island.

Data: MeetingListItem now carries type; MeetingListFacets exposes types[]. /data/meetings.json carries the enum value per item.

Config + i18n: terminology.meetingTypes override (per-locale). Built-in meeting.type.* strings for all 17 values, English + French. meetingTypeLabel(type, locale, terminology) helper resolves override -> i18n -> humanize.

UI: MeetingCard meta row gains a small-caps type badge (label via helper). MeetingDetail uses the helper instead of raw {meeting.type}. Search island adds a Type facet between Year and Location, with hash round-trip (#types=plenary,working_group). Meeting result rows in the island also gain the type badge for consistency with the card.

Card headline: composed from place (flag + City, Country or 🌐 Virtual) instead of the entity title. The title previously restated dates + committee the meta row already carried and produced 15-word headlines. Full title now rides as the link title attribute (tooltip) + remains the detail page h1 + JSON-LD + sitemap value. Falls back to title when no place data.

Drift guardrail: src/i18n/meeting-types.spec.ts asserts every canonical MeetingType value has an English meeting.type.* entry + a few required French translations + helper behavior (override, fallback, nullish).

171 unit + 51 e2e tests green (was 171 + 51, +6 new i18n spec tests).
The 'Powered by Edoxen' line now shows the Edoxen wordmark instead of plain text. Both theme variants are inlined (dark ink for light backgrounds, light ink for dark backgrounds) and CSS toggles visibility based on data-theme on <html>. No asset-path juggling — works on consumer basePaths out of the box.

Also updates the link target from edoxen.github.io to www.edoxen.org (the canonical site).

New EdoxenBrandMark.astro component wraps the dual SVGs with proper aria-label. e2e assertion locks in: link points to edoxen.org, light variant visible by default, dark variant hidden until theme flips.
The meetings JSON endpoint was projecting facetDecades, facetBodies, and facetCountries but not facetTypes — inconsistent with the FetchResponse interface declaration and with the new Type facet. The island currently recomputes facets client-side so it is not user-visible today, but the inconsistency is the kind of thing that bites later (e.g. when someone tries to optimize the island to use the precomputed arrays).

integration.ts now projects facetTypes alongside the other three. FetchResponse interface updated. e2e locks the wire shape: facetTypes === ['plenary'] for the fixture.
The /data/decisions.json and /data/meetings.json endpoints were projecting facetBodies/Kinds/Years/Actions/Statuses and facetDecades/Bodies/Countries/Types respectively — but the search-filter island never read them. It computes chip counts client-side from items[] (single source of truth, supports live-filter recount, no drift). The wire arrays were: incomplete (no counts, just unique values), inconsistent (decisions had different keys from meetings), drift bait (adding a model facet meant remembering three projection points), and actively misleading (empty facetBodies for a consumer without body_type set).

Removed from integration.ts, FetchResponse interface, and e2e. Kept the model-level DecisionListFacets and MeetingListFacets (still used for SSR: decade sections, About-page facts, JSON-LD). Added a comment on each clarifying the boundary.

Wire is now {items: [...]} for both endpoints.
README §Terminology gains a meetingTypes subsection showing the per-locale override API, the three-tier resolution order (override -> i18n -> humanize), an example for a committee styling meetings as 'Plénière CIML', and a pointer to the drift guardrail spec.

i18n-keys.yaml catalogs all 17 meeting.type.* keys plus meeting.virtual so consumers translating into other locales know exactly what to provide.

Per the user's directive: built-in translations stay English + French; other locales are consumer-config-driven and documented.
README meetingTypes example now shows both Simplified (全体会议) and Traditional (全體會議) Chinese blocks. Notes the current limitation: normalizeUiLocale truncates codes >3 chars so both currently collapse into the same 'zho' bucket — serving them as separate routed locales needs a small helper change.
@ronaldtse
ronaldtse merged commit b641115 into main Jul 22, 2026
2 checks passed
@ronaldtse
ronaldtse deleted the feat/meeting-type-facet branch July 22, 2026 10:32
ronaldtse added a commit that referenced this pull request Jul 22, 2026
…card headline

Consumes the changeset from PR #46. Bumps @edoxen/browser 0.8.0 -> 0.9.0 (minor: new config knob + new UI feature). Example apps bumped in lockstep.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant