Skip to content

Declarative Widget Host vs Imperative Widget SDK

Technical Story: Dashboard widgets — host orchestration vs module-driven lifecycle

Dashboard widgets are federated UI fragments (cash alerts, variance KPIs, etc.) rendered on the host home surface. rwe-core and whisker-host treat widget lifecycle very differently.

rwe-core — host orchestrates, modules signal

Section titled “rwe-core — host orchestrates, modules signal”
  • widget-registry.ts lists allowed widgets; WidgetHostProvider loads remotes and mounts them hidden.
  • Modules call host.notifyReady(id, payload?) when they have data worth showing; host.notifyHide(id) when dismissed or stale.
  • Host exposes window.rweWidgetHost / useWidgetHost() from retailUXShell/widgetHostSdk.
  • Host tracks per-operator dismiss via widgetsLastHiddenByUser[operatorId][widgetId] (see 0005).
  • The host cares what widgets are doing — readiness, hide, analytics.

whisker-host today — declarative manifest, host-agnostic render

Section titled “whisker-host today — declarative manifest, host-agnostic render”
  • Modules declare widgets[] in the manifest (dimensions, type, allowedPersonas).
  • Host renders widget slots from manifest + dashboard layout store; no notifyReady / notifyHide SDK.
  • Widget federation loads like any other remote; visibility is driven by dashboard composition, not module callbacks.
  • Host does not participate in widget readiness negotiation.

Both can federate the same React components. The difference is who owns visibility logic and how tightly the host couples to widget runtime behaviour.


  • Host simplicity — Platform should not need widget-specific orchestration for every module.
  • Module autonomy — Widgets with conditional data (e.g. “show only when variance > threshold”) need a lifecycle hook.
  • Migration — Existing rwe-core widgets already call notifyReady.
  • Analytics — Centralised trackEvent through host vs module-direct NewRelic.
  • Shared devices — Dismiss semantics must tie to operator, not device.

Option A — Keep rwe-core imperative SDK on whisker-host

Section titled “Option A — Keep rwe-core imperative SDK on whisker-host”

Port WidgetHostProvider, notifyReady / notifyHide, getLastHiddenByUser, trackEvent to whisker-host (or WCL).

Option B — Pure declarative widgets (Whisker today)

Section titled “Option B — Pure declarative widgets (Whisker today)”

Manifest + dashboard layout only; modules render whenever mounted; no readiness protocol.

Option C — Optional widget lifecycle protocol in WCL

Section titled “Option C — Optional widget lifecycle protocol in WCL”

Host exposes a thin, optional SDK (rwe-core-compatible surface); modules that need readiness use it; simple widgets ignore it.

Option D — Host-agnostic widgets only — modules handle visibility internally

Section titled “Option D — Host-agnostic widgets only — modules handle visibility internally”

No host SDK; each widget self-hides inside its bundle; host always mounts manifest widgets.


Option A — Full imperative SDK (rwe-core parity)

Section titled “Option A — Full imperative SDK (rwe-core parity)”
  • Good: drop-in for migrated widgets; proven dismiss + analytics pattern; host controls suggestion stack vs persistent lanes.
  • Bad: host remains coupled to widget runtime; every widget team learns SDK; host must maintain registry + lifecycle state machine.

Option B — Pure declarative (status quo)

Section titled “Option B — Pure declarative (status quo)”
  • Good: host stays dumb; manifest is sole contract; fewer moving parts.
  • Bad: no standard for “show when data ready”; migrated widgets must rewrite; empty/loading widgets may flash on dashboard.
Section titled “Option C — Optional lifecycle protocol (recommended)”
  • Good: simple widgets stay declarative; complex widgets opt into notifyReady / notifyHide; migration path for rwe-core; host can noop SDK until module calls it.
  • Bad: two widget classes to document; risk of inconsistent UX if teams skip SDK when they should use it.

Option D — Module-internal visibility only

Section titled “Option D — Module-internal visibility only”
  • Good: maximum host agnosticism.
  • Bad: duplicated hide/show logic; no central dismiss persistence; poor fit for suggestion-stack UX rwe-core built.

Chosen direction: Option C — optional widget lifecycle protocol, implemented in or alongside WCL, with host-agnostic rendering as the default.

Principles:

  1. Default: whisker-host renders widgets declared in manifest + dashboard layout without requiring imperative callbacks.
  2. Opt-in: modules that need conditional display or athlete dismiss use an rwe-core-compatible SDK surface (notifyReady, notifyHide, getLastHiddenByUser, trackEvent).
  3. Migration: rwe-core widgets may keep call sites during transition; host provides compatible implementation.
  4. Not chosen: requiring every widget to register with host orchestration (Option A) or forbidding lifecycle hooks entirely (Option B/D).

Status: SDK not shipped in whisker-host today — this ADR records target model; tracked as improvement #16 in Risks & improvements.


  • New simple widgets avoid SDK ceremony.
  • Complex widgets and rwe-core migrations have a defined protocol.
  • Host stays agnostic for the common case; engaged only when modules opt in.
  • Two widget integration paths to document and test.
  • Until SDK ships, rwe-core widget migrations remain blocked or require rewrites.
  • Per-operator dismiss must be implemented with SDK (ties to 0005).

  1. Spec optional SDK in WCL with rwe-core-compatible method signatures.
  2. Ship host-side provider that mirrors rwe-core semantics for opt-in widgets only.
  3. Document in module-kit / module-author skill: when declarative is enough vs when to call notifyReady.
  4. Do not block Whisker adoption on full SDK — block widget migration from rwe-core until SDK + operator dismiss exist.