feat(browser): meeting type badge + Type facet + place-led card headline#46
Merged
Conversation
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).
Merged
3 tasks
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.
3 tasks
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.
This was referenced Jul 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MeetingTypeis 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
MeetingListItemcarriestype;MeetingListFacetsexposestypes[]./data/meetings.jsonnow includes the enum value per item.Config + i18n
terminology.meetingTypesoverride (per-locale, e.g.{ eng: { plenary: "Plénière CIML" } }).meeting.type.*strings insrc/i18n/ui.tsfor 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
MeetingCardmeta row gains a small-caps type badge (label via helper).MeetingDetailuses the helper instead of raw{meeting.type}.#types=plenary,working_group).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
titleattribute (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.tsasserts every canonicalMeetingTypevalue has an Englishmeeting.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.titleattribute on the link, place is the visible headline.Changeset
.changeset/meeting-card-place-headline.md— minor bump (new config knob + new UI feature).