Skip to content

<goma-mission-details> ​

The full detail view for one mission: a banner + progress header, type-specific tabs (a casino mission shows Levels / Games / Rules; a sports mission shows Levels / Events / Rules; both shows all four), and the join-state variants (in-progress, not-joined, left-challenge). The mission can be host-fed directly, resolved by following a sibling <goma-missions-hub>'s selection, or (in rpc mode) fetched by id from the EveryMatrix Challenges backend, which also unlocks a lazy-loaded Games tab.

Quitting is confirmation-gated: the Quit this mission link (Rules tab) only emits goma:mission-optout. It does not forfeit anything itself. The host owns the "Are you sure?" confirmation and the actual forfeit call, then tells the widget the forfeit succeeded via el.showForfeited(), which snapshots the mission and switches the view to the "You've left this mission" state. The Quit link only renders once the player has joined (an active/running mission); on a not-joined or pending mission the Rules tab shows no Quit link. Quit is also hidden while the progress is Started (Status 0, claimed but not opted in yet), like the EverMatrix widget.

Element tag ​

html
<goma-mission-details></goma-mission-details>

Installation ​

bash
pnpm add @gomagaming/mission-details vue pinia vue-router vue-i18n @vueuse/core autobahn-browser

See Installation for the one-time .npmrc token setup. vue, pinia, vue-router, vue-i18n, @vueuse/core, and autobahn-browser are peerDependencies: installed once at the app level and deduped across every @gomagaming/* widget.

Configuration ​

js
const el = document.getElementById('detail')
el.config = {
  locale: 'en',
  // Only needed when dataSource is 'rpc'. See Data sources below.
  apiBaseUrl: 'https://sports-api.example.com',
  domainId: 123,
  playerSession: 'abc123',
}
KeyTypeRequiredDescription
localestringNoi18n locale (en / fr / pt). Defaults to 'en'.
apiBaseUrlstringFor dataSource: 'rpc'REST base URL for the EM Challenges API. Needed for both the by-id resolve (missionId set) and the Games-tab lazy fetch.
domainIdstring | numberFor dataSource: 'rpc' by-id resolveSent as an integer DomainId on the challenge REST calls. Not needed for the Games-tab fetch, which only sends ChallengeId.
playerSessionstringFor dataSource: 'rpc' by-id resolvePlayer session token forwarded as PlayerSession. Not needed for the Games-tab fetch.
themeobjectNo--goma-* token overrides: see Theming.
messagesobjectNoPer-locale i18n overrides: see i18n / message overrides.
debugbooleanNoVerbose console logging. Defaults to false.

Without apiBaseUrl (or with dataSource left at 'prop'), the by-id resolve and the Games-tab fetch both silently no-op: the widget stays on whatever mission the mission prop or the follow-select watcher supplies.

Data sources ​

Three independent ways the active mission gets into the widget, in the order the render logic actually resolves them (follow-select > by-id resolve > explicit prop):

  1. Direct prop: set mission to a Mission object (see the shape in the hub docs).
  2. Follow a hub (default): leave followMissionSelect on and supply a missions pool; when a sibling <goma-missions-hub> fires goma:mission-select, this widget looks up detail.missionId against the pool (falling back to detail.mission if the id isn't in the pool) and renders it. See Integrates with.
  3. rpc by id: set dataSource: 'rpc' and a missionId (the mission's ChallengeId), with config.apiBaseUrl / domainId / playerSession set and no mission prop or hub in sight. The widget resolves that one mission by passing the id as a server-side ChallengeId filter to the feed chosen by missionSource: 'my-challenges' (default) → GetChallengesInfo (no IsFutureAvailable flag, so the server returns the challenge whichever Active/Pending bucket it's in; if that flagless call comes back empty it retries once with IsFutureAvailable: true for the Pending bucket before concluding not-available); 'catalog' → DisplayableWidgetChallenges (the joinable offers). There is no cross-feed fallback: a catalog offer lives only in DisplayableWidgetChallenges, and a challenge the player forfeited (quit) is gone from GetChallengesInfo, so a 'my-challenges' miss shows the "no longer available" state instead of being resurrected as a claimable catalog offer. Set missionSource from the tab the details were opened from, and keep it in the URL (?source=catalog) so it survives a refresh/deep-link. This enables a refresh-safe standalone /…/:id details route. missionId is empty ('') by default, which falls back to the follow-select / explicit-prop behaviour above.

Independently of how the mission arrives, in dataSource: 'rpc' mode opening the Games tab lazy-loads that mission's game tiles from GetChallengeGames (once per challengeId/id, cached until it changes): this only needs config.apiBaseUrl. Games render as a 3-column grid of square, artwork-only thumbnails (the game name rides on each tile's aria-label / image alt); a Load more button reveals additional pages behind gamesPageSize.

Props / attributes ​

PropertyAttributeTypeDefaultDescription
missionmissionMission | nullnullThe mission to render (prop mode).
missionsmissionsMission[][]Pool the follow-select watcher resolves ids against.
followMissionSelectfollow-mission-selectbooleantrueFollow a sibling hub's goma:mission-select.
missionSelectSelectormission-select-selectorstring''Optional CSS selector to only follow a specific hub instance.
activeDetailTabactive-detail-tab'levels'|'games'|'rules'|'events''levels'Initial tab. Falls back to 'levels' if the value isn't offered for the mission's platform.
leftStateImageleft-state-imagestring''Host-injectable illustration for the "left this mission" card.
dataSourcedata-source'prop'|'rpc''prop''rpc' enables the by-id resolve (with missionId) and the Games-tab lazy fetch.
missionIdmission-idstring''The mission's ChallengeId. With dataSource: 'rpc', resolves that single mission: see Data sources.
missionSourcemission-source'my-challenges'|'catalog''my-challenges'Which feed the by-id resolve queries (rpc mode). 'my-challenges' → GetChallengesInfo (the player's enrolled/quit challenges); 'catalog' → DisplayableWidgetChallenges (the joinable offers). Set this from the tab the details were opened from: a catalog offer lives only in DisplayableWidgetChallenges, while a forfeited challenge is gone from GetChallengesInfo and must show the not-available state rather than being resurrected as claimable. Each source queries exactly one feed (no cross-fallback). Persist it in the URL (e.g. ?source=catalog) so it survives a refresh/deep-link.
gamesPageSizegames-page-sizenumber9Games-tab page size (rpc mode). 0 shows all fetched games at once.
colorModecolor-mode'dark'|'light''dark'Toggles the root dark class so the widget can follow the host app's light/dark theme.

Appearance (styling object) ​

appearance carries structure only: radii and other mode-agnostic single values that map to one --goma-* CSS custom property each, written as an inline style on the widget's host element. Set it via el.appearance = {...} (the JS property) or the appearance="..." HTML attribute (a JSON string, parsed on write). Unset keys leave the current default untouched: an empty {} (the default) reproduces the widget's current look exactly.

Colours are not in appearance. Every mission colour (footer CTA button colours, the reward/requirement glyphs, and the Levels-tab level colours) moved to config.theme because colours need light/dark pairs. Set them via el.theme = {...}; see the Missions section of the Theming guide for the full colour-token list.

<goma-mission-details> shares the same structure map as <goma-missions-hub>, so you can pass a host's single appearance object to both, but each widget only applies the keys that map onto surfaces it renders. The structure keys that take effect on <goma-mission-details> are:

KeyCSS variableAffects
buttonRadius--goma-radiusButtonFooter CTA button corner radius.
levelCardRadius--goma-missionLevelCardRadiusLevels-tab level-card corner radius.
gamesColumns--goma-gamesColumnsColumns of the Games tab grid on <goma-mission-details> (tiles and skeletons). Default 3.
gameTileRatio--goma-gameTileRatioAspect ratio of a game tile, for example 168 / 146. Default 1 / 1.
detailTabsFullWidth--goma-detailTabsFullWidthtrue removes the 16px side padding of the detail tab strip. Default false.
detailTabIndicatorHeight--goma-detailTabIndicatorHeightHeight of the ACTIVE detail tab underline only. Inactive tabs draw the 1px missionDetailTabTrack line. Default 2px.
fontFamily--goma-fontFamilyFont of the widget text. Default the widget font. inherit takes the host font.

No-op structure keys on this widget

The shared map also carries cardRadius and the pill keys (pillRadius, pillActiveBorderWidth, pillFontWeight, pillActiveFontWeight, pillHeight, pillPaddingX). These have no effect on <goma-mission-details>. Its type tabs (Levels / Games / Rules / Events) are an underline strip, not pills, and its non-level cards use a fixed corner radius. Those keys only style <goma-missions-hub> (pill tab strip, cardRadius mission cards). Passing them here is harmless: they're simply ignored. buttonRadius and levelCardRadius are the geometry knobs that apply to this widget.

Level colours still apply here

Even though the pill/cardRadius structure keys are no-ops, this widget's Levels tab does read the mission colour tokens: missionRewardIcon, missionRequirementIcon, missionLevelBadgeBg / …BorderColor / …Text, missionLevelActiveBorderStart / …End, and missionLevelCompletedBorderColor. Those are set through config.theme, not appearance. See the Theming guide.

Mode-agnostic

appearance layers on top of whichever dataSource is active ('prop' or 'rpc'). It only ever touches presentation, never data fetching.

Quit-challenge modal is host-driven

The quit-challenge confirmation modal (Rules tab → "Quit this challenge") is rendered by the host, not the widget. The widget only emits goma:mission-optout once the host confirms the quit. Neither appearance nor theme has any effect on that modal; style it yourself.

Worked example: a light-theme host. Structure goes on appearance, colours go on theme. This is the same pair a host would hand <goma-missions-hub>; on <goma-mission-details> only the button + level keys take effect (the pill / cardRadius structure keys are ignored here, see the note above):

js
// structure (mode-agnostic single values)
el.appearance = {
  buttonRadius: '9999px',
  levelCardRadius: '20px',
  // ignored here, but style <goma-missions-hub> when the object is shared:
  cardRadius: '0.75rem',
  pillRadius: '9999px',
}

// colours (need light/dark parity → config.theme)
el.theme = {
  missionButtonBg: '#40b840',
  missionButtonText: '#ffffff',
  missionButtonBorderColor: '#40b840',
  missionRewardIcon: '#40b840',
  missionRequirementIcon: '#4a90e2',
}

Events ​

All events are composed + bubbling.

EventdetailWhen
goma:mission-cta{ missionId, challengeId, internalUserId, action }Footer CTA pressed, or the "left this mission" card's own action link tapped. action is derived from join state ('start' / 'pause' / 'resume' for an enrolled mission, 'discover' once forfeited) or, for a not-yet-joined mission, an explicit mission.cta override (e.g. 'deposit' / 'launch' / 'claim'). missionId is the per-enrollment progress id; challengeId + internalUserId support the write endpoints.
goma:mission-optout{ missionId, challengeId }Quit confirmed by the host: actually fired the moment the Rules tab's "Quit this mission" link is tapped (the widget has no confirmation modal of its own; see the note above). challengeId is what a forfeit call needs.
goma:mission-game-select{ missionId, game }A game tile (Games tab) tapped. In rpc mode game is the full raw casino game object (GameLaunchUrl, GameSlug, GameName, GameId, CasinoGameId, Vendor, …) so the host can launch it directly; in prop mode it's whatever object the host put in mission.games.
goma:mission-back{ missionId }Header back chevron pressed.
goma:retry{}User taps the retry button on the render-error banner.
ready{}Mounted and rendered.
goma:error{ message, code, component? }Fires on an error raised in the widget's own outermost component (its setup, its render, its lifecycle hooks or its watchers), where code is the error's name, e.g. 'TypeError' ('RUNTIME_ERROR' when it has none). Errors raised inside the widget's child components do not reach this event: the in-shadow error boundary catches those and shows the retry banner instead, emitting only goma:retry. The connection-layer path (code: 'API_INIT_ERROR') does not exist here: Missions is host-fed / rpc-fetched over REST-shaped data, not a WAMP sports-API connection.

Instant updates and confirmation ​

The EverMatrix (EM) read feeds lag a successful write by about 1.1 s. A refetch right after a write still returns the old data. So the widget can show the new state at once, and then confirm it with fresh data.

Methods that create an instant change. The host calls them right after the write returns Success: true (or after an SSE message):

  • markClaimed(challengeId): hides the claimed card in the catalog.
  • setProgressStatus(progressId, status): shows 'in-progress', 'paused' or 'forfeited'.
  • setChallengeAvailable(challengeId, available): shows a challenge as Unavailable or normal again.

How long it lives. An instant change lives for 10 s at most.

When it ends early. It ends as soon as fetched data agrees with it. A stale refetch in between does not undo it.

What happens at the end. If the 10 s pass and the data never agreed, the widget does NOT fall back to its cached data. The instant change stays in force and the open challenge refetches ONE more time. When that refetch settles (it worked or it failed), the instant change ends and the fetched data decides. The card never shows the old state in between. Several changes that end together give one refetch, not many. A hard cap protects you: if the refetch has not settled 10 s after the 10 s, the instant change ends anyway. It also ends at once when the player or the config changes.

A refresh during a fetch. A refresh() that comes while a fetch is running is not answered by that older fetch, because it may have started before the write. The widget runs exactly one more fetch after it. Several refreshes in the meantime share that one extra fetch.

What the host must do. Call refresh() about 1.5 s after a write or an SSE message. That is just after the EM lag. Restart the 1.5 s timer on each new event, so a burst of events gives one refresh. Add a max wait: fire the refresh at the latest 4 s after the FIRST event of the burst, so a steady stream of events cannot delay it forever.

Timeline example.

TimeWhat happens
0 sThe player taps Pause. The host sends the write and calls setProgressStatus(id, 'paused'). The card shows Suspended at once.
1.5 sThe host calls refresh().
1.5 s, server agreesThe fetched item has the Paused status. The instant change ends. Done.
1.5 s, server does not agree yetThe card keeps Suspended.
10 sThe widget refetches once. The card keeps Suspended while it runs.
When that refetch settles (20 s at the latest)The instant change ends. The fetched data decides the card.

Methods ​

MethodDescription
refresh()Re-resolves the mission by id in dataSource: 'rpc' mode (with missionId set). Call it after a successful host-side CTA / quit mutation so the header, progress and join-state reflect the new server state without a reload. No-op in 'prop' / follow-select mode.
showForfeited()One-shot signal telling the widget a host-driven forfeit succeeded. Snapshots the current mission with joinState: 'forfeited' so the "You've left this mission" state renders even after the next reconcile refetch drops the mission from the feed.
markClaimed(challengeId)Hides the catalog (Challenges tab) card whose challengeId equals the argument (compared as strings). Call it right after the host's claim write returns Success: true, because EverMatrix read feeds lag a write by about 1.1 s and a refetch would still list the challenge. The override ends when a fetched catalog no longer lists the challenge, or after 10 s, whichever comes first. A stale refetch in between does not bring the card back.
setProgressStatus(progressId, status)Shows every card or details view whose mission id (the EM progress id, compared as strings) equals progressId as if EM had returned Status InProgress ('in-progress', 1), Paused ('paused', 7) or Forfeited ('forfeited', 5). The CTA, the suspended tag and, after an opt-in from Started (0), the current level, progress and unlocked ladder all follow. Call it right after ChangeProgressStatus returns Success: true. The override ends when a fetched item carries the same Status, or after 10 s, whichever comes first. A stale refetch in between does not undo it. A 'forfeited' progress leaves the Active and Pending lists (the Finished tab is history data, fetched separately), and the override also ends when the progress is gone from the fetched feeds. In <goma-mission-details>, 'forfeited' on the shown mission shows the same "You've left this Challenge" state as showForfeited(). In 'prop' mode only progressStatus, the suspended tag and the CTA change: the level row stays as the host fed it.
setChallengeAvailable(challengeId, available)false: every item whose ChallengeId equals the argument (compared as strings) gets ChallengeStatus Paused (2), as when the operator disables the challenge in UBO: the Unavailable tag, the greyed card and the "View Details" CTA. It covers the catalog, My Challenges and the details view. true: every such item gets ChallengeStatus Enabled (1), even when a refetch right after the SSE still returns Paused (2). Each override ends when the fetched items agree (all unavailable for false, all Enabled for true), when the challenge is gone from the fetched feeds, or after 10 s, whichever comes first. In 'prop' mode false sets the status to unavailable, the label and a fed cta; true clears an unavailable status.

markClaimed, setProgressStatus and setChallengeAvailable exist on <goma-missions-hub> and <goma-mission-details> and write to one shared store, so a call through either element shows in both when they share the same @gomagaming/sports-domain copy. A CDN page needs exactly one import map entry for it: see Why an import map is needed at all.

ChallengeStatus rule (challenge level, on catalog and progress items). The operator sets ChallengeStatus in UBO. Any value other than Enabled (1), meaning Draft (0), Paused (2), Closed (3) or Deleted (4), or IsAvailable === false, makes the challenge Unavailable. Unavailable wins over every player progress Status: tag "Unavailable", greyed and inert card, CTA "View Details" only, no progress bar, no current-level row, levels greyed, and no action in the details footer. A player pause is a different thing: only Status Paused (7) on an Enabled challenge renders as Suspended, with the Resume CTA.

Integrates with ​

Listens for goma:mission-select (gated by followMissionSelect, default true, optionally scoped to one hub via missionSelectSelector) and shows the selected mission. This is the pairing the widget is built around: <goma-missions-hub> upstream fires goma:mission-select when a mission card is tapped, and this widget resolves the id against its missions pool (or falls back to the event's inline mission payload) with no extra glue code. See Cross-widget wiring for the full payload contract and the emitter/listener matrix.

goma:mission-cta, goma:mission-optout, goma:mission-game-select, and goma:mission-back are host-only today: no other shipped widget listens for them. The widget reads and writes no shared-storage envelope (no betslip, favourites, or session state).

Empty / loading / error states ​

  • No mission resolved: before any of the three data paths produce a mission (and once resolution has settled with nothing found), the widget shows a centred missions_no_levels message.
  • Resolving by id (rpc), while dataSource: 'rpc' and missionId is set but no mission has arrived yet (and no by-id error and no forfeited snapshot), a MissionDetailsSkeleton renders in place of the header, tabs and levels list.
  • Games tab loading (rpc): until GetChallengeGames has returned for THIS mission, the Games tab shows only the skeleton grid, even when the mission object carries games. After the response it shows exactly the fetched games, or the empty state. Switching to another mission clears the old games at once, so they never show under the new mission. In 'prop' mode the games the host passes show directly.
  • Per-tab empty states: a mission with no levels / games / events shows that tab's own "nothing to show" message (missions_no_levels / missions_no_games / missions_no_events).
  • Status badge: a Suspended (blue) or Unavailable (grey) mission shows that pill top-left on the header banner, the same ColorTag and text as the list card. It replaces the "Ends in" countdown. Forfeited still wins. The footer CTA rules do not change.
  • Not-joined / not-started mission, until the player joins (the mission is enrolled and running), the Levels tab renders the whole ladder locked, there is no active Level 1; every level shows its locked state. Joining activates the current level.
  • Left this mission: after el.showForfeited() (or a resolved mission whose joinState is already 'forfeited'), the Levels tab renders the "You've left this mission" card instead of the levels list, with its own CTA that also emits goma:mission-cta (action: 'discover').
  • Render error: an onErrorCaptured boundary catches a Vue render error, shows a red inline banner (ERROR: <message>) with a Retry button (emits goma:retry) and a dismiss ×, and auto-clears after 8000 ms even without a retry tap.

Theming ​

The core --goma-* tokens this widget's own components read (the curated playground picker set lives in packages/mission-details/src/themeTokens.js; the Levels-tab colour tokens below are additional): see Theming for the complete reference. Every one is a colour set via config.theme:

  • Backgrounds: --goma-backgroundPrimary (page), --goma-backgroundSecondary (header/back-bar), --goma-backgroundCards (level cards), --goma-backgroundBorder (empty-state border, "no levels/games/events" and "left this mission"), --goma-separatorLine (tab underline, dividers, header progress-bar track).
  • Text: --goma-textHeadlinePrimary (headings), --goma-textPrimary (primary text / active tab), --goma-textSecondary (secondary text / inactive tab).
  • Icons: --goma-iconPrimary (back chevron / carets).
  • Highlights: --goma-highlightPrimary (not-joined unlock prompt), --goma-highlightPrimaryContrast (focus ring), --goma-highlightSecondary (Terms & Conditions link; completed-level label and border; header progress-bar fill end), --goma-highlightTertiary (Read-full-terms link; header progress-bar fill start; active per-level mini-bar gradient start).
  • Buttons, --goma-allWhite (the header's "ends-in" countdown status pill, background + border, via the shared ColorTag component; its label text is --goma-allDark, not this token. Once the mission is forfeited the banner shows a "Forfeited" pill in --goma-alertError instead, and the countdown is not rendered at all), --goma-allDark (header progress-bar center label; also the "ends-in" pill's text, above), --goma-missionButtonBg / --goma-missionButtonText (solid footer CTA fill/text for join actions), --goma-missionButtonBorderColor (outline footer CTA border/text for resume/view/pause actions, and the Games-tab "Load more" button).
  • Alerts: --goma-alertError ("Quit this mission" link; render-error banner).
  • Levels-tab level colours: dedicated tokens the level cards read, each set via config.theme: --goma-missionLevelBadgeBg / --goma-missionLevelBadgeBorderColor / --goma-missionLevelBadgeText (level-number progress badge, also the header progress-bar's current-level node, since the header reuses the same progress component as <goma-missions-hub>'s cards, not a dedicated bar), --goma-missionLevelActiveBorderStart / --goma-missionLevelActiveBorderEnd (active card gradient border), --goma-missionLevelCompletedBorderColor (completed card border), and --goma-missionRewardIcon / --goma-missionRequirementIcon (reward/ requirement glyphs). Their defaults resolve to the highlight greens above. The card corner radius is the one structure knob here: --goma-missionLevelCardRadius (default 20px), set via appearance.levelCardRadius.
  • Sticky footer placement: --goma-stickyFooterOffset and --goma-stickyFooterZIndex. Neither has a default, so the footer CTA sits flush to the bottom of the viewport with automatic stacking unless you say otherwise. See Keeping the CTA clear of your own bottom chrome below.

Keeping the CTA clear of your own bottom chrome ​

The footer CTA is position: sticky; bottom: 0, and that 0 resolves against the viewport, not against your layout. If your page carries its own fixed bottom chrome (a nav bar, a tab strip, a cookie banner) the CTA scrolls underneath it, because the widget renders in a shadow root and has no way to measure something outside itself.

Tell it how much room to leave:

css
goma-mission-details {
  --goma-stickyFooterOffset: 56px; /* your nav bar's height */
}

