# `dashboard-setup` → `SetupWidget` migration plan

How the production **Focus Widget** steps area
(`wix-private/dashboard-setup` → `packages/dashboard-setup-app/src/components/dashboard-widget/steps`)
eventually consumes cairo's `@wix/patterns` **`SetupWidget`** instead of its own
step components + data context.

> **Living doc.** Update the readiness checklist (§4) and "Done so far" (§5) as
> each `SetupWidget` feature lands. Gap *status* is tracked separately in
> [`dashboard-setup-integration-gaps.md`](./dashboard-setup-integration-gaps.md);
> this plan references gaps (Gx) but does not duplicate their status.
>
> **Working reference:** `packages/example-bm/src/pages/setup-widget/index.tsx` —
> a BM page that renders `SetupWidget` against the **real Focus Widget backend**
> (`/_serverless/dashboard-setup/steps-simple`), calling it from `fetchData`
> (no host `ResolveFn`). Shows the `FocusDataSimple` → `SetupWidgetData` adapter end to end.

---

## 1. Target shape

`dashboard-setup` keeps ownership of **data, platform actions, real-time, and
the app shell**; `SetupWidget` owns the **steps UI + per-step lifecycle**.

```tsx
// in dashboard-setup (consumer)
const setup = useSetupWidget({
  fetchData: adaptFocusWidgetData, // Step[] → SetupWidgetData (see §3)
});

// keep the raw steps to resolve platform actions by id
const rawById = useRawStepsById(setup.steps);

useDuplexerRefresh(() => setup.refresh());           // server push → refresh (G1)

return (
  <SetupWidget
    state={setup}
    renderStepContent={(s) => <ExtensionHost step={rawById[s.id]}
        onActionCompleted={() => setup.setStepStatus(s.id, 'completed')} />}
    onStepClick={(s) => handleAction(rawById[s.id], rawById[s.id].cta)}
    onCompletedStepClick={(s) => handleAction(rawById[s.id], rawById[s.id].completedCta)}
  />
);
```

This **replaces**: `DashboardWidgetDataProvider` (steps state, `focusedStepId`/
`getNextStep`, `setStepCompleted`, `onDashboardWidgetDataChange`,
`allStepsCompleted`), `steps.component`, `step.container`, `step.component`,
`completed-cta`, `extension-step` wrapper, `icon`, `permission-tooltip`.

`dashboard-setup` **retains**: data services (`fetchInitialData` /
`fetchFocusWidgetData`), `useHandleAction`, the duplexer subscription, the
extension platform (`getWidget` + `MICROACTION_ID_TO_COMPONENT_ID`), minimize +
the milestone/happy-moment hero, sidebar/banner, all BI/dealer telemetry.

---

## 2. Replacement map (production → SetupWidget)

| dashboard-setup | SetupWidget |
|---|---|
| `DashboardWidgetDataProvider` steps state | `useSetupWidget` → `SetupWidgetState.steps` |
| `focusedStepId` + `getNextStep` | `expandedStepId` / `toggleStep` + `primaryStepId` |
| `setStepCompleted` (STEP_COMPLETED listener) | `state.setStepStatus(id, 'completed')` from the extension's `onActionCompleted` |
| `onDashboardWidgetDataChange` (duplexer refetch) | consumer calls `state.refresh()` (G1) |
| `allStepsCompleted` / `setupCompleted` | `state._isAllComplete` |
| `steps.component` list + `step.component` row | `SetupWidget` + internal `SetupStepRow`/`SetupStepCard` |
| `step.container.onCtaClick` (navigate) | `onStepClick(step)` |
| `completed-cta` + `onCompletedCtaClick` | `completedCtaLabel` + `onCompletedStepClick(step)` (G2, G12) |
| `extension-step` (`getWidget`, `onActionCompleted`) | `renderStepContent(step)` (G9) |
| `IconComponent` / `permission-tooltip` | internal status icon / `disabledTooltip` on disabled CTA (G5) |
| `getAggregatedSteps` / `AggregatedByGroupStep` | `steps` (nested) on a step + `SetupStepGroup` (G3) |
| `SetupError` (initial) + silent refresh-error | `renderError` (or default Setup error card + retry) / silent `refresh` failure (G11) |
| host `ResolveFn` loading | built-in `SetupWidgetSkeleton` (G10); `state.isRefreshing` for background refresh |

---

## 3. Data adapter (`Step[]` → `SetupWidgetData`)

The consumer writes one pure function (its `fetchData`), translating + mapping:

