@interop/byoe-react-template 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+
15
+ - uses: actions/setup-node@v6
16
+ with:
17
+ node-version: '24'
18
+
19
+ # Provision pnpm via Corepack (bundled with Node) from the
20
+ # `packageManager` pin in package.json, avoiding the flaky
21
+ # pnpm/action-setup self-installer.
22
+ - name: Enable Corepack
23
+ run: corepack enable
24
+
25
+ - name: Install dependencies
26
+ run: pnpm install --frozen-lockfile
27
+
28
+ - name: Lint
29
+ run: pnpm run lint
30
+
31
+ - name: Typecheck
32
+ run: pnpm run typecheck
33
+
34
+ - name: Build
35
+ run: pnpm run build
36
+
37
+ - name: Test (Node)
38
+ run: pnpm run test:node
39
+
40
+ - name: Install Playwright browsers
41
+ run: pnpm exec playwright install --with-deps chromium
42
+
43
+ - name: Test (Browser)
44
+ run: pnpm run test:browser
@@ -0,0 +1,38 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ id-token: write # required for npm provenance
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+
16
+ - uses: actions/setup-node@v6
17
+ with:
18
+ node-version: '24'
19
+ registry-url: 'https://registry.npmjs.org'
20
+
21
+ # Provision pnpm via Corepack (bundled with Node) from the
22
+ # `packageManager` pin in package.json, avoiding the flaky
23
+ # pnpm/action-setup self-installer.
24
+ - name: Enable Corepack
25
+ run: corepack enable
26
+
27
+ # Node 24's bundled npm is already >= 11.5.1, as trusted publishing
28
+ # requires. Do not `npm install -g npm@latest` here: npm overwriting
29
+ # itself in the runner toolcache leaves it unable to resolve `sigstore`.
30
+
31
+ - name: Install dependencies
32
+ run: pnpm install --frozen-lockfile
33
+
34
+ - name: Build
35
+ run: pnpm run build
36
+
37
+ - name: Publish to npm
38
+ run: npm publish
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - TBD
4
+
5
+ ### Changed
6
+
7
+ - Scoped the package name to `@interop/byoe-react-template` (the unscoped
8
+ `byoe-react-template` npm package is deprecated).
9
+
10
+ ### Added
11
+
12
+ - Initial BYOE app template: a Vite + React + TypeScript SPA built on
13
+ `@interop/was-react`.
14
+ - CHAPI wallet login (DID Auth) with a protected route and offline dev mode.
15
+ - An example WAS-backed "notes" collection with local-first encrypted storage
16
+ and background sync.
17
+ - MUI app shell with sync status and reconnect UI.
18
+ - Three-tier test setup (offline/mocked, real WAS server, full wallet login)
19
+ plus a Node unit tier.
20
+ - A dev provisioning script for syncing without a wallet.
package/CLAUDE.md ADDED
@@ -0,0 +1,55 @@
1
+ # byoe-react-template (BYOE Notes)
2
+
3
+ A minimal, working "Bring Your Own Everything" (BYOE) app template: a Vite +
4
+ React + TypeScript SPA with DID-Auth login via a CHAPI wallet, local-first
5
+ encrypted storage, and background sync to a WAS server, built on
6
+ `@interop/was-react`. It ships one example "notes" collection. The library owns
7
+ the reusable plumbing (identity, login, session, encrypted replica, sync, hooks,
8
+ MUI components); this repo is the thin app-specific shell around it.
9
+
10
+ ## Source layout
11
+
12
+ - `src/app.config.ts` -- env-var exports and the one `WasAppConfig` the library
13
+ consumes (app name, credential type/vocab, `COLLECTIONS` map). First stop when
14
+ renaming the template.
15
+ - `src/stores/notes.ts` -- the example entity store (`createEntityStore`) plus
16
+ the `StoreRegistry` the rehydrate mechanism drives.
17
+ - `src/dev/` -- dev-mode bootstrap (`bootstrap.ts` opens the local store from
18
+ `devSeed.ts` and hydrates) and `devSync.ts` (CHAPI-bypassed replication from a
19
+ provisioned grants file).
20
+ - `src/components/` -- `AppShell` (top bar, `SyncStatusChip`, `ReconnectBanner`,
21
+ logout) and `DevGate` (dev-mode router gate; wallet mode uses the library's
22
+ `ProtectedRoute`).
23
+ - `src/pages/` -- `LoginPage` (CHAPI login) and `NotesPage` (list/add/edit/
24
+ delete).
25
+ - `scripts/provision-dev-grants.ts` -- provisions a dev Space, collections, and
26
+ delegated zcaps against a running was-teaching-server.
27
+ - `playwright.config.ts` (offline/mocked, CI), `playwright.was.config.ts` (real
28
+ WAS server), `playwright.wallet.config.ts` (full wallet login), and `test/`
29
+ tiers, plus Node unit tests via Vitest.
30
+
31
+ ## Commands
32
+
33
+ ```
34
+ pnpm dev # wallet mode; VITE_AUTH_MODE=dev for offline mode
35
+ pnpm run build # typecheck + Vite build
36
+ pnpm run typecheck
37
+ pnpm run lint
38
+ pnpm run fix # eslint --fix + prettier
39
+ pnpm run test:node # Vitest + fake-indexeddb
40
+ pnpm run test:browser # Playwright, offline/mocked (CI)
41
+ pnpm run test:browser:was # Playwright against a real local WAS server
42
+ pnpm run test:browser:wallet # Playwright full wallet login (local/manual)
43
+ pnpm run provision:dev # provision dev grants (SERVER_URL, default :3002)
44
+ ```
45
+
46
+ ## Conventions
47
+
48
+ - pnpm; Node >= 24; ESM only.
49
+ - Prettier: no semicolons, single quotes, no trailing commas,
50
+ `arrowParens: avoid`.
51
+ - Never use the right-arrow character (write `to`), em-dash character (write
52
+ `--`), or ellipsis character (write `...`).
53
+ - Inline single-use option/argument types at the function signature instead of
54
+ declaring a named type.
55
+ - Keep app-agnostic logic in `@interop/was-react` rather than growing it here.
package/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Interop Alliance
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,217 @@
1
+ # BYOE Notes _(@interop/byoe-react-template)_
2
+
3
+ [![Node.js CI](https://github.com/interop-alliance/byoe-react-template/workflows/CI/badge.svg)](https://github.com/interop-alliance/byoe-react-template/actions?query=workflow%3A%22CI%22)
4
+ [![NPM Version](https://img.shields.io/npm/v/@interop/byoe-react-template.svg)](https://npm.im/@interop/byoe-react-template)
5
+
6
+ > A template for building "Bring Your Own Everything" (BYOE) apps on Wallet
7
+ > Attached Storage: a Vite + React + TypeScript SPA with DID-Auth login via a
8
+ > CHAPI wallet, local-first encrypted storage, and background sync, built on
9
+ > [`@interop/was-react`](https://npm.im/@interop/was-react).
10
+
11
+ ## Table of Contents
12
+
13
+ - [Background](#background)
14
+ - [What is in this template](#what-is-in-this-template)
15
+ - [Install](#install)
16
+ - [Usage](#usage)
17
+ - [Renaming this template into a new app](#renaming-this-template-into-a-new-app)
18
+ - [Testing](#testing)
19
+ - [Contribute](#contribute)
20
+ - [License](#license)
21
+
22
+ ## Background
23
+
24
+ "Bring Your Own Everything" (BYOE) is a way to build web apps with no backend
25
+ that the app owns. The user brings their own identity (a wallet) and their own
26
+ storage (Wallet Attached Storage, WAS), and the app stores everything encrypted
27
+ in that user-owned space. The app is a Relying Party: it authenticates via
28
+ "Login With Wallet" (CHAPI) and reads and writes the user's WAS space using
29
+ wallet-delegated authorization capabilities (zcaps). It never owns the space,
30
+ never holds the wallet's root key, and invokes only the zcaps the wallet grants
31
+ it.
32
+
33
+ "Bring Your Own Storage" (BYOS) is the storage half of that model. Every
34
+ collection is encrypted client-side as an Encrypted Data Vault (EDV): the WAS
35
+ server only ever sees opaque JWE envelopes and can neither read nor search the
36
+ plaintext. Data is local-first -- a local RxDB (IndexedDB) database holds the
37
+ encrypted envelopes and replicates them to WAS in the background. The app works
38
+ fully offline; sync resumes on reconnect.
39
+
40
+ This template is a minimal, working BYOE app that wires all of that up through
41
+ `@interop/was-react`, which owns the reusable plumbing (identity derivation, the
42
+ CHAPI login flow, the session lifecycle, the encrypted local replica, WAS
43
+ replication, and the React hooks and MUI components). For the depth on those
44
+ pieces -- the login flow, session lifecycle, and sync architecture -- see the
45
+ [`@interop/was-react` README](https://npm.im/@interop/was-react). This document
46
+ covers what the template ships and how to make it your own.
47
+
48
+ ## What is in this template
49
+
50
+ - A **login page** (`src/pages/LoginPage.tsx`) driving DID-Auth login through
51
+ CHAPI: one "Login with wallet" button, a per-phase progress line, and an error
52
+ alert.
53
+ - A **protected route**: in wallet mode the library's `ProtectedRoute` (from
54
+ `@interop/was-react/mui`) gates the app; in offline dev mode a local `DevGate`
55
+ plays the same role.
56
+ - One example **WAS-backed "notes" collection** (`src/pages/NotesPage.tsx` +
57
+ `src/stores/notes.ts`): list, add, edit, and delete notes, read from an
58
+ in-memory store hydrated from the encrypted local replica and replicated to
59
+ WAS in the background.
60
+ - An **MUI app shell** (`src/components/AppShell.tsx`): a top bar with the app
61
+ name, the library's `SyncStatusChip` (offline / syncing / synced / error), a
62
+ logout button, and the `ReconnectBanner` shown when granted access nears
63
+ expiry.
64
+ - A **dev provisioning script** (`scripts/provision-dev-grants.ts`) for syncing
65
+ against a local WAS server without a wallet in the loop, and a **three-tier
66
+ test setup** (see [Testing](#testing)).
67
+
68
+ ## Install
69
+
70
+ ### Prerequisites
71
+
72
+ - **Node.js >= 24** and **pnpm** (the repo pins `pnpm@11.9.0`).
73
+ - For real background sync, a **WAS server** --
74
+ [`was-teaching-server`](https://github.com/interop-alliance/) run locally.
75
+ - For wallet login, a **CHAPI wallet** -- for example
76
+ [`freewallet`](https://github.com/interop-alliance/), or any CHAPI wallet
77
+ reachable through the [authn.io](https://authn.io) mediator.
78
+
79
+ ### Setup
80
+
81
+ ```
82
+ pnpm install
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ### Auth modes
88
+
89
+ The app runs in one of two auth modes, selected by `VITE_AUTH_MODE`:
90
+
91
+ - **Wallet mode (default).** `pnpm dev` serves the login page and gates the app
92
+ behind Login With Wallet. This needs a CHAPI wallet and a WAS server to
93
+ complete a login and sync.
94
+
95
+ ```
96
+ pnpm dev
97
+ ```
98
+
99
+ - **Offline dev mode.** `VITE_AUTH_MODE=dev pnpm dev` skips login entirely and
100
+ boots straight into a local encrypted store keyed by a fixed public dev seed.
101
+ No wallet, no server, no sync -- just the UI against the local replica. Good
102
+ for developing screens.
103
+
104
+ ```
105
+ VITE_AUTH_MODE=dev pnpm dev
106
+ ```
107
+
108
+ - **Dev mode with sync.** Dev mode plus `VITE_WAS_DEV_SYNC=true` replicates to a
109
+ running WAS server without a wallet, using a locally provisioned grants file.
110
+ First, with a `was-teaching-server` running, provision the grants:
111
+
112
+ ```
113
+ pnpm run provision:dev
114
+ ```
115
+
116
+ This creates a dev Space and the app's collections on the server (default
117
+ `http://localhost:3002`, override with `SERVER_URL`) and writes the delegated
118
+ zcaps to the git-ignored `public/dev-grants.local.json`. Then run:
119
+
120
+ ```
121
+ VITE_AUTH_MODE=dev VITE_WAS_DEV_SYNC=true pnpm dev
122
+ ```
123
+
124
+ > The fixed dev seed is public. Never use dev mode (or that seed) for anything
125
+ > real: anything encrypted under it is readable by anyone.
126
+
127
+ ### Environment variables
128
+
129
+ All are optional; see `src/app.config.ts` for the defaults.
130
+
131
+ | Variable | Default | Purpose |
132
+ | ------------------------- | ------------------------ | ---------------------------------------------------------------------- |
133
+ | `VITE_AUTH_MODE` | `wallet` | `wallet` (CHAPI login gate) or `dev` (local dev-seed store, no login). |
134
+ | `VITE_APP_ORIGIN` | `http://localhost:5173` | This app's origin; the CHAPI anti-phishing binding on the app key. |
135
+ | `VITE_WAS_SERVER_URL` | `http://localhost:3002` | Remote WAS server; the expected host of every granted zcap. |
136
+ | `VITE_WAS_DEV_SYNC` | `false` | Dev mode only: replicate to WAS using a provisioned grants file. |
137
+ | `VITE_WAS_DEV_GRANTS_URL` | `/dev-grants.local.json` | Where the app fetches the dev grants JSON from. |
138
+ | `VITE_WAS_SYNC_RETRY_MS` | (library default) | Replication retry backoff, in ms. |
139
+ | `VITE_WAS_SYNC_POLL_MS` | (library default) | Periodic re-sync interval, in ms. |
140
+
141
+ ### Other scripts
142
+
143
+ ```
144
+ pnpm run build # typecheck, then Vite production build
145
+ pnpm run typecheck # tsc --noEmit
146
+ pnpm run lint # eslint over src, test, scripts
147
+ pnpm run fix # eslint --fix, then prettier --write
148
+ ```
149
+
150
+ ## Renaming this template into a new app
151
+
152
+ To turn "BYOE Notes" into your own app:
153
+
154
+ 1. **`package.json`** -- change `name` and `description`.
155
+ 2. **`src/app.config.ts`** -- set `appConfig.appName`, the
156
+ `credential.credentialType` and `credential.vocabBase` (a unique credential
157
+ type and vocab URI for your app's seed credential), and the `COLLECTIONS`
158
+ list (see step 5).
159
+ 3. **`index.html`** -- change the `<title>`.
160
+ 4. **UI strings** -- replace the "BYOE Notes" text in
161
+ `src/components/AppShell.tsx` and `src/pages/LoginPage.tsx`.
162
+ 5. **Replace the notes collection with your own.** Each collection needs three
163
+ things:
164
+ - a `{ key, id }` entry in `COLLECTIONS` in `src/app.config.ts` (the app-side
165
+ `key` maps to the WAS collection `id`, a deliberately unprefixed, generic
166
+ name shared across interoperable apps);
167
+ - an **entity store** created with `createEntityStore<T>(key)` (see
168
+ `src/stores/notes.ts`);
169
+ - a **registry entry** wiring that store's `hydrate` / `patch` / `drop` /
170
+ `replaceAll` handlers into the exported `StoreRegistry`.
171
+
172
+ Then build a page against your store's `insert` / `update` / `remove` verbs,
173
+ modeled on `src/pages/NotesPage.tsx`, and route to it in `src/App.tsx`.
174
+
175
+ Every entity payload MUST carry `updatedAt` (ISO timestamp) and `deviceId`
176
+ (from the library's `getDeviceId()`), stamped on every insert and update:
177
+ remote conflicts are resolved last-writer-wins on that pair, and a payload
178
+ without them loses every conflict to the server copy.
179
+
180
+ ## Testing
181
+
182
+ The template has three browser test tiers plus a Node unit tier. The offline
183
+ tier is the CI-suitable default; the WAS and wallet tiers need sibling checkouts
184
+ of the supporting servers.
185
+
186
+ ```
187
+ pnpm run test:node # Vitest + fake-indexeddb, Node (no browser)
188
+ pnpm run test:browser # Playwright, offline/mocked dev mode; CI-suitable
189
+ pnpm run test:browser:was # Playwright against a real local WAS server
190
+ pnpm run test:browser:wallet # Playwright full wallet login flow; local/manual
191
+ ```
192
+
193
+ - **`test:node`** runs the Vitest suite against `fake-indexeddb`. No browser or
194
+ server needed.
195
+ - **`test:browser`** (the default `playwright.config.ts`) serves the app in
196
+ offline dev mode and drives the UI against the local encrypted replica only.
197
+ No sibling checkouts or servers required.
198
+ - **`test:browser:was`** (`playwright.was.config.ts`) boots a local
199
+ `was-teaching-server` from a sibling `../was-teaching-server` checkout
200
+ (override with `WAS_SERVER_DIR`), provisions dev grants, and exercises real
201
+ replication in dev-sync mode.
202
+ - **`test:browser:wallet`** (`playwright.wallet.config.ts`) boots the WAS server
203
+ plus a `freewallet` dev server (sibling `../freewallet`, override with
204
+ `FREEWALLET_DIR`) and runs the full Login With Wallet flow. This is a
205
+ local/manual tier, not for CI.
206
+
207
+ The combined `pnpm test` runs `lint` then `test:node`.
208
+
209
+ ## Contribute
210
+
211
+ PRs accepted. If editing this README, please conform to the
212
+ [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
213
+ Keep app-agnostic logic in `@interop/was-react` rather than growing it here.
214
+
215
+ ## License
216
+
217
+ [MIT License](LICENSE.md) (c) 2026 Interop Alliance.
@@ -0,0 +1,143 @@
1
+ # Phase 2 handoff: build `byoe-react-template`
2
+
3
+ Phase 1 is DONE: `@interop/was-react` is fully extracted, tested, documented
4
+ (local checkout at `~/code/Interop/was-react`, for reading source/API), and
5
+ published to npm as `@interop/was-react@0.1.2`. This file gives a fresh
6
+ session everything needed to build
7
+ the template app in THIS repo (`~/code/Interop/byoe-react-template`, currently
8
+ an empty clone).
9
+
10
+ ## Goal (from the original prompt)
11
+
12
+ A minimal but complete Vite + React + TypeScript SPA, `private: true`, no
13
+ packaging machinery, demonstrating the BYOE pattern end to end by consuming
14
+ `@interop/was-react`. Keep the template thin: anything app-agnostic belongs in
15
+ the library.
16
+
17
+ Required app content:
18
+ - Login page with DID Auth via CHAPI (wallet polyfill wired up)
19
+ - One protected route behind `ProtectedRoute` (from `@interop/was-react/mui`)
20
+ - One example WAS-backed collection ("notes"): list/add/edit/delete,
21
+ local-first with sync, `SyncStatusChip` in the app shell
22
+ - MUI app shell with `ReconnectBanner`
23
+
24
+ Tooling modeled on `~/code/Dima/life-advisor/package.json`: scripts `dev`,
25
+ `build` (typecheck + `vite build`), `fix`, `format`, `lint`, `typecheck`,
26
+ `test`, `test:node`, `test:browser`, `test:browser:was`,
27
+ `test:browser:wallet`, `provision:dev`; pnpm; `engines.node >= 24`; MIT;
28
+ author "Interop Alliance" (https://github.com/interop-alliance/); eslint flat
29
+ config + prettier + tsconfig conventions as in life-advisor /
30
+ isomorphic-lib-template.
31
+
32
+ Testing: vitest + fake-indexeddb for unit tests; THREE Playwright configs
33
+ (mocked / real WAS server / real wallet) mirroring life-advisor's
34
+ `playwright.config.ts`, `playwright.was.config.ts`,
35
+ `playwright.wallet.config.ts`; plus the dev-grants provisioning script
36
+ (`provision:dev` calls the library's `@interop/was-react/dev`).
37
+
38
+ README: what BYOE/BYOS is (brief), prerequisites (WAS server, wallet), how to
39
+ rename the template into a new app, how to run each test tier. Also a short
40
+ CLAUDE.md describing structure and conventions.
41
+
42
+ Constraints: do NOT modify life-advisor, freewallet, or was-react (except:
43
+ publishing reminder below). Check in with the user when the template is done.
44
+
45
+ ## Consuming @interop/was-react
46
+
47
+ Published: use `"@interop/was-react": "^0.1.2"` from the npm registry (do NOT
48
+ use link:/file: references). was-react peers the template must install:
49
+ `react >= 19`, `react-dom`, `zustand ^5`, `rxdb ^17`, and for the mui entry:
50
+ `@mui/material ^9`, `@mui/icons-material ^9`, `@emotion/react`,
51
+ `@emotion/styled`, `react-router ^8`. Node-only dev entry needs `tsx` to run
52
+ the provision script (or use the installed `was-provision-dev-grants` bin).
53
+
54
+ ## was-react API facts (verified)
55
+
56
+ - Wrap app above the router:
57
+ `<WasSessionProvider config={appConfig} registry={storeRegistry}>`.
58
+ - `WasAppConfig`: `{ appName, appOrigin, collections: [{ key, id }],
59
+ credential: { credentialType, vocabBase }, wasServerUrl?, mediatorBase?,
60
+ dbName?, storageKeyPrefix?, sync?, expiry? }`. The library builds the
61
+ documentLoader/LoginConfig internally from this.
62
+ - `StoreRegistry = Record<collectionKey, { hydrate(): Promise<void>;
63
+ upsert(doc: {id}): void; drop(uuid): void; clear(): void }>` -- one entry
64
+ per `collections[].key`.
65
+ - `createEntityStore` (zustand factory) exposes
66
+ `hydrate/insert/update/remove/replaceAll/patch/drop` -- NO `upsert`/`clear`;
67
+ registry wiring needs small adapters: `upsert -> patch` (with a cast),
68
+ `clear -> replaceAll([])`. The was-react README quick start shows exactly
69
+ this for a "notes" store -- FOLLOW IT. (Optional improvement noted with the
70
+ user: a `toRegistryEntry(store)` helper in was-react could remove this
71
+ boilerplate; fine to add to was-react during Phase 2 if it proves annoying,
72
+ it was offered and not rejected.)
73
+ - Hooks: `useLogin()` -> `{ login, status, phase, error }` (phases:
74
+ `probing`/`storing-key`/`requesting-grants`/`verifying`), `useSession`,
75
+ `useLogout`, `useReconnect`, `useSyncStatus` (rollup:
76
+ offline / error > syncing > synced), `useAppReady`.
77
+ - `@interop/was-react/mui`: `ProtectedRoute` (props incl.
78
+ `loginPath`, default `/login`; calls `restore()` on mount, redirects
79
+ unauthenticated), `ReconnectBanner`, `SyncStatusChip`. Wallet-mode only (no
80
+ dev-mode branch; life-advisor's dev `initApp`/`AUTH_MODE` pattern stays
81
+ app-side -- the template does NOT need a dev mode unless the WAS e2e tier
82
+ requires dev-sync, see below).
83
+ - Dev sync (no wallet): app-side wiring like life-advisor's `devSync.ts` --
84
+ fetch a grants JSON, `parseGrants`, `deriveIdentity({ seed })`,
85
+ `startWasSync({ parsed, zcapClient, localStore,
86
+ syncController: createSyncController({ collections, sync }),
87
+ onRemoteChange: (key, event) => patchFromChange(registry, key, event) })`.
88
+ Library exports all of these from the root entry.
89
+ - `@interop/was-react/dev`: `provisionDevGrants({ serverUrl, seed,
90
+ collections: string[], spaceName?, outFile?, probe? })`; CLI
91
+ `was-provision-dev-grants --server-url --seed (hex|base64url)
92
+ --collections a,b --space-name --out --probe`.
93
+ - CHAPI E2E test bridge globals (for Playwright without a real mediator):
94
+ `__WAS_REACT_E2E_CHAPI__` (activation flag),
95
+ `__WAS_REACT_E2E_CHAPI_REQUESTS__`, `__WAS_REACT_E2E_CHAPI_RESPONSES__`;
96
+ active when `import.meta.env.MODE !== 'production'`.
97
+ - Read `~/code/Interop/was-react/README.md` -- its Quick start section is the
98
+ authoritative wiring example (all symbols verified against source).
99
+
100
+ ## Playwright tier facts (from life-advisor + freewallet)
101
+
102
+ - Default config: offline/mocked suite, vite dev server, single chromium.
103
+ - WAS config: serialized, single worker; boots the app AND a real WAS server
104
+ from a sibling checkout `../was-teaching-server` (env override
105
+ `WAS_SERVER_DIR`), server started with `pnpm run dev` and env
106
+ `PORT`/`SERVER_URL`; the server's `SERVER_URL` must EXACTLY equal the
107
+ client's `VITE_WAS_SERVER_URL` (zcap invocation-target host match).
108
+ life-advisor's version uses globalSetup/globalTeardown to provision grants.
109
+ - Wallet config: boots 3 servers -- was-teaching-server, freewallet
110
+ (`../freewallet`, env `FREEWALLET_DIR`), and the app. life-advisor's
111
+ playwright.wallet.config.ts hardcodes `/Users/dmitriz/...` defaults --
112
+ use env-overridable relative defaults (`../was-teaching-server`,
113
+ `../freewallet`) instead.
114
+ - freewallet CI note: only the mocked tier runs in CI; WAS/wallet tiers are
115
+ local/manual (need sibling checkouts).
116
+
117
+ ## Repo conventions (user's rules)
118
+
119
+ - pnpm, Node >= 24, MIT, author Interop Alliance. Do not commit or bump
120
+ versions; user handles that. CHANGELOG dates are `TBD`.
121
+ - Never use the characters `→` (use `to`), mdash (use `--`), `…` (use `...`).
122
+ - Never mention `_spec` dirs, roadmap phases/tracks, or bedrock libraries in
123
+ CHANGELOG/code comments.
124
+ - TS style: single-use options types inlined at signatures; single-field
125
+ interfaces inlined at usage.
126
+ - pnpm build-script approvals: if scripts fail with ERR_PNPM_IGNORED_BUILDS,
127
+ set `allowBuilds: esbuild: true` in pnpm-workspace.yaml (was done in
128
+ was-react). Lockfile/minimumReleaseAge issues: stop and ask the user.
129
+ - Delegate to Opus 4.8 sub-agents whenever appropriate.
130
+ - Prettier in these repos: no semicolons, single quotes, no trailing commas,
131
+ arrowParens avoid, proseWrap always.
132
+
133
+ ## Suggested plan
134
+
135
+ 1. Scaffold: Vite React-TS app modeled on life-advisor's tooling (copy its
136
+ eslint/prettier/tsconfig conventions; `private: true`, no exports/files/
137
+ publishConfig). `@interop/was-react@^0.1.2` + peers.
138
+ 2. App: config + notes entity store + registry + provider + router (login
139
+ page, protected `/notes` route, MUI shell with ReconnectBanner +
140
+ SyncStatusChip). Deliberately minimal, every piece demonstrating one part.
141
+ 3. Tests: vitest unit (notes store round-trip w/ fake-indexeddb), 3 Playwright
142
+ tiers, provision:dev script.
143
+ 4. README + CLAUDE.md. Check in with user when done.
@@ -0,0 +1,40 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import tseslint from 'typescript-eslint'
4
+ import prettierConfig from 'eslint-config-prettier'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['dist', '**/*.min.js']),
9
+ {
10
+ files: ['**/*.ts', '**/*.tsx'],
11
+ extends: [
12
+ js.configs.recommended,
13
+ tseslint.configs.recommended,
14
+ prettierConfig // must be last in extends
15
+ ],
16
+ languageOptions: {
17
+ ecmaVersion: 2022,
18
+ globals: { ...globals.browser, ...globals.node },
19
+ parserOptions: {
20
+ project: ['./tsconfig.dev.json']
21
+ }
22
+ },
23
+ rules: {
24
+ 'no-unused-vars': 'off',
25
+ '@typescript-eslint/no-unused-vars': [
26
+ 'error',
27
+ {
28
+ argsIgnorePattern: '^_',
29
+ varsIgnorePattern: '^_',
30
+ caughtErrorsIgnorePattern: '^_',
31
+ destructuredArrayIgnorePattern: '^_'
32
+ }
33
+ ],
34
+ '@typescript-eslint/no-explicit-any': 'off',
35
+ curly: ['error', 'all'],
36
+ 'no-var': 'error',
37
+ 'prefer-const': 'error'
38
+ }
39
+ }
40
+ ])
package/index.html ADDED
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>BYOE Notes</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@interop/byoe-react-template",
3
+ "description": "Template for building 'Bring Your Own Everything' (BYOE) apps on Wallet Attached Storage: a Vite + React + TypeScript SPA with DID Auth login via a CHAPI wallet, local-first encrypted storage, and background sync, built on @interop/was-react.",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc --noEmit -p tsconfig.dev.json && vite build",
9
+ "typecheck": "tsc --noEmit -p tsconfig.dev.json",
10
+ "fix": "eslint --fix src test && pnpm run format",
11
+ "format": "prettier --write \"src/**/*.{ts,tsx}\" \"test/**/*.ts\" \"scripts/**/*.ts\" \"*.md\"",
12
+ "lint": "eslint src test scripts",
13
+ "test": "pnpm run lint && pnpm run test:node",
14
+ "test:browser": "playwright test",
15
+ "test:browser:was": "playwright test -c playwright.was.config.ts",
16
+ "test:browser:wallet": "playwright test -c playwright.wallet.config.ts",
17
+ "test:node": "vitest run",
18
+ "provision:dev": "tsx scripts/provision-dev-grants.ts"
19
+ },
20
+ "dependencies": {
21
+ "@emotion/react": "^11.14.0",
22
+ "@emotion/styled": "^11.14.1",
23
+ "@interop/was-react": "^0.1.3",
24
+ "@mui/icons-material": "^9.0.0",
25
+ "@mui/material": "^9.0.0",
26
+ "react": "^19.2.4",
27
+ "react-dom": "^19.2.4",
28
+ "react-router": "^8.1.0",
29
+ "rxdb": "^17.0.0",
30
+ "uuidv7": "^1.2.1",
31
+ "zustand": "^5.0.12"
32
+ },
33
+ "devDependencies": {
34
+ "@eslint/js": "^10.0.1",
35
+ "@playwright/test": "^1.60.0",
36
+ "@types/node": "^26.0.1",
37
+ "@types/react": "^19.2.14",
38
+ "@types/react-dom": "^19.2.3",
39
+ "@vitejs/plugin-react": "^6.0.0",
40
+ "eslint": "^10.4.0",
41
+ "eslint-config-prettier": "^10.1.8",
42
+ "fake-indexeddb": "^6.0.0",
43
+ "globals": "^17.6.0",
44
+ "prettier": "^3.8.3",
45
+ "tsx": "^4.20.0",
46
+ "typescript": "^6.0.3",
47
+ "typescript-eslint": "^8.59.4",
48
+ "vite": "^8.0.14",
49
+ "vitest": "^4.1.7"
50
+ },
51
+ "packageManager": "pnpm@11.9.0",
52
+ "engines": {
53
+ "node": ">=24.0"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "author": "Interop Alliance (https://github.com/interop-alliance/)",
59
+ "license": "MIT"
60
+ }