If your chrome also sets a z-index, lift the footer above it:

css
goma-mission-details {
  --goma-stickyFooterOffset: 56px;
  --goma-stickyFooterZIndex: 20;   /* above your nav bar's z-index */
}

Both also work through config.theme (stickyFooterOffset / stickyFooterZIndex) if you drive theming from JavaScript.

js
el.theme = {
  backgroundPrimary: '#03061b',
  backgroundCards: '#1f2147',
  highlightPrimary: '#ff6600',
}

Role tokens and layout settings ​

The rule. Every colored element reads the --goma-<name> token whose name is the design color variable on that layer. The host maps its own palette by identity: app <name> goes into --goma-<name>. A host never changes its app palette to fit the widget. Each color token has a -dark twin (--goma-<name>-dark) and the widget paints it as text-<name> dark:text-<name>-dark (or bg- or border-).

Role tokens. Where designs use different variables for the same element, the widget reads one role token and each app fills it from its own variable. With no value set, a role token keeps the color the element had before (the Default column), so an app that sends nothing sees no change from the role tokens. The exceptions are the five private tokens with a new default and the elements listed in "Elements that changed token" below: those follow the design variable, so a host whose palette gives them other values sees a change. A role token that already existed gets a -dark twin that defaults to its light token (var(--goma-<name>)), so a host that sets only the light value still paints both modes.

Role tokenElementsDefaultWhen to set it
pillActiveTextSelected sub-tab pill label#ffffffSend it when the design draws this element with a variable other than the default.
pillActiveBgSelected sub-tab pill fill#2a2d60Send it when the design draws this element with a variable other than the default.
missionButtonBgSolid CTA fill (hub card, details footer)var(--goma-allWhite)Send it when the design draws this element with a variable other than the default.
missionButtonTextSolid CTA labelvar(--goma-allDark)Send it when the design draws this element with a variable other than the default.
missionButtonBorderColorOutline CTA border, and the Games "Load more" border and labelvar(--goma-allWhite)Send it when the design draws this element with a variable other than the default.
missionCtaOutlineText (new)Outline CTA labelvar(--goma-missionButtonBorderColor)Send it when the design draws this element with a variable other than the default.
missionDetailTabIndicator (new)Active underline of the detail tabsvar(--goma-textPrimary)Send it when the design draws this element with a variable other than the default.
missionDetailTabTrack (new)Inactive track: the 1px line under the detail tab strip. Inactive tabs draw no line of their ownvar(--goma-separatorLine)Send transparent when the design draws no line for inactive tabs.
missionUnlockTextUnlock or description line on a not-joined cardvar(--goma-highlightTertiary)Send it when the design draws this element with a variable other than the default.
missionValueTextValue text on a card (min deposit, levels, requirement, reward)var(--goma-highlightTertiary)Send it when the design draws this element with a variable other than the default.
missionLevelBadgeTextCurrent level number on the card progress barvar(--goma-textPrimary)Send it when the design draws this element with a variable other than the default.
missionTagBorderColorBorder of the Casino or Sports pillvar(--goma-highlightTertiary)Send it when the design draws this element with a variable other than the default.
missionHistoryWonColorFinished tab: TOTAL WON border and value, completed level circle, reward and statusvar(--goma-highlightTertiary)Send it when the design draws this element with a variable other than the default.
missionLevelActiveBorderStart / missionLevelActiveBorderEndTwo stops of the active level card gradient borderhighlightTertiary / highlightSecondarySend two variables for a two-color border, or the same variable twice for one color.
missionRulesTermsLink (new)"Read full terms" link on the Rules tabvar(--goma-highlightTertiary)Send it when the design draws this element with a variable other than the default.
missionProgressFillStart (new)From stop of the progress fill gradient (card progress bar, level row bar). The to stop stays highlightSecondaryvar(--goma-highlightTertiary)Send it when the design draws this element with a variable other than the default.
missionTermsLink (new)Terms & Conditions link on the challenge card, the header and the finished cardvar(--goma-highlightSecondary)Send it when the design draws this element with a variable other than the default.
missionHeaderUnlockText (new)Not-joined unlock prompt in the challenge headervar(--goma-highlightPrimary)Send it when the design draws this element with a variable other than the default.
missionDetailTabActiveText (new)Label of the active detail tabvar(--goma-textPrimary)Send it when the design draws this element with a variable other than the default.
missionDetailsBg (new)Page surface behind the details page (the sticky footer bar keeps backgroundPrimary)var(--goma-backgroundPrimary)Send it when the design draws this element with a variable other than the default.
missionEventsPercent (new)Contribution percent on the Events tab below 100%var(--goma-highlightPrimary)Send it when the design draws this element with a variable other than the default.
missionEventsPercentComplete (new)Contribution percent on the Events tab at 100% or morevar(--goma-highlightTertiary)Send it when the design draws this element with a variable other than the default.
missionLevelCompletedText (new)"Completed" label on a completed level cardvar(--goma-highlightSecondary)Send the variable the design uses for this label.

New -dark twins. pillText, pillActiveBorderColor, missionLevelBadgeBg, missionLevelBadgeBorderColor and missionLevelCompletedBorderColor keep their defaults. Each has a new -dark twin that defaults to the light token, so a host that sets only the light token changes dark mode too.

Platform pill gradient. Two new private tokens paint the Casino or Sports pill.

Private tokenElementDefault (light)Was
missionTagBgStart / missionTagBgEnd (new)Casino or Sports pill gradient, left to rightvar(--goma-backgroundSecondary) / var(--goma-backgroundSecondary) (a flat pill)(flat missionTagBg, default backgroundSecondary)

missionTagBg has no default any more. The pill paints the missionTagBgStart to missionTagBgEnd gradient. Both ends default to backgroundSecondary, which is the flat fill the pill had before, so a host that sends nothing sees the same pill. A host that wants a gradient sends missionTagBgStart (for example backgroundPrimary) and missionTagBgEnd (for example backgroundSecondary). A host that still sets missionTagBg (or missionTagBg-dark) gets that flat color instead, so existing hosts keep working.

Layout settings. Five appearance keys cover small per-design layout differences. Each default equals the previous look. A boolean is written to the CSS variable as 1 or 0.

KeyCSS variableDefaultWhen to set it
gamesColumns--goma-gamesColumns3Send 2 for a two-column tile grid.
gameTileRatio--goma-gameTileRatio1 / 1Send the tile aspect ratio the design draws, for example 168 / 146.
detailTabsFullWidth--goma-detailTabsFullWidthfalseSend true when the design runs the tab strip edge to edge.
detailTabIndicatorHeight--goma-detailTabIndicatorHeight2pxSend the underline height the design draws, for example 6px.
fontFamily--goma-fontFamilythe widget fontSend inherit to use the host font.

gamesColumns and gameTileRatio style the Games tab (tiles and loading skeletons). detailTabsFullWidth: true removes the 16px side padding of the detail tab strip. detailTabIndicatorHeight sets the active underline height. The tab label does not move when the height changes. fontFamily: 'inherit' makes the widget use the host font.

