Skip to content

Commit 2c02a0d

Browse files
carsonp6claude
authored andcommitted
feat(gga): redesign example app as React + Origin (Platform/Customer views + debug mode) (#28569)
## Summary Redesigns the Grid Global Accounts example app from a single 1,127-line `index.html` of "boxes + copied IDs" into a polished **React + `@lightsparkdev/origin`** app with two persona views and a debug mode — so it reads like what a Platform and a Customer actually see when integrating Grid, with logs/IDs/raw payloads hidden behind a Debug toggle. ## What changed - **React + Origin**: vanilla TS → React 19 + Vite (`@vitejs/plugin-react`) + `@lightsparkdev/origin` (styles + components), mirroring `grid-kyc-demo`'s wiring. - **Two persona views** behind a top switcher: - **Platform** — config/connection panel, customers table, create-customer, and "Act as" (selects a customer + switches to its Customer view). - **Customer** — real embedded-wallet flows for the active customer: login (OTP/OAuth/Passkey), wallet home, fund, pay (quote+execute), settings (manage credentials/sessions, export). - **Debug mode** (off by default): a docked debug console (request/response log), per-card raw-JSON expanders, and a context chip that reveals customer/account/session IDs — all hidden until toggled. - **Integration logic reused, not rewritten**: `api-client`/`turnkey`/`webauthn`/`session` + flow orchestration were decoupled from the old `ui.ts` via a `Reporter` interface; only the rendering layer was rebuilt. The vanilla `ui.ts`/`main.ts` are deleted. ## Notes - Design + plan committed in `REDESIGN.md` / `REDESIGN_PLAN.md`. - Built/verified on Node ≥20.19; `build` (tsc+vite) + `vitest` green. Every operation runs its real flow (nothing auto-signed); the persona switch only scopes the active customer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> GitOrigin-RevId: b77c9c3d76f67898f969272d37cf93a0396a91c9
1 parent 88d6ef4 commit 2c02a0d

64 files changed

Lines changed: 9965 additions & 2662 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ test-config.yml
5454
dist/
5555
build/
5656
lib/
57+
# The GGA example app keeps decoupled source modules under src/lib/ (not a
58+
# compiled-output dir), so it must not be swept up by the `lib/` rule above.
59+
!apps/examples/grid-global-accounts-example-app/src/lib/
5760

5861
# Vim swap files
5962
*.swp
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Verification screenshots — captured locally, not tracked.
2+
.screenshots/
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# GGA Example App — React + Origin Redesign
2+
3+
Date: 2026-06-10 · Status: proposed, pending review before the implementation plan.
4+
5+
## Goal
6+
7+
Turn the example app from one 1,127-line `index.html` of "boxes + copied IDs" into a polished **React + `@lightsparkdev/origin`** app that shows the **two sides of a Grid integration** — a **Platform view** and a **Customer view** — so a partner sees roughly what they'd build, with all logs / IDs / raw responses hidden behind a **Debug toggle**.
8+
9+
## Personas & shell
10+
11+
- **Persona switcher** (top bar): `Platform ⇄ Customer`, one view visible at a time.
12+
- **Active-customer chip** (Customer view): "Acting as: \<name>".
13+
- **Debug toggle** (top bar, **off by default**): reveals the debug drawer + raw IDs/JSON. Like a dev-tools panel you flip on.
14+
15+
## Platform view (admin-dashboard feel)
16+
17+
- **Platform config** panel — auth/connection status + editable platform settings.
18+
- **Customers** — a table (name · email · status · wallet state), a **Create customer** action, and **"Act as"** which selects the customer and switches to the Customer view scoped to it.
19+
20+
## Customer view (consumer-wallet feel, for the active customer)
21+
22+
- **Logged out →** login screen: pick a method (OTP / OAuth / Passkey). Real Turnkey ceremonies — nothing auto-signed.
23+
- **Logged in (session) →** wallet home: balance + account(s), and actions **Fund** (external account → money-in), **Send / Pay** (quote + execute), **Activity**.
24+
- **Settings →** manage credentials & sessions (add/remove passkey/OAuth, revoke sessions) and **export**.
25+
- **"Act as" = scope-switch only**: it threads the `customer_id` so you don't copy IDs; every operation still runs its real flow explicitly.
26+
27+
## Debug mode
28+
29+
- **Off** → only the two polished personas are visible.
30+
- **On** → a **debug drawer** (request/response log, today's "Response Log"), per-card **"raw"** expanders, and the context chip reveals actual IDs/JSON.
31+
32+
## Conversion strategy (vanilla → React + Origin)
33+
34+
- **Reuse the integration logic** (the valuable ~1,500 LOC): `api-client`, `turnkey`, `webauthn`, `session`, `config`, `mode`, and the flow orchestration in `flows/*.ts`. Today they call `ui.ts` for output; **decouple that** by injecting a small **reporter interface** (status + structured log events) that React consumes as state + the debug log, instead of DOM writes.
35+
- **Rebuild as React + Origin**: the shell, Platform view, Customer view, debug drawer — using Origin components and `@lightsparkdev/origin/styles.css`.
36+
- **Delete**: `ui.ts` DOM code and the `index.html` body (keep a minimal `index.html` with just the React mount root).
37+
- **Template**: mirror `grid-kyc-demo`'s React + Vite + Origin wiring (`main.tsx` imports `@lightsparkdev/origin/styles.css`; `declarations.d.ts` shims Origin's TS resolution; `@vitejs/plugin-react`).
38+
39+
## Proposed structure
40+
41+
```
42+
src/
43+
main.tsx # mount React + import "@lightsparkdev/origin/styles.css"
44+
App.tsx # shell: persona switcher, debug toggle, view routing
45+
declarations.d.ts # Origin TS-resolution shim (per grid-kyc-demo)
46+
state/ # activeCustomer, session, debugOn, log (React context/store)
47+
lib/ # reused logic: api-client, turnkey, webauthn, session, config, mode — no DOM
48+
flows/ # reused orchestration: returns results / emits log events (no DOM)
49+
components/ # Shell, PersonaSwitcher, DebugToggle, DebugDrawer, ContextChip, RawExpander
50+
views/
51+
platform/ # Config, CustomersTable, CreateCustomer
52+
customer/ # Login, WalletHome, Fund, Pay, Activity, Settings
53+
index.html # minimal: <div id="root">
54+
```
55+
56+
## Aesthetic
57+
58+
`@lightsparkdev/origin` styles + components (Origin palette, typography, spacing, components). Clean, light, credible — not a bespoke pixel-perfect design system. Stays within Origin defaults.
59+
60+
## Scope / sequencing (small stack on #28475, each step runnable + screenshotted)
61+
62+
1. **Scaffold**: add `react`, `react-dom`, `@vitejs/plugin-react`, `@lightsparkdev/origin`; `main.tsx` + `App` shell + Origin styles; minimal `index.html`; `declarations.d.ts`. Renders an empty shell with the persona switcher + debug toggle.
63+
2. **Decouple logic from `ui.ts`**: introduce the reporter/state+log interface; move `api-client`/`turnkey`/`webauthn`/`session`/`config`/`mode` + flows under `lib/`/`flows/`, DOM-free.
64+
3. **Platform view**: config panel + customers table + create + "act as".
65+
4. **Customer view**: login → wallet home → fund/pay → settings/export.
66+
5. **Debug drawer**: wire the log, raw expanders, context-chip IDs.
67+
6. **Remove** the old vanilla `ui.ts` + `index.html` body; cleanup + final polish pass.
68+
69+
## Non-goals
70+
71+
- Not a bespoke pixel-perfect design — use Origin defaults.
72+
- Not changing real Turnkey/API behavior — same flows, new rendering.
73+
- No backend changes.
74+
75+
## Open items (resolve during build)
76+
77+
- External-account/fund + quote/execute confirmed on the **Customer** side.
78+
- Exact Origin components to use (Button, Card, Table, TextInput, Tabs, Drawer/Modal, Badge) — pick as we build.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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

Comments
 (0)