@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.
- package/.github/workflows/ci.yml +44 -0
- package/.github/workflows/publish.yml +38 -0
- package/CHANGELOG.md +20 -0
- package/CLAUDE.md +55 -0
- package/LICENSE.md +20 -0
- package/README.md +217 -0
- package/_spec/phase2-handoff.md +143 -0
- package/eslint.config.js +40 -0
- package/index.html +12 -0
- package/package.json +60 -0
- package/playwright.config.ts +38 -0
- package/playwright.wallet.config.ts +82 -0
- package/playwright.was.config.ts +56 -0
- package/pnpm-workspace.yaml +11 -0
- package/prettier.config.js +9 -0
- package/scripts/provision-dev-grants.ts +42 -0
- package/src/App.tsx +34 -0
- package/src/app.config.ts +73 -0
- package/src/components/AppShell.tsx +58 -0
- package/src/components/DevGate.tsx +51 -0
- package/src/dev/bootstrap.ts +54 -0
- package/src/dev/devSeed.ts +13 -0
- package/src/dev/devSync.ts +80 -0
- package/src/main.tsx +24 -0
- package/src/pages/LoginPage.tsx +76 -0
- package/src/pages/NotesPage.tsx +166 -0
- package/src/stores/notes.ts +41 -0
- package/src/vite-env.d.ts +1 -0
- package/test/browser/notes.spec.ts +82 -0
- package/test/browser-wallet/walletLogin.spec.ts +312 -0
- package/test/browser-was/globalSetup.ts +6 -0
- package/test/browser-was/globalTeardown.ts +6 -0
- package/test/browser-was/replication.spec.ts +154 -0
- package/test/browser-was/serverLifecycle.ts +92 -0
- package/test/node/notesStore.test.ts +149 -0
- package/tsconfig.dev.json +17 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +18 -0
|
@@ -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
|
+
[](https://github.com/interop-alliance/byoe-react-template/actions?query=workflow%3A%22CI%22)
|
|
4
|
+
[](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.
|
package/eslint.config.js
ADDED
|
@@ -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
|
+
}
|