```ts
const toSetupStep = (s: Step): SetupWidgetStep => ({
  id: s.id,
  title: t(s.title),                                   // pre-translate (G8)
  ctaLabel: s.cta && t(s.cta.value.key),
  completedCtaLabel: s.completedCta && t(s.completedCta.value.key), // G2
  status: s.isCompleted ? 'completed'
        : !s.isPermitted ? 'disabled'
        : s.inProgress ? 'in-progress'
        : 'upcoming',                                  // focus is derived, never pinned (C3)
  expansionMode: isExtensionStep(s) ? 'inline' : 'navigate',
});
```

> ⚠️ Status is **lossy** today (single enum can't carry `inProgress` + `!permitted`
> together) — see **G4**. Aggregated groups (`stepGroup`/`aggregated`) map to a
> step with `steps` (nested) (G3) — group `stepGroup`s into one step whose `steps` (nested)
> are the members.

---

## 4. Migration readiness checklist

✅ ready to wire now · ⛔ blocked on a gap · 🟡 partial

| Integration point | SetupWidget API | Status |
|---|---|---|
| Render the step list + header/progress ratio | `SetupWidget` + `state` | ✅ |
| Data adapter `Step[]`→`SetupWidgetData` | `fetchData` | ✅ (status enum lossy by design — G4 decided) |
| Inline (extension) step content + completion | `renderStepContent` + `setStepStatus` | ✅ (G9) |
| Navigate action | `onStepClick` → `handleAction(step, step.cta)` | ✅ |
| Completed action | `completedCtaLabel` + `onCompletedStepClick` → `handleAction(step, step.completedCta)` | ✅ (G2, G12) |
| Real-time / cache-bust refresh | duplexer → `state.refresh()` | ✅ (G1) |
| All-complete | `state._isAllComplete` (+ keep consumer's milestone hero via `onAllCompleteClick` or conditional render) | 🟡 (widget hero is WDS-only) |
| Initial loading | built-in `SetupWidgetSkeleton` (header + rows) | ✅ (G10); `state.isRefreshing` for background refresh |
| Error state | `CardContainer` error + retry, or `renderError` | ✅ (Setup copy + `renderError` — G11) |
| Aggregated groups (Stores/Bookings) | `steps` (nested) + `SetupStepGroup` | ✅ (G3 — progress counts leaves) |
| Disabled-step permission tooltip | `disabledTooltip` (or default) on the disabled CTA | ✅ (G5) |
| BI / dealer telemetry | `onStepView` + `onStepClick`/`onCompletedStepClick` (consumer fires BI/dealer) | ✅ (G6) |
| Minimize | — (consumer keeps it) | n/a (out of scope) |

---

## 5. Done so far (delivered SetupWidget capabilities)

What's already landed and how it maps to a dashboard-setup integration point:

- **G1 — `state.refresh()`** → replaces `onDashboardWidgetDataChange`; the
  consumer's duplexer subscription calls `refresh()` for server-driven status
  changes (navigate steps finishing elsewhere). Background swap, keeps steps,
  resets focus, swallows refetch errors — matches production.
- **G2 — `completedCtaLabel`** → completed steps stay actionable (replaces
  `completed-cta`). Quiet `TextButton` on completed rows, desktop + mobile.
- **G12 — `onCompletedStepClick`** → first-class completed action (replaces
  `onCompletedCtaClick` → `handleAction(step, completedCta)`), distinct from
  `onStepClick`.
- **G9 (baseline)** — `renderStepContent` render prop → the consumer mounts the
  extension (`getWidget`) and reports completion via `setStepStatus`.
- **Base widget** — step list, title + progress ratio, single-open inline
  expansion, navigate `onStepClick`, loading skeleton, error+retry,
  all-complete, responsive desktop/mobile.

**Net:** the **funnel happy-path** (list + progress + inline-complete + navigate
+ completed action + refresh + error UI + aggregated groups) is wireable today.
Funnel + aggregated areas are fully wireable. G4 (status enum) decided as-is;
G13 (copy overrides) optional/low-priority. No remaining blockers.

---

## 6. Suggested sequencing

1. **Spike adapter** — build `adaptFocusWidgetData` + `rawById`, render
   `SetupWidget` behind a flag next to the existing widget (funnel only). Wire
   `fetchData`, `refresh`, `onStepClick`, `onCompletedStepClick`,
   `renderStepContent`.
2. **Close parity gaps** as needed by the funnel: G11 (error copy), G10 (loading),
   G4 (status) — each lands in `@wix/patterns` with its own tests/story/SLED3.
3. **Aggregated areas** (Stores/Bookings) — G3, then migrate those step groups.
4. **Telemetry** — G6 callbacks, wire BI/dealer in the consumer.
5. **Cutover** — remove the dashboard-setup step components (§1 "replaces"); keep
   shell/minimize/hero/sidebar.
