Files
suaveplan/apps/testbed/README.md
2026-05-27 23:29:53 +01:00

175 lines
23 KiB
Markdown

# @suaveplan/testbed
A live demonstration app for every `@suaveplan` library, running against real services. Each page exercises the actual library API — no stubs, no mocks.
<!-- begin:auto-docs -->
## Documentation tree
- [console-panel](./src/client/components/layout/console-panel.md) — `ConsolePanel` intercepts `window.console.log`, `warn`, and `error` on mount and renders each captured message in an in…
- [header](./src/client/components/layout/header.md) — `Header` is the top-level application bar for the testbed. It displays the app title on the left and renders `LocaleSwi…
- [locale-switcher](./src/client/components/layout/locale-switcher.md) — `LocaleSwitcher` renders a `<select>` allowing the user to switch between the four supported locales (`en`, `fr`, `de`,…
- [sidebar](./src/client/components/layout/sidebar.md) — `Sidebar` renders the primary navigation for the testbed as a vertically scrollable nav panel. Routes are grouped by th…
- [theme-toggle](./src/client/components/layout/theme-toggle.md) — `ThemeToggle` is a button that toggles between `light` and `dark` visual themes. It applies the theme by setting a `dat…
- [code-block](./src/client/components/shared/code-block.md) — `CodeBlock` renders a preformatted code snippet with monospace styling and an optional language label. It is used on li…
- [demo-card](./src/client/components/shared/demo-card.md) — `DemoCard` is the standard wrapper for every interactive library demonstration in the testbed. It provides a consistent…
- [status-badge](./src/client/components/shared/status-badge.md) — `StatusBadge` renders a colored pill badge indicating `ok`, `error`, or `pending` state. It is used on library demo pag…
- [crypto](./src/client/pages/crypto/crypto-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [crypto-page](./src/client/pages/crypto/crypto-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [datetime](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [datetime-page](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [extension-test](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [extension-test-page](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [feature-flags](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [feature-flags-page](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [home](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [home-page](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [i18n](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [i18n-page](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [logger](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [logger-page](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [network-broadcast](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [network-broadcast-page](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [network-webrtc](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [network-webrtc-page](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [network-websocket](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [network-websocket-page](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [storage-s3](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [storage-s3-page](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [surrealdb](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
- [surrealdb-page](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- end:auto-docs --> <!-- en…
<!-- end:auto-docs --> - [crypto-page](./src/client/pages/crypto/crypto-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [datetime](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [datetime-page](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [extension-test](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [extension-test-page](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [feature-flags](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [feature-flags-page](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [home](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [home-page](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [i18n](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [i18n-page](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [logger](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [logger-page](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-broadcast](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-broadcast-page](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-webrtc](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-webrtc-page](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-websocket](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-websocket-page](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage-s3](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [storage-s3-page](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [surrealdb](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [surrealdb-page](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [crypto-page](./src/client/pages/crypto/crypto-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [datetime](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [datetime-page](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [extension-test](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [extension-test-page](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [feature-flags](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [feature-flags-page](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [home](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [home-page](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [i18n](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [i18n-page](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [logger](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [logger-page](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-broadcast](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-broadcast-page](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-webrtc](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-webrtc-page](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-websocket](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-websocket-page](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage-s3](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [storage-s3-page](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [surrealdb](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [surrealdb-page](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [crypto-page](./src/client/pages/crypto/crypto-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [datetime](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [datetime-page](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [extension-test](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [extension-test-page](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [feature-flags](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [feature-flags-page](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [home](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [home-page](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [i18n](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [i18n-page](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [logger](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [logger-page](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-broadcast](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-broadcast-page](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-webrtc](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-webrtc-page](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-websocket](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-websocket-page](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage-s3](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [storage-s3-page](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [surrealdb](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [surrealdb-page](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [crypto-page](./src/client/pages/crypto/crypto-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [datetime](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [datetime-page](./src/client/pages/datetime/datetime-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [extension-test](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [extension-test-page](./src/client/pages/extension-test/extension-test-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [feature-flags](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [feature-flags-page](./src/client/pages/feature-flags/feature-flags-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [home](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [home-page](./src/client/pages/home/home-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [i18n](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [i18n-page](./src/client/pages/i18n/i18n-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [logger](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [logger-page](./src/client/pages/logger/logger-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-broadcast](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-broadcast-page](./src/client/pages/network-broadcast/network-broadcast-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-webrtc](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-webrtc-page](./src/client/pages/network-webrtc/network-webrtc-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [network-websocket](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [network-websocket-page](./src/client/pages/network-websocket/network-websocket-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [storage-s3](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [storage-s3-page](./src/client/pages/storage-s3/storage-s3-page.md) — <!-- begin:auto-docs --> _(no submodules)_- [surrealdb](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_ - [surrealdb-page](./src/client/pages/surrealdb/surrealdb-page.md) — <!-- begin:auto-docs --> _(no submodules)_
## Overview
The testbed is a React 19 SPA served by a Hono/Bun server. The client runs library demos entirely in the browser (crypto, logger, datetime, i18n, feature-flags) or proxies requests to the server for service-backed operations (S3, SurrealDB, WebRTC signalling, WebSocket relay).
## Quick Start
```bash
# From monorepo root
bun install
bun run dev --filter=@suaveplan/testbed
# Or directly in this directory
bun run dev
```
The app starts at **http://localhost:3200**.
## Services
Optional services are detected at page load via a `/api/*/status` health check. Pages gracefully degrade when services are unavailable.
| Service | Env Vars | Default |
|-------------|-------------------------------------------------------------------------------------------|---------|
| Garage S3 | `GARAGE_S3_ENDPOINT`, `GARAGE_S3_ACCESS_KEY_ID`, `GARAGE_S3_SECRET_ACCESS_KEY`, `GARAGE_S3_BUCKET` | `testbed` |
| SurrealDB | `SURREALDB_URL`, `SURREALDB_USERNAME`, `SURREALDB_PASSWORD` | `root/root` |
## Running E2E Tests
```bash
# All browsers
bun run test:e2e
# Chromium only
bunx playwright test --project=chromium
# Specific spec file
bunx playwright test e2e/libraries/crypto.spec.ts
# Extension tests (requires chromium-extensions project)
bunx playwright test --project=chromium-extensions e2e/extensions/
```
The Playwright web server auto-starts `bun run dev` on port 3200 if not already running.
## Multi-Session Tests
Multi-session tests in `e2e/multi-session/` open multiple browser contexts to simulate real-time scenarios:
- **broadcast-channel.spec.ts** — sender broadcasts via `BroadcastChannel`; receiver tab receives messages. Runs entirely in-browser (no server needed).
- **websocket-gateway.spec.ts** — sender and receiver connect to the WebSocket relay server at `/api/ws`.
- **webrtc-mesh.spec.ts** — host and peer pages complete a WebRTC handshake via `/api/signalling`.
- **webrtc-server-failover.spec.ts** — verifies graceful handling when the signalling endpoint returns 503.
All multi-session tests use `createMultiSession` from `@suaveplan/testing-e2e/multi-session`.
## Extension Tests
Extension tests in `e2e/extensions/` use `launchWithExtension` from `@suaveplan/testing-e2e/extension-fixture` to launch Chromium with a sideloaded test extension.
Place your test extension at `e2e/fixtures/test-extension/`. The extension should inject `<div id="extension-injected">...</div>` into the page for detection.
```bash
bunx playwright test --project=chromium-extensions
```
## `data-testid` Contract
Every page exposes these stable selectors for E2E tests:
| Selector | Element |
|----------------------------------|--------------------------------|
| `[data-testid="page-title"]` | `<h1>` with page/library name |
| `[data-testid="demo-card"]` | Wraps interactive demo area |
| `[data-testid="output"]` | Live result display |
| `[data-testid="console-panel"]` | Console capture panel |
| `[data-testid="header"]` | Top navigation bar |
| `[data-testid="sidebar"]` | Left navigation sidebar |
| `[data-testid="library-card"]` | Library cards on home page |
| `[data-testid="locale-select"]` | Locale dropdown on i18n page |
| `[data-testid="message-input"]` | WS/WebRTC/Broadcast input |
| `[data-testid="send-button"]` | Send button on network pages |
| `[data-testid="extension-mount"]` | Extension injection target |
| `[data-status="<status>"]` | Connection status indicator |
## Console Panel
Each page has a `[data-testid="console-panel"]` section that documents the library operations in use. It is informational only — it does not capture actual browser console output. Console baseline tests use `attachConsoleMonitor` from `@suaveplan/testing-e2e/console-monitor` to assert no unexpected errors.
## Test Control API
In test mode (`NODE_ENV=test`), the server mounts `testControlRoutes` at `/api/test-control`:
| Method | Path | Description |
|--------|------------------------------------|-----------------------------------------------|
| POST | `/api/test-control/disable-route` | Temporarily returns 503 for a given API route |
Used by `simulateServerFailure` from `@suaveplan/testing-e2e/multi-session`.
## Env-Bridge Variables
| Variable | Used By | Description |
|-----------------------------|------------------|--------------------------------------|
| `GARAGE_S3_ENDPOINT` | Storage S3 page | Garage S3-compatible endpoint URL |
| `GARAGE_S3_ACCESS_KEY_ID` | Storage S3 page | S3 access key ID |
| `GARAGE_S3_SECRET_ACCESS_KEY` | Storage S3 page | S3 secret access key |
| `GARAGE_S3_BUCKET` | Storage S3 page | S3 bucket name (default: `testbed`) |
| `SURREALDB_URL` | SurrealDB page | SurrealDB connection URL |
| `SURREALDB_USERNAME` | SurrealDB page | SurrealDB username (default: `root`) |
| `SURREALDB_PASSWORD` | SurrealDB page | SurrealDB password (default: `root`) |
| `PORT` | Server | HTTP listen port (default: `3200`) |
## Routes Manifest
| Path | Library/Feature | Category | Services |
|-------------------------|-----------------------------|--------------|----------|
| `/` | Testbed home | home | |
| `/crypto` | `@suaveplan/crypto` | crypto | |
| `/logger` | `@suaveplan/logger` | observability| |
| `/datetime` | `@suaveplan/datetime` | primitives | |
| `/i18n` | `@suaveplan/i18n` | primitives | |
| `/feature-flags` | `@suaveplan/feature-flags` | primitives | |
| `/storage-s3` | Storage — S3 | storage | Garage |
| `/surrealdb` | SurrealDB | database | SurrealDB|
| `/network-webrtc` | Network — WebRTC | network | Signalling|
| `/network-websocket` | Network — WebSocket | network | WS relay |
| `/network-broadcast` | Network — BroadcastChannel | network | |
| `/extension-test` | Extension Test | extensions | |
## Port Allocation
| Port | Service |
|------|--------------------------|
| 3200 | Testbed dev/prod server |
| 6042 | Storybook |