Accepted values. A value the browser cannot parse does not fall back to the widget default. The browser drops the declaration and uses ITS default for that CSS property (for example no grid columns, or the inherited font). Send valid values only.

KeyAccepted value
gamesColumnsA positive integer, for example 2.
gameTileRatioA CSS aspect-ratio, for example 1 / 1 or 168 / 146. Not 16:9.
detailTabsFullWidthA boolean, true or false. Not the string 'false'.
detailTabIndicatorHeightA CSS length, for example 2px.
fontFamilyA CSS font-family list, or inherit.

A --goma-fontFamily that the host sets through theme is kept when appearance does not set fontFamily. The older radius, size and spacing keys take a CSS length. The font weight keys take a CSS font weight.

Elements that changed token. The table lists the element, the old token and the new token. Both the light class and the dark: class changed.

ComponentElementOldNew
Hub pageSticky top tab band fillbackgroundCardsbackgroundTertiary
Hub pageChallenges and My Challenges headingstextHeadlinePrimarytextPrimary
Top tabsStrip underline, inactive underlineseparatorLine, transparentseparatorLineSecondary
Top tabsInactive labeltextSecondarytextPrimary
Sub-tab pillsResting pill fillpillspillBg (default pills)
Details pageBack label and chevrontextHeadlinePrimarytextPrimary
Challenger card, header, finished cardTerms & Conditions linkhighlightSecondarymissionTermsLink (default highlightSecondary)
Challenger headerNot-joined unlock prompthighlightPrimarymissionHeaderUnlockText (default highlightPrimary)
Details pagePage surfacebackgroundPrimarymissionDetailsBg (default backgroundPrimary)
Detail tabsActive labeltextPrimarymissionDetailTabActiveText (default textPrimary)
Detail tabsInactive underline, inactive labeltransparent, textSecondaryseparatorLine, textPrimary
Challenger cardTitletextHeadlinePrimarytextPrimary
Card progress barTrackseparatorLinescroll
Card progress barCentre labelallDarkhighlightSecondaryContrast
Card progress barNext level node fillseparatorLinebackgroundBorder
Platform pillLabelallWhitetextPrimary
Status tag, ends-inFill, label, borderallWhite, allDark, allWhitehighlightPrimaryContrast, highlightSecondaryContrast, shadow
Status tag, suspendedLabelallDarkhighlightSecondaryContrast
Status tag, finishedFill, labelhighlightTertiary, allWhitehighlightSecondary, highlightSecondaryContrast
Status tag, forfeitFill, labelalertError, allWhitemyTicketsLost, highlightPrimaryContrast
Status tag, expiredLabelallWhitehighlightPrimaryContrast
Status tag, newLabel, borderallWhite, allWhitehighlightPrimaryContrast, shadow
Challenge headerTitletextHeadlinePrimarytextPrimary
Empty stateTitle; card variant border; text-only filltextHeadlinePrimary; separatorLine; backgroundCardstextPrimary; backgroundBorder; backgroundTertiary
Empty stateText-only CTA linkhighlightSecondaryhighlightTertiary
Rules panelContribution type and rule valuestextSecondarytextPrimary
Events panelContribution percent below 100% / at 100% or morehighlightPrimary / highlightTertiarymissionEventsPercent (default highlightPrimary) / missionEventsPercentComplete (default highlightTertiary)
History cardOuter border; titleseparatorLine; textHeadlinePrimarybackgroundBorder; textPrimary
Level cardActive card fill; "Completed" label; progress trackbackgroundCards; highlightSecondary; separatorLinebackgroundSecondary; missionLevelCompletedText (default highlightSecondary); scroll

i18n / message overrides ​

Keys this widget's own components reference (bundled en defaults shown):

  • back ("Back"), retry ("Retry"), terms_conditions ("Terms & Conditions")
  • missions_levels_tab / missions_games_tab / missions_rules_tab / missions_events_tab: tab labels
  • missions_level ("Level"), missions_completed ("Completed")
  • missions_end_date ("End date"), missions_ends_in ("Ends in")
  • missions_forfeit_header: shown in place of the progress bar once forfeited
  • missions_contribution / missions_contribution_type / missions_contribution_rule: Rules and Events tab copy
  • missions_odds_rules ("Odds:"): Rules-tab heading for a sports challenge's odds limits. One line per limited bet type: missions_odds_single, missions_odds_multiple, missions_odds_multiple_selection, missions_odds_system, missions_odds_system_selection (each takes a {range}). The range itself is missions_odds_between ("min. {min} · max. {max}"), missions_odds_min or missions_odds_max when only one bound is set
  • missions_read_full_terms, missions_quit ("Quit this mission")
  • missions_no_levels / missions_no_games / missions_no_events: per-tab and top-level empty states
  • missions_load_more: Games-tab pagination button
  • missions_left_title / missions_left_subtitle: the "left this mission" card
  • missions_cta_start / _pause / _resume / _deposit / _discover / _launch / _claim / _view: footer CTA labels, keyed off the derived action

Pass config.messages to override:

js
el.config = {
  locale: 'en',
  messages: {
    en: { missions_quit: 'Leave this mission' },
  },
}

Embedding examples ​

Vanilla: paired with the hub ​

html
<goma-missions-hub id="hub"></goma-missions-hub>
<goma-mission-details id="detail"></goma-mission-details>
<script type="module">
  import '@gomagaming/missions-hub'
  import '@gomagaming/mission-details'

  const missions = [/* Mission[] */]
  hub.missions = missions
  detail.missions = missions   // lets the follow-select watcher resolve ids
</script>

React ​

jsx
import '@gomagaming/mission-details'

<goma-mission-details
  ref={(el) => { if (el) el.mission = activeMission }}
  onGoma:mission-cta={(e) => handleCta(e.detail)}
/>

Vue ​

vue
<script setup>
import '@gomagaming/mission-details'
import { ref } from 'vue'
const mission = ref(/* Mission */)
</script>

<template>
  <goma-mission-details :mission.prop="mission" @goma:mission-back="goBack" />
</template>

Accessibility ​

  • The detail tab strip is an ARIA tablist; each tab is a role="tab" with aria-selected.
  • The header's in-progress bar is a role="progressbar" with aria-valuenow / aria-valuemin / aria-valuemax and an aria-label carrying the centre status text.
  • The back button carries an aria-label; the render-error banner is role="alert".
  • Each Games-tab tile is a <button> with aria-label and title set to the game name, and an alt-tagged, lazy-loaded image.
  • Interactive elements (back button, tabs, footer CTA, quit link, retry button, game tiles, load-more button) all carry a visible focus-visible ring keyed to --goma-highlightPrimaryContrast.
  • Decorative glyphs (the lock icon on a locked level, the requirement sword icon) are aria-hidden="true".

Integration Caveats ​

  • SSR/webview: importing this package outside a browser previously threw immediately; fixed with a guard on the shared element factory. This widget is a direct target of an upcoming host integration, so this fix matters in particular.

  • Tokens/Theming: corrected the same stale --goma-buttonActiveHoverTertiary doc claim as missions-hub (shared header component), plus a second stale --goma-allWhite claim found while fixing it.

  • Initialization: autobahn-browser is a required peer dependency even though this widget's rpc mode is plain REST; documented why, matching highlight-card/pros-choice's phrasing.

  • Initialization: this widget shares @gomagaming/core's i18n singleton with every other widget on the page; mounting two widgets with different locale values on one page is not currently isolated per instance (a known constraint, not fixed in this pass).

  • Configuring before the definition loads. Assigning config (or any property) while the element is still an unknown element is safe: the value is replayed through the widget when it upgrades. That matters when loading from the CDN, whose loader imports widget modules asynchronously, so your script usually runs first. On release sets at 0.34.0 and earlier the value was silently discarded and the widget mounted with no configuration and a clean console: there, await window.__gomaCdn.ready first, or configure through attributes. See Setting a property before the widget's definition has loaded.

See Integration Caveats for the cross-widget picture.

Changelog ​

Current release: 0.37.11. Every GomaPulse package shares one version number and they are published together, so a release that changes nothing here still bumps this widget. Only the releases that changed it are listed below.

Every release that changed this widget, newest first. The latest is open; the rest expand.

0.37.4
    • <goma-mission-details>: the Rules tab of a sports challenge now lists its odds limits under an "Odds:" heading, one line per bet type (for example "Single bets: min. 2 · max. 3"). The lines come from the challenge's SportSettings odds ranges (total odds for single, multiple and system bets, and odds per selection for multiple and system bets); a range with no bound set is not shown.
    • @gomagaming/sports-domain: challenge missions now carry rules.odds, an array of localized lines (empty when the challenge sets no odds limits). Odds values are formatted for the widget's locale, so fr and pt read 1,5.
    • @gomagaming/core: new missions_odds_* i18n keys in en, fr and pt.
0.37.2
  • The Challenges widgets (<goma-missions-hub> and <goma-mission-details>) now read Figma color tokens, as a light class and a dark: class. New optional role tokens, each with a -dark twin: pillBg, missionCtaOutlineText, missionDetailTabIndicator, missionDetailTabTrack, missionTopTabTrack (default separatorLine), missionTopTabInactiveLine (default transparent), missionRulesTermsLink, missionProgressFillStart, missionEventsPercent, missionEventsPercentComplete, missionLevelCompletedText (default highlightSecondary), missionTermsLink, missionHeaderUnlockText, missionDetailTabActiveText, missionHubBg, missionDetailsBg, missionPillsBarBg, missionTagBgStart and missionTagBgEnd. New optional appearance keys: gamesColumns, gameTileRatio, detailTabsFullWidth, detailTabIndicatorHeight, topTabIndicatorHeight, pillsBarPaddingBottom and fontFamily. A fontFamily that the host sets through theme is no longer removed when appearance does not set it.

    New element methods on both elements, for an optimistic update right after a write: markClaimed, setProgressStatus and setChallengeAvailable. New @gomagaming/sports-domain exports: markClaimed, setProgressStatus, setChallengeAvailable, isClaimed, getStatusOverride, applyStatusToMission, applyAvailabilityToMission, isForfeitedOverride, filterClaimed and OVERRIDE_TTL_MS. An override belongs to one player session: a new player session drops all of them.

    Data fixes: one request per tab, a page size of 20, a Suspended or Unavailable badge on the details header, Quit hidden for a Started challenge, a games skeleton until the games of this challenge arrive, and the forfeited snapshot and lookup now use the ChallengeId. In rpc mode goma:missions-count now sends total: null, and active, pending and finished stay null until that sub-tab is loaded.

    No breaking changes: no prop, event or token was removed. Every new token and setting defaults to today's look, and the defaults of the existing private tokens are unchanged. Each of these five private tokens now has a -dark twin that defaults to its light value, so a host that sets only the light value still paints both modes: pillText, pillActiveBorderColor, missionLevelBadgeBg, missionLevelBadgeBorderColor and missionLevelCompletedBorderColor.

    About 40 elements now read a different existing palette token, so that they follow the Figma variable on that layer. A host whose palette gives those tokens different values sees those colors change. With the default palette and no new token, these colors change:

    • Inactive top tab label: #939dff to white (textPrimary).
    • Card progress track and level progress track: #2a2d60 to #393d83 (scroll).
    • Active level card fill: #1f2147 to #161732 (backgroundSecondary).
    • ColorTag ends-in and new border: white to #03061b (shadow).
    • ColorTag finished: green fill with white text to #404cff fill (highlightSecondary) with #03061b text (highlightSecondaryContrast).
    • ColorTag forfeit fill: #f9554d to #60111b (myTicketsLost).
    • Rules tab contribution text: #939dff to white (textPrimary).
    • Inactive detail tab label: #939dff to white (textPrimary).
    • Empty state text link: #404cff to #01b72e (highlightTertiary).
0.36.1
  • Playground manifests (@gomagaming/<widget>/playground) now offer a control for every setting each widget really has. Settings that cannot be a form field (raw provider data, read-only state mirrors, props a widget never reads) are recorded with a reason in the repo instead of being silently missing.

    Controls that never reached the widget are fixed:

    • The betslips' placeBetHeadersJson / placeBetBodyJson / quickAddAmountsJson are now placeBetHeaders / placeBetBody / quickAddAmounts.
    • The time-filter and filters-settings ticks editors set ticks.
    • live-chat's layout control writes position, the config key the element reads.
    • The sports-navigation widgets drop the unread sportsJson.
    • JSON-typed defaults are real objects and arrays instead of serialized strings. The top-competitions highlightedImages string default was indexed character by character and requested bad image URLs on every load. The missions-hub emptyStateImages illustrations now show.

    No widget runtime behaviour changes.

0.36.0
  • Moved every shared UI component out of @gomagaming/sports-domain into a new @gomagaming/ui package, which depends on it:

    • The game cards: RegularOdds and its live/prelive/outright/market variants, SkeletonCard and CountryFlag. Used by every widget that lists events.

    • The betslip family: BetslipPanel and its selection, summary and bonus pieces, and the GomaDialog modal shell. Used by the betslip floating and sidebar widgets.

    • The missions family: the missions cards, level rows, tags, empty state and mission-detail panels. Used by the missions hub and mission details widgets.

    • The promo-card family: the promo card shell, hero, competition row, kickoff time, CTA link and skeleton. Used by the highlight card and pros' choice widgets.

    MyBetsPanel and MyBetsCard moved into @gomagaming/my-bets, their only consumer. The betslip widgets keep showing them through <goma-my-bets>.

    No widget's public surface, markup or behaviour changes, and each widget renders the same components as before. @gomagaming/ui is installed automatically with the widgets that use it.

    @gomagaming/sports-domain no longer exports any component. It keeps the stores, API clients, composables and the promo-card auto-mode layer, and additionally exports useBetslipSystem, useBonusSessionGate, bonusRowTitle, convertOdd, formatRatePercent, formattedDate, formattedTime and resolveDateLocale. A host importing one of the moved components from @gomagaming/sports-domain directly should import it from @gomagaming/ui (or, for the MyBets pieces, use <goma-my-bets>) instead.

