|
| 1 | +# GGA React + Origin Redesign — Implementation Plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | +
|
| 5 | +**Goal:** Convert the vanilla-TS Grid Global Accounts example app into a polished React + `@lightsparkdev/origin` app with two persona views (Platform / Customer) and a debug toggle, reusing the existing integration logic. |
| 6 | + |
| 7 | +**Architecture:** Reuse the real integration logic (`api-client`, `turnkey`, `webauthn`, `session`, `config`, `mode`, `flows/*`) after decoupling it from `ui.ts` via an injected `Reporter` interface; rebuild only the rendering layer as React components styled with Origin. Mirror `grid-kyc-demo`'s React+Vite+Origin wiring. |
| 8 | + |
| 9 | +**Tech Stack:** React 19, Vite 8 (`@vitejs/plugin-react`), `@lightsparkdev/origin` (styles + components), TypeScript. Verification: `tsc`/`vite build` + dev-server screenshots; vitest unit tests for the decoupled logic. |
| 10 | + |
| 11 | +**Workspace:** `@lightsparkdev/grid-global-accounts-example-app` at `js/apps/examples/grid-global-accounts-example-app/`. |
| 12 | +**Commands:** dev `yarn workspace @lightsparkdev/grid-global-accounts-example-app dev` · build/typecheck `yarn workspace @lightsparkdev/grid-global-accounts-example-app build` · lint `yarn lint && yarn format`. |
| 13 | +**Note (frontend):** for UI tasks, acceptance is "build/typecheck passes + dev screenshot matches the intent." Component *internals* are built during execution with the **frontend-design** skill; this plan pins the file map, interfaces, props, Origin components, and per-task acceptance. Logic tasks use real vitest unit tests (TDD). |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Target file structure |
| 18 | + |
| 19 | +``` |
| 20 | +src/ |
| 21 | + main.tsx # mount React + import "@lightsparkdev/origin/styles.css" |
| 22 | + App.tsx # shell: persona switcher, debug toggle, view routing |
| 23 | + declarations.d.ts # *.module.scss / *.module.css shims (per grid-kyc-demo) |
| 24 | + state/ |
| 25 | + store.tsx # AppStateProvider + useAppState(): persona, activeCustomer, session, debugOn, log[]; reporter impl |
| 26 | + lib/ # reused logic, DOM-free, Reporter-injected |
| 27 | + reporter.ts # Reporter interface + LogEntry type |
| 28 | + api-client.ts turnkey.ts webauthn.ts session.ts config.ts mode.ts |
| 29 | + flows/ # reused orchestration, DOM-free, returns results / emits via Reporter |
| 30 | + customer.ts email-otp.ts oauth.ts passkey.ts manage.ts money.ts context.ts |
| 31 | + components/ |
| 32 | + Shell.tsx PersonaSwitcher.tsx DebugToggle.tsx DebugDrawer.tsx RawExpander.tsx ContextChip.tsx |
| 33 | + views/ |
| 34 | + platform/ PlatformView.tsx Config.tsx CustomersTable.tsx CreateCustomer.tsx |
| 35 | + customer/ CustomerView.tsx Login.tsx WalletHome.tsx Fund.tsx Pay.tsx Activity.tsx Settings.tsx |
| 36 | +index.html # minimal: <div id="root"> |
| 37 | +``` |
| 38 | + |
| 39 | +(Old `ui.ts` and the old `main.ts` are deleted in Task 6; the existing `flows/*.ts` and lib modules are *moved/edited in place*, not rewritten.) |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +### Task 1: Scaffold React + Origin shell |
| 44 | + |
| 45 | +**Files:** |
| 46 | +- Modify: `package.json` (deps), `vite.config.ts` (react plugin), `index.html` (mount root) |
| 47 | +- Create: `src/main.tsx`, `src/declarations.d.ts`, `src/App.tsx`, `src/state/store.tsx`, `src/components/{Shell,PersonaSwitcher,DebugToggle}.tsx` |
| 48 | + |
| 49 | +- [ ] **Step 1: Add deps** |
| 50 | +```bash |
| 51 | +yarn workspace @lightsparkdev/grid-global-accounts-example-app add \ |
| 52 | + "@lightsparkdev/origin@*" react@^19.2.6 react-dom@^19.2.6 @emotion/react@^11.14.0 @emotion/styled@^11.14.1 |
| 53 | +yarn workspace @lightsparkdev/grid-global-accounts-example-app add -D \ |
| 54 | + @vitejs/plugin-react@^5.2.0 @types/react@^19.2.15 @types/react-dom@^19.2.3 |
| 55 | +``` |
| 56 | + |
| 57 | +- [ ] **Step 2: Add the React plugin to `vite.config.ts`** — keep the existing proxy/server block; add: |
| 58 | +```ts |
| 59 | +import react from "@vitejs/plugin-react"; |
| 60 | +// in defineConfig({ ... }): plugins: [react()], |
| 61 | +``` |
| 62 | + |
| 63 | +- [ ] **Step 3: Minimal `index.html` body** — replace the giant body with: |
| 64 | +```html |
| 65 | +<body> |
| 66 | + <div id="root"></div> |
| 67 | + <script type="module" src="/src/main.tsx"></script> |
| 68 | +</body> |
| 69 | +``` |
| 70 | + |
| 71 | +- [ ] **Step 4: Create `src/declarations.d.ts`** (the `*.module.scss` + `*.module.css` shims block, copied verbatim from `grid-kyc-demo/src/declarations.d.ts` — Origin's `main` points at its source so tsc walks into `.module.scss`). |
| 72 | + |
| 73 | +- [ ] **Step 5: Create `src/main.tsx`** |
| 74 | +```tsx |
| 75 | +import { StrictMode } from "react"; |
| 76 | +import { createRoot } from "react-dom/client"; |
| 77 | +import "@lightsparkdev/origin/styles.css"; |
| 78 | +import { App } from "./App"; |
| 79 | + |
| 80 | +const container = document.getElementById("root"); |
| 81 | +if (!container) throw new Error("#root not found"); |
| 82 | +createRoot(container).render(<StrictMode><App /></StrictMode>); |
| 83 | +``` |
| 84 | + |
| 85 | +- [ ] **Step 6: Create `src/state/store.tsx`** — `AppStateProvider` + `useAppState()` hook exposing: |
| 86 | +```ts |
| 87 | +type Persona = "platform" | "customer"; |
| 88 | +type AppState = { |
| 89 | + persona: Persona; setPersona(p: Persona): void; |
| 90 | + activeCustomer: { id: string; name: string; email: string } | null; |
| 91 | + setActiveCustomer(c: AppState["activeCustomer"]): void; |
| 92 | + session: { /* held session material */ } | null; setSession(s: unknown): void; |
| 93 | + debugOn: boolean; toggleDebug(): void; |
| 94 | + log: LogEntry[]; // from lib/reporter |
| 95 | + reporter: Reporter; // pushes into log + status; see Task 2 |
| 96 | +}; |
| 97 | +``` |
| 98 | +(Reporter is fully defined in Task 2; here just hold `log` state + provide a `reporter` that appends.) |
| 99 | + |
| 100 | +- [ ] **Step 7: Create `Shell` + `PersonaSwitcher` + `DebugToggle`** using Origin components (segmented control / tabs for the switcher, a switch for debug). `App.tsx` renders `<AppStateProvider><Shell>{persona === "platform" ? <PlatformView/> : <CustomerView/>}</Shell></AppStateProvider>` with empty placeholder views for now. |
| 101 | + |
| 102 | +- [ ] **Step 8: Verify** — `yarn workspace @lightsparkdev/grid-global-accounts-example-app build` passes (tsc + vite). Then `… dev`, screenshot: an Origin-styled shell with a working Platform⇄Customer switcher and a debug toggle (placeholder view bodies). |
| 103 | + |
| 104 | +- [ ] **Step 9: Commit** — `feat(gga): scaffold React + Origin shell with persona switcher + debug toggle` |
| 105 | + |
| 106 | +--- |
| 107 | + |
| 108 | +### Task 2: `Reporter` interface + decouple logic from `ui.ts` |
| 109 | + |
| 110 | +**Files:** |
| 111 | +- Create: `src/lib/reporter.ts`, `src/lib/__tests__/reporter.test.ts` |
| 112 | +- Modify (move into `lib/`, remove `ui.ts` imports, accept `Reporter`): `api-client.ts`, `turnkey.ts`, `webauthn.ts`, `session.ts`, `config.ts`, `mode.ts` |
| 113 | +- Modify (accept `Reporter`, return results, no DOM): `flows/*.ts` |
| 114 | +- Modify: `src/state/store.tsx` (real `reporter` impl pushing to `log` + status) |
| 115 | + |
| 116 | +- [ ] **Step 1: Define `Reporter`** in `src/lib/reporter.ts` |
| 117 | +```ts |
| 118 | +export type LogEntry = { |
| 119 | + id: string; ts: number; |
| 120 | + level: "info" | "error" | "request" | "response"; |
| 121 | + label: string; detail?: unknown; // raw payload / IDs / JSON, shown only in debug mode |
| 122 | +}; |
| 123 | +export interface Reporter { |
| 124 | + log(entry: Omit<LogEntry, "id" | "ts">): void; |
| 125 | + status(message: string, kind?: "info" | "error" | "success"): void; |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +- [ ] **Step 2: Write failing unit test** `src/lib/__tests__/reporter.test.ts` — a collecting reporter records entries with ids/timestamps; assert order + fields. (Add a `test` script + vitest devDep if absent: `"test": "vitest run"`.) |
| 130 | +- [ ] **Step 3: Implement** the collecting reporter (used by the React store) → test passes (`yarn workspace … test`). |
| 131 | + |
| 132 | +- [ ] **Step 4: Decouple each lib module** — replace `import { ... } from "../ui"` / `ui.log(...)` / `ui.setStatus(...)` calls with a `reporter: Reporter` parameter (thread it through). No `document.*`. Pattern, per module: |
| 133 | + - was: `ui.log("submitted", body)` → now: `reporter.log({ level: "request", label: "submitted", detail: body })`. |
| 134 | +- [ ] **Step 5: Decouple each flow** in `flows/*.ts` similarly — take `reporter` (and the active context) as args, **return** their result instead of rendering. `flows/manage.ts` + `session.ts` also drop their direct DOM (`getElementById`/`innerHTML`). |
| 135 | +- [ ] **Step 6: Wire the store's `reporter`** to append `LogEntry`s to `log` and surface `status`. |
| 136 | +- [ ] **Step 7: Verify** — `… build` passes; `… test` green; existing flows still callable from a temporary dev button (smoke). |
| 137 | +- [ ] **Step 8: Commit** — `refactor(gga): decouple integration logic from ui.ts via Reporter` |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +### Task 3: Platform view |
| 142 | + |
| 143 | +**Files:** Create `src/views/platform/{PlatformView,Config,CustomersTable,CreateCustomer}.tsx` |
| 144 | + |
| 145 | +- [ ] **Config** (Origin Card + form inputs): shows platform auth/connection status + editable platform settings; reads/writes via `lib/config.ts` + `flows/context.ts`. |
| 146 | +- [ ] **CreateCustomer** (Origin form/modal): calls `flows/customer.ts`; on success adds the customer to `state` (a session-local list — the demo tracks customers it created) and selects it. |
| 147 | +- [ ] **CustomersTable** (Origin Table): lists the session-local customers (name · email · status · wallet state) with a row **"Act as"** action → `setActiveCustomer(row)` + `setPersona("customer")`. |
| 148 | +- [ ] **Verify** — `… build` passes; `… dev` screenshot: config panel + customer table + create flow + "act as" switches to (placeholder/real) Customer view. |
| 149 | +- [ ] **Commit** — `feat(gga): platform view (config, customers table, create, act-as)` |
| 150 | + |
| 151 | +--- |
| 152 | + |
| 153 | +### Task 4: Customer view (split into sub-commits) |
| 154 | + |
| 155 | +**Files:** Create `src/views/customer/{CustomerView,Login,WalletHome,Fund,Pay,Activity,Settings}.tsx` |
| 156 | + |
| 157 | +- [ ] **4a — Login**: method tabs (OTP / OAuth / Passkey) → real flows (`flows/email-otp.ts`, `oauth.ts`, `passkey.ts`) for the `activeCustomer`; on success `setSession(...)`. Logged-out state if no session. Commit. |
| 158 | +- [ ] **4b — WalletHome + Fund + Pay + Activity**: balance/accounts (Origin Card/stat); **Fund** via `flows/money.ts` (external account → money-in); **Pay** via `flows/money.ts` (quote + execute); **Activity** list. Commit. |
| 159 | +- [ ] **4c — Settings**: manage credentials & sessions (add/remove passkey/OAuth, revoke) + export via `flows/manage.ts`. Commit. |
| 160 | +- [ ] **Verify each** — `… build` passes; `… dev` screenshots of login → wallet → fund/pay → settings, acting as a created customer end-to-end (real flows). |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +### Task 5: Debug drawer + raw expanders + context chip |
| 165 | + |
| 166 | +**Files:** Create `src/components/{DebugDrawer,RawExpander,ContextChip}.tsx`; wire into `Shell`. |
| 167 | + |
| 168 | +- [ ] **DebugDrawer**: rendered when `debugOn`; lists `state.log` entries (request/response/info/error) with expandable `detail` JSON. Origin Drawer/panel styling. |
| 169 | +- [ ] **RawExpander**: a reusable "raw" disclosure used inside cards; renders `detail` JSON only when `debugOn`. |
| 170 | +- [ ] **ContextChip**: shows active customer/session; reveals the actual IDs (customer/wallet/session) only when `debugOn` (collapsed to name otherwise). |
| 171 | +- [ ] **Verify** — `… dev` screenshot: debug off = clean personas; debug on = drawer + raw JSON + IDs appear. |
| 172 | +- [ ] **Commit** — `feat(gga): debug drawer + raw expanders + context chip (off by default)` |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +### Task 6: Remove vanilla shell + final polish |
| 177 | + |
| 178 | +**Files:** Delete `src/ui.ts`, old `src/main.ts`; remove any remaining old markup; final pass. |
| 179 | + |
| 180 | +- [ ] Delete `src/ui.ts` and the old `src/main.ts`; grep for stray references (`grep -rn "from \"./ui\"" src` → none). |
| 181 | +- [ ] `yarn lint && yarn format`; `yarn workspace @lightsparkdev/grid-global-accounts-example-app build` passes. |
| 182 | +- [ ] Final `… dev` screenshots: Platform view, Customer view (logged in), debug on — confirm polished + Origin-branded. |
| 183 | +- [ ] **Commit** — `chore(gga): remove vanilla ui.ts/main.ts; final polish` |
| 184 | + |
| 185 | +--- |
| 186 | + |
| 187 | +## Self-review |
| 188 | + |
| 189 | +- **Spec coverage:** personas+switcher (Task 1), Platform view (Task 3), Customer view incl. fund/pay (Task 4), debug mode (Task 5), context threading / "act as" scope-switch (Task 3 act-as + store), Origin styling (Tasks 1–5), reuse-logic-decouple-ui (Task 2), remove vanilla (Task 6). ✓ All spec sections covered. |
| 190 | +- **Placeholders:** scaffold/interfaces are concrete code; UI internals are intentionally built via frontend-design at execution with build+screenshot acceptance (noted up top) — not a hidden TODO. |
| 191 | +- **Type consistency:** `Reporter`/`LogEntry` defined in Task 2 and consumed by the store (Task 1 forward-references it, fully defined in Task 2) and by lib/flows; `Persona`/`activeCustomer`/`session`/`debugOn`/`log` names consistent across store and components. |
0 commit comments