0.34.0
  • Stop a region list fetched for the wrong sport from overwriting the right one, and parse numeric strings assigned to numeric widget properties instead of silently replacing them with the widget's default

    A <goma-region-filter> configured for any sport other than Football issued two location requests instead of one: its own, correctly scoped to the configured sport, and a second one (scoped to Football) that nothing had asked for. Both wrote the same place, so the pills the widget rendered were whichever response happened to come back last. The same race reached <goma-competitions-list>, which groups its competitions by the same region data.

    The unrequested request came from the per-widget boot routine, which fetched locations with no sport at all and so fell back to sport 1. Locations are scoped to a sport on the server, and boot has no sport to scope them by, so it no longer fetches them: the only readers of that data are the widgets above, and each of them already fetches it for the sport it is showing.

    That removes the collision at its source. As a second line of defence, a locations payload now records which sport it was fetched for, and a widget ignores one belonging to a sport it is not showing. This matters whenever two scoped fetches can overlap (switching sport while a request is still in flight, for instance) and it is deliberately narrow: a payload with no recorded sport is still rendered, so the guard can only ever suppress data it can positively identify as belonging to somewhere else.

    Separately, every numeric property on every widget (sportId, skeletonCount, pageSize, spaceBetween, autoplayInterval and the rest) used to accept a JavaScript number and nothing else. A numeric string was not parsed; it was discarded and quietly replaced by the property's default value. el.sportId = '8' therefore left the widget on sport 1 and fetched the wrong data, with nothing logged to say so. Three ordinary integration paths hand over a string: a router parameter, a value read out of a CMS or JSON configuration, and a framework re-render that stringifies its props. Numeric strings are now parsed. Values that are not a finite number and not a numeric string (null, undefined, '', booleans, objects, NaN, Infinity) still fall back to the default, as before. HTML attributes (sport-id="8") and the config object were never affected by this and are unchanged.

0.33.0
  • Missions: make locked and completed level cards readable, show a Forfeited tag once a mission is left, draw the lock and trophy from the design vectors, and let a host lift the detail CTA above its own bottom chrome

    The Levels tab told its three level states apart twice over: once through the card treatment (gradient border, solid border, no border) and again by muting the copy to textSecondary on everything that was not the active card. Locked cards then dimmed the whole card to 40% on top of the already-muted grey, which is why the text measured rgb(170,170,170) against a design that asks for white. Completed cards had the opposite half of the same bug: they inherited the muted typography but were never dimmed at all, so they rendered at full opacity.

    Typography is now identical in all three states and opacity alone separates them: active at full strength, completed at 60%, locked at 40%. Expect the completed card to shift less than the diff suggests: brighter text at 60% lands close to muted text at 100%, which is the intended result, not a fix that failed to apply.

    The lock glyph and the Total Won trophy were both hand-drawn approximations rather than the design vectors: the lock had rounded corners and no keyhole, the trophy a different silhouette entirely. Both are now the exported vectors, inheriting their colour through currentColor exactly as before.

    A forfeited mission kept counting down in the detail header. The banner tag was gated only on there being a countdown, and the clock keeps running on a challenge the player has left, so the header advertised time remaining beside a body that had already switched to the left-challenge state. Forfeiting now outranks the countdown and the banner shows a "Forfeited" tag instead.

    The "you've left this mission" panel painted itself with backgroundOdds: the odds-BUTTON colour, not a surface. Themes whose odds buttons are light rendered a white slab on a dark page, and because the panel's heading is textPrimary white, the heading disappeared into it entirely. It now paints the page background, matching the design, with its border, corner radius and padding unchanged.

    The selected tab pill's ring can now be painted with a gradient. It was built as a border, which only accepts a colour, so a host could choose the ring's colour and thickness but never make it a sweep, and the tab strip is styled differently by each app that embeds it, so that was not the widget's call to make. The ring is now a background layer and --goma-pillActiveRing sets what it is painted with, accepting any CSS <image>. Left unset it paints a flat sheet of --goma-pillActiveBorderColor, which renders exactly like the solid border it replaced, so no existing host moves. Repeat your first colour as the last stop on a conic gradient, or the 360°→0° wrap leaves a seam.

    This also removes a .tab-pill-active rule and a --goma-tabPillActive token that nothing referenced. The rule carried a hardcoded gradient no widget ever applied, and its .dark variant could not have matched under any circumstances, since dark mode swaps the --goma-* values rather than adding a .dark class.

    The mission-detail footer CTA now renders its label in uppercase, matching the design.

    The detail view's sticky footer CTA resolves bottom: 0 against the viewport, so a host page with its own fixed bottom chrome (a nav bar, a cookie bar) had the CTA scrolling underneath it, with no way for the widget to measure something outside its own shadow root. Two new tokens let the host declare it: --goma-stickyFooterOffset reserves the space and --goma-stickyFooterZIndex settles the stacking order. Neither has a default, so a host that sets neither sees no change.

0.32.0
  • Widgets no longer dispatch a bare error event alongside goma:error.

    Every factory-built widget's app-level Vue error handler dispatched two events for the same failure: goma:error, and an un-namespaced error. The bare one is gone; goma:error is unchanged and still carries the same { message, code, component? } detail.

    Like every GomaPulse event it was composed and bubbling, so it did not stop at the widget. It crossed the shadow root, crossed the document, and arrived at whatever the host had on window.addEventListener('error', …): as a CustomEvent with message, error and filename all undefined, since those are ErrorEvent fields a CustomEvent does not have. A host with error reporting installed received a malformed error record it could neither read nor attribute.

    That had been written off as harmless on the grounds that the handler behind it could never fire. It can. Vue begins its errorCaptured walk at the throwing component's parent, so the onErrorCaptured boundary each widget installs in its own root component cannot see that component's own errors, and being the root, there is nothing above it to catch them either. Anything raised in a widget's root scope (its setup, its render, its lifecycle hooks, its watchers) skips the boundary and reaches the app-level handler every time. Only errors from child components are caught by the boundary, which is what it is for: those show the in-shadow banner and emit goma:retry.

    Hosts listening for goma:error need no change. A host listening for bare error on a widget element should move to goma:error; it was documented as unreachable, so in practice it delivered nothing until now.

0.26.0
  • Stop publishing source maps and package source.

    dist/*.js.map was emitted by the shared Vite preset and swept into every tarball by files: ["dist/", "src/"], with the original source inlined in sourcesContent. src/ shipped the same source again, in the clear. Both are one URL away from a browser once a package is served from a CDN, so the preset now builds with sourcemap: false and files no longer carries src/.

    @gomagaming/core keeps the two paths its export map actually points at: src/styles/main.css and src/types/index.d.ts. Its tarball drops from 1741 files to 576, which also brings it back under the 900-file limit a per-request unpacker can handle.

    Deep imports of @gomagaming/<pkg>/src/* from an installed package no longer resolve. Nothing documented used them; imports inside the monorepo are unaffected.

0.25.2
  • Mission details: specify which feed the by-id resolve queries (fixes catalog details).

    <goma-mission-details> gains a missionSource property ('my-challenges' default | 'catalog') so the by-id resolve queries exactly the feed the details were opened from: 'catalog' → DisplayableWidgetChallenges (a Défis offer lives only there), 'my-challenges' → GetChallengesInfo. This fixes a catalog challenge being unopenable after the previous "GetChallengesInfo-only" change, without reintroducing a blind fallback: a quit challenge stays "no longer available" instead of resurfacing as claimable. Persist it in the URL (?source=catalog) so it survives a refresh/deep-link.

    Also hardens the by-id resolve:

    • Pending safety net: the my-challenges lookup does the flagless GetChallengesInfo and, if empty, retries once with IsFutureAvailable: true (the Pending bucket) before concluding not-available. Never touches the catalog.
    • Config-not-ready timeout (15s): a missionId set with a config that never supplies a usable session no longer skeletons forever; it surfaces a retryable error.
    • Error state: a by-id fetch failure renders a retryable error (missions_detail_error, en/fr/pt) instead of masquerading as the empty "no levels" state.
0.25.1
  • Mission details: resolve a challenge by id from GetChallengesInfo only.

    <goma-mission-details> (rpc, by-id) now reads the challenge from a single GetChallengesInfo call, server-filtered to the one ChallengeId, with no IsFutureAvailable flag so the server returns it whichever bucket (Active/Pending) it's in, and no DisplayableWidgetChallenges fallback. A forfeited (quit) challenge drops out of GetChallengesInfo; the old catalog fallback resurrected it as a claimable offer, so quitting then re-opening the details showed a "claim" button. When GetChallengesInfo returns nothing the view now shows a "This challenge is no longer available." state (new i18n key missions_challenge_unavailable, en/fr/pt) instead.

    Also fixes a latent infinite-skeleton: the by-id resolve now exposes notFound so the details stop the loading skeleton when the challenge genuinely isn't returned. createChallengeApi().getChallengesInfo omits IsFutureAvailable from the request body when the caller doesn't pass it (the hub feeds still pass false/true for the Active/Pending split).

0.25.0
  • Missions widgets: new appearance styling object (el.appearance = { … } / appearance attribute) that maps curated keys to --goma-* CSS variables for pill/card/button geometry and pill/button/reward-icon colours. Defaults reproduce the current look; a light-theme host can now reach full design parity.
0.18.0
  • <goma-mission-details> now emits the full casino game object when a Games-tab tile is tapped. goma:mission-game-select carries { missionId, game }, where game is the whole casino game (GameLaunchUrl, GameSlug, GameName, GameId, CasinoGameId, Vendor, …): the challenge-games mapper preserves the raw object so the host can launch the game directly from the payload. The Games-tab "Load more" button adopts the mission secondary-button style (white outline pill) instead of the highlight-primary outline.
0.17.0
  • Challenges CTA write-path support: goma:mission-cta now carries challengeId + internalUserId (alongside missionId) and goma:mission-optout carries challengeId, so a host can drive the EM Challenges write endpoints (ChangeProgressStatus needs the progress id + internal user id; ChallengeForfeit needs the challenge id). A new public refresh() element method (backed by a reactive nonce in core) re-runs the widget's rpc data source on demand (<goma-missions-hub> re-fetches the enrolled + catalog feeds; <goma-mission-details> re-resolves the challenge by id) so a successful host-side mutation reflects in the UI without a reload. No-op in prop mode.

  • Mission details: missionId (the challenge's ChallengeId) resolves a single challenge in rpc mode via a server-side ChallengeId filter on the EM list feeds (GetChallengesInfo → DisplayableWidgetChallenges), so one request returns the full challenge shell. Enabling a refresh-safe standalone details route. The catalog (not-joined) resolution now also carries levels + rules so those details aren't empty. The Games tab paginates behind a "Load more" button (gamesPageSize, default 10) since GetChallengeGames returns up to 100 games in one call. <goma-missions-hub>'s goma:mission-select now also carries challengeId, the stable id a deep-link route resolves by.

  • Challenges (rpc mode): GetChallengesInfo is now the sole feed. DisplayableWidgetChallenges is no longer used. Each item carries two status fields: the player's progress Status and the challenge-lifecycle ChallengeStatus. The feed is split client-side: live not-started offers (progress Status 0 + ChallengeStatus Enabled) fill the Challenges catalog, every enrolled challenge (Status ≠ 0) fills My Challenges, and the by-id details route resolves a challenge (catalog or enrolled) from the same feed.

    <goma-mission-details>: the detail tabs now vary by challenge type. Casino shows Levels/Games/Rules, sports shows Levels/Events/Rules, both shows all four. The footer CTA is aligned to the hub card buttons (4px rounded, primary-accent outline / green solid). Quitting now opens an "Are you sure?" confirmation modal (native top-layer dialog with a backdrop fade + panel scale animation) before emitting goma:mission-optout; the Quit affordance itself is a red link.

    <goma-missions-hub>: a new stickyTop config (px) pins the Challenges | My Challenges tab strip below the host's fixed chrome as the list scrolls.

  • Challenges data + details overhaul.

    • Feeds split by source. The Challenges catalog now comes from DisplayableWidgetChallenges (the not-yet-joined offers); My Challenges Active / Pending come from GetChallengesInfo, called twice, the IsFutureAvailable flag decides the sub-tab (false → Active, true → Pending), and Finished from PlayerHistory. The three feeds are independent, so a catalog or history failure leaves the rest intact.
    • CTA driven by the challenge's triggers. A catalog card's button and action derive from the challenge's enabled Triggers (priority Deposit > Game Launch > Claim → deposit / launch / claim), with a new launch label.
    • Rules & Events. The Rules panel now renders the challenge description, the rules text before the T&C link, and the contribution rule; a sports challenge additionally renders an Events tab from the challenge's per-event contribution rules.
    • Games tab is a 3-column grid of square, artwork-only thumbnails (the game name rides on each tile's aria-label / image alt); page size is now 9 (three full rows) and the "Load more" button matches the app's secondary-button styling.
    • Left / forfeited state. Quitting a challenge shows an optimistic left-state card and the details header drops the end date while keeping the T&C link; PlayerHistory level completion is read from each level's IsCompleted so a forfeited challenge no longer reports as completed.
  • Missions cards get their production visual design. The hub's challenger card now renders faithful New, Deposit (not-joined), Suspended, Unavailable and Pending states: gold-bordered platform pill, status/countdown badges, an on-card level progress bar, and solid/outline CTAs. The Finished-tab history card renders Finished, Forfeit and Expired states with a Total-Won summary and a per-level timeline (completed / forfeited markers). <goma-mission-details> gets its redesigned header (banner countdown, level progress bar, end date). The EveryMatrix Challenges mappers derive the corresponding mission fields (status, level completion, totals) so the same designs render from live data.

  • Missions widgets gain an opt-in dataSource: 'rpc' mode that fetches live EveryMatrix Challenges data (read path). <goma-missions-hub> renders Active/Pending from DisplayableWidgetChallenges and Finished from PlayerHistory; <goma-mission-details> lazy-loads its Games tab. The default 'prop' (host-fed) path is unchanged.

  • Missions Finished-tab history card fixes: the forfeited outcome now uses the shared alertError token consistently. The Forfeit pill, the per-level ✕ circle, and the "Forfeited" status text all paint --goma-alertError (replacing a maroon myTicketsLost pill and a hardcoded #be170f red). The token default is corrected to its design-system value #f9554d. The "X/Y levels" count now counts only levels the player actually completed, so a fully-forfeited challenge reads 0/4 instead of 4/4.

  • Missions start/pause/resume CTA now follows the real ChangeProgressStatus state machine. The mapper exposes the raw per-player progressStatus, and both the hub card CTA (deriveCtaAction) and the detail footer drive off it: a Started(0) progress shows Start (it must be activated before it can be paused, deactivating it is rejected server-side as "wrong status"), an InProgress(1) one shows Pause, and a Paused(7) one shows Resume. Previously every enrolled active-tab challenge offered Pause, which failed for freshly-enrolled Started(0) challenges.

0.10.0
  • Add the Missions widgets (Part 1, UI only, host-fed mock data):

    • <goma-missions-hub>: tabbed Active / Pending / Finished mission cards with per-tab empty states whose illustrations are host-injectable. Emits goma:mission-select, goma:mission-cta, goma:missions-offers.
    • <goma-mission-details>: banner + progress header, Levels / Games / Rules / Events tabs, and join-state variants (in-progress, not-joined, left-challenge). Follows goma:mission-select from a sibling hub; emits goma:mission-cta, goma:mission-optout, goma:mission-game-select, goma:mission-back.
    • sports-domain gains the shared Missions presentational components, useMissions helpers, and a useCountdown composable.
    • core locales gain the missions_* i18n keys (en / fr / pt).