diff --git a/.dockerignore b/.dockerignore index fe092c9..6c7c49c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,7 @@ **/node_modules **/dist !apps/admin/internal/panel/build +!apps/web/internal/client/build bin coverage.* .complement diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00fbd2d..191c183 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -289,3 +289,89 @@ jobs: status="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8009/_matrix/client/v3/sync \ -H "Authorization: Bearer $token")" test "$status" = "404" + + web: + name: Web client + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: server/go.sum + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Typecheck and build the frontend + run: | + pnpm --filter @warp/web typecheck + pnpm --filter @warp/web build + + - name: Verify formatting + working-directory: apps/web + run: | + unformatted="$(gofmt -l .)" + if [ -n "$unformatted" ]; then + echo "These files need gofmt:" + echo "$unformatted" + exit 1 + fi + + - name: Vet and test warp-web + working-directory: apps/web + run: | + go vet ./... + go test -count=1 ./... + + - name: Smoke test warp-web against the homeserver + run: | + set -euo pipefail + make build + (cd apps/web && go build -o ../../bin/warp-web ./cmd/warp-web) + sed 's/^ server_name: .*/ server_name: ci.example/' warp.yaml.example > warp.yaml + ./bin/warp generate-keys + ./bin/warp migrate + ./bin/warp create-account --username ci-user --password hunter2hunter2 + ./bin/warp serve & + ./bin/warp-web -homeserver http://127.0.0.1:8008 -listen 127.0.0.1:8080 & + trap 'kill $(jobs -p) 2>/dev/null || true' EXIT + + for _ in $(seq 1 30); do + if curl -fsS http://127.0.0.1:8008/healthz >/dev/null 2>&1 && + curl -fsS http://127.0.0.1:8080/healthz >/dev/null 2>&1; then + break + fi + sleep 1 + done + + # The client is embedded, its routes fall back to index.html, and + # its runtime config says to use the proxy. + curl -fsS http://127.0.0.1:8080/ | grep -q '
' + curl -fsS 'http://127.0.0.1:8080/room/!abc:ci.example' | grep -q '
' + curl -fsS http://127.0.0.1:8080/config.json | grep -q '"homeserver_url":""' + + # The client API goes through the proxy, trailing-slash routes too... + token="$(curl -fsS http://127.0.0.1:8080/_matrix/client/v3/login -d '{ + "type": "m.login.password", + "identifier": {"type": "m.id.user", "user": "ci-user"}, + "password": "hunter2hunter2" + }' | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')" + test -n "$token" + curl -fsS 'http://127.0.0.1:8080/_matrix/client/v3/sync?timeout=0' \ + -H "Authorization: Bearer $token" | grep -q '"next_batch"' + curl -fsS http://127.0.0.1:8080/_matrix/client/v3/pushrules/ \ + -H "Authorization: Bearer $token" | grep -q '"global"' + + # ...and the admin API does not. + status="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/_warp/admin/v1/stats \ + -H "Authorization: Bearer $token")" + test "$status" = "404" diff --git a/.gitignore b/.gitignore index a7d1daa..778b047 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ Thumbs.db .idea/ .vscode/* !.vscode/extensions.json + +# Local planning and design material +/PLAN/ +/DESIGN/ diff --git a/Makefile b/Makefile index c47fac9..52eb390 100644 --- a/Makefile +++ b/Makefile @@ -128,6 +128,38 @@ admin-test: ## Typecheck the frontend and test warp-admin admin-docker: ## Build the warp-admin container image docker build -t $(ADMIN_IMAGE):$(VERSION) -f $(ADMIN_DIR)/Dockerfile . +# --- Web client (apps/web) -------------------------------------------- +# Optional too: warp-web serves the built client and proxies its Matrix +# requests to the homeserver. + +WEB_DIR := apps/web +WEB_IMAGE ?= warp-web + +.PHONY: web-web +web-web: ## Build the web client frontend into apps/web/internal/client/build/dist + $(PNPM) --filter @warp/web build + +.PHONY: web +web: web-web ## Build bin/warp-web with the client embedded + cd $(WEB_DIR) && $(GO) build -ldflags "-X main.version=$(VERSION)" -o ../../$(BIN_DIR)/warp-web$(EXE) ./cmd/warp-web + +.PHONY: web-run +web-run: web ## Run warp-web against http://localhost:8008 + ./$(BIN_DIR)/warp-web$(EXE) -homeserver http://localhost:8008 + +.PHONY: web-dev +web-dev: ## Run the client with hot reload on :5175, proxied to localhost:8008 + $(PNPM) --filter @warp/web dev + +.PHONY: web-test +web-test: ## Typecheck the frontend and test warp-web + $(PNPM) -r typecheck + cd $(WEB_DIR) && $(GO) vet ./... && $(GO) test ./... + +.PHONY: web-docker +web-docker: ## Build the warp-web container image + docker build -t $(WEB_IMAGE):$(VERSION) -f $(WEB_DIR)/Dockerfile . + .PHONY: complement-image complement-image: ## Build the Complement homeserver image docker build -t $(COMPLEMENT_IMAGE) -f $(SERVER_DIR)/test/complement/Dockerfile . diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index 26b3faf..0000000 --- a/PLAN.md +++ /dev/null @@ -1,418 +0,0 @@ -# Warp — a Matrix homeserver and client suite in Go - -Project plan. Warp is a from-scratch [Matrix](https://spec.matrix.org) homeserver -written in Go, taking architectural lessons from -[Dendrite](https://github.com/element-hq/dendrite) without copying its code, -plus a family of first-party clients (admin panel, web, desktop, mobile). - ---- - -## 1. Goals and non-goals - -**Goals** - -- Spec-conformant homeserver: Client-Server API, Server-Server (federation) API, - Application Service API, Push Gateway client side. -- **Federation is optional.** Warp must run as a fully isolated, private - homeserver with federation switched off, and be switchable at runtime. -- Single binary, single process ("monolith"). Dendrite's polylith mode was - deprecated for good reason — the internal component boundaries stay, the - network hops go. -- Three storage drivers: **PostgreSQL** (production), **SQLite** (dev / - single-user), **PocketBase** (all-in-one appliance deployments). -- Pass [Complement](https://github.com/matrix-org/complement) (the official - integration test suite, itself written in Go). -- Federate with Synapse, Dendrite, Conduit/Conduwuit/Tuwunel when enabled. -- First-party clients: admin web panel, web client, desktop client, Android - app (iOS later), all sharing as much code as possible. - -**Non-goals (for now)** - -- Identity server, SSO/OIDC provider (delegate to MAS later), VoIP/TURN - provisioning, legacy Sytest. - ---- - -## 2. Key decisions - -| Decision | Choice | Why | -|---|---|---| -| Go version | 1.26, `net/http` with pattern routing (`GET /_matrix/client/v3/...`) | No router dependency needed since Go 1.22. | -| Event core (canonical JSON, hashing, ed25519 signing, auth rules, state resolution v2) | **Own implementation**, tested against `gomatrixserverlib` test vectors and Complement | This is the heart of a homeserver and the point of the project. Fallback: import `gomatrixserverlib` if it stalls the schedule. | -| Internal messaging | Go interfaces + channels, one process | Dendrite embeds NATS JetStream; we don't need durable internal queues until we do. Design the producer/consumer interfaces so NATS can be dropped in later. | -| Storage | `database/sql` + hand-written SQL behind a per-component `Storage` interface; drivers: `pgx` (Postgres), `modernc.org/sqlite` (pure Go SQLite), PocketBase (embedded `core.App`, see §4) | Same dual-backend model as Dendrite plus one; no ORM. | -| Migrations | Embedded SQL files, per component, `embed.FS` | Simple, reviewable. | -| Config | Single YAML file + env overrides; a small set of settings (federation on/off, registration on/off) also mutable at runtime via admin API and persisted in DB | | -| Logging / metrics / tracing | `log/slog`, Prometheus `/metrics`, OpenTelemetry (later) | | -| Room versions | Implement the latest stable room version first (the one `m.room.create` defaults to in the current spec), then backfill older versions as needed for federation | Old room versions matter only when joining old rooms elsewhere. | -| Sync | `/sync` v3 first; Simplified Sliding Sync (MSC4186) as a later phase | Element X and our own mobile app want sliding sync; classic clients require `/sync`. | -| Frontend stack (admin + web client) | Vue 3 + TypeScript + Vite + Pinia; **Tailwind CSS** with a hand-rolled component set in `apps/ui` (Headless UI / Radix Vue for accessible primitives: dialogs, menus, listboxes) | Same UI code runs in the browser and inside the Wails desktop shell; Tailwind keeps the bundle small and the design system ours. | -| Desktop shell | **Wails v3** (Go host + webview) reusing the Vue web client | Pure-Go UI toolkits (Fyne/Gio) would mean a second UI codebase. | -| Mobile | Flutter + `matrix_dart_sdk` (famedly) with vodozemac for E2EE | Mature SDK; same codebase later ships to iOS. | -| Client protocol/crypto in Go | `mautrix-go` (+ `goolm` pure-Go Olm/Megolm) for the desktop host and any Go-side tooling; `matrix-js-sdk` + `matrix-sdk-crypto-wasm` in the browser | Do not write our own client-side crypto. | - ---- - -## 3. Federation switch - -`federation.enabled` is a first-class mode, not an afterthought. - -```yaml -federation: - enabled: false # default off for a new install - allowlist: [] # if non-empty, only these server names may federate - denylist: [] - allow_runtime_toggle: true -``` - -**When disabled:** - -| Area | Behaviour | -|---|---| -| Routes | `/_matrix/federation/*`, `/_matrix/key/*`, `/.well-known/matrix/server` are not registered (404). | -| Outbound | Transaction queue, key fetcher, remote media fetch, remote `keys/query`, remote profile/directory lookups are not started. | -| Client API | `/join` with `server_name` for a room not known locally → `M_FORBIDDEN` with a clear message; inviting `@user:otherserver` → `M_FORBIDDEN`; `publicRooms?server=` → `M_FORBIDDEN`. | -| Room IDs / event IDs | Unchanged — still `!id:warp.example`. Rooms created offline federate normally if federation is later enabled. | -| Signing | Events are still signed with the server key; only publication/verification of *remote* keys is skipped. | - -**Runtime toggle:** `PUT /_warp/admin/v1/federation {"enabled": true}` (and the -admin panel switch). Implemented as a `federation.Module` with `Start(ctx)` / -`Stop()`; the HTTP mux delegates federation routes through a gate that checks -the live flag, so no restart is required. The setting is persisted in the -`server_settings` table and overrides the YAML value on boot if -`allow_runtime_toggle` is true. - -**Allowlist mode** is the middle ground for "federate only with my other -servers": all remote traffic is filtered by server name at the auth layer and in -the outbound queue. - ---- - -## 4. Storage drivers - -All components talk to storage through Go interfaces (e.g. -`roomserver.Database`, `userapi.Database`). Each driver implements them. - -| Driver | Engine | Target use | Notes | -|---|---|---|---| -| `postgres` | PostgreSQL 15+ via `pgx` | Production, multi-user, large rooms | Primary; Complement runs against this. | -| `sqlite` | `modernc.org/sqlite` (no cgo), WAL mode | Dev, tests, single-user | Serialised writer; fine up to a few hundred users. | -| `pocketbase` | Embedded [PocketBase](https://pocketbase.io) (`core.App`) on its own SQLite file | "Appliance" deployments: one binary that also provides admin auth, file storage (local/S3) for media, and realtime for the admin panel | Records are stored via PocketBase collections; heavier than raw SQLite and its record model is a poor fit for the event DAG, so **the roomserver/syncapi tables bypass PocketBase's collection API and use its underlying `dbx` connection directly**. PocketBase adds value for `mediaapi` (its filesystem abstraction) and for admin-panel auth. Marked experimental until it passes the same test suite as `sqlite`. | - -Rules: - -- One SQL dialect per driver, kept in `internal/storage/{postgres,sqlite,pocketbase}`. -- Every storage test runs against all three drivers (`go test -tags=storage_all`). -- Migrations are per-driver; `warp migrate` runs them. - ---- - -## 5. Server architecture - -``` - +--------------------------------------------------+ - clients ------> | clientapi (C-S REST handlers, auth, ratelimit) | - +--------------------------------------------------+ - other HS -----> | federationapi [gated by federation.enabled] | - +-------------+-------------+----------------------+ - | userapi | roomserver | syncapi | - | accounts | event DAG | read model / streams| - | devices | auth rules | /sync, /messages | - | tokens | state res | filters, tokens | - | push rules | redactions | | - +-------------+-------------+----------------------+ - | keyserver | mediaapi | appservice | - | device keys| upload/dl | registration, txns | - | OTKs, x-sig| thumbnails | | - +-------------+-------------+----------------------+ - | adminapi (/_warp/admin/*) + embedded admin SPA | - +--------------------------------------------------+ - | internal/matrix: event, canonicaljson, signing, | - | roomversion, auth, stateres | - +--------------------------------------------------+ - | storage: postgres | sqlite | pocketbase | - +--------------------------------------------------+ -``` - -**Data-flow principle (borrowed from Dendrite):** the roomserver is the single -writer for room events. Every event — local or federated — goes in through the -roomserver, which authorises it, resolves state, persists it, and then emits an -"output event" that the syncapi, federationapi (outbound, if enabled), -appservice, and push consumers subscribe to. Nothing else writes room state. - ---- - -## 6. Repository layout (monorepo) - -``` -Warp/ -├── go.work Go workspace tying the Go modules together -├── server/ Go module: the homeserver -│ ├── cmd/warp/ serve, generate-keys, create-account, migrate -│ ├── internal/ -│ │ ├── config/ -│ │ ├── httputil/ -│ │ ├── matrix/ canonicaljson, event, signing, roomversion, auth, stateres -│ │ ├── clientapi/ federationapi/ roomserver/ syncapi/ userapi/ -│ │ ├── keyserver/ mediaapi/ appservice/ pushserver/ adminapi/ -│ │ └── storage/{postgres,sqlite,pocketbase,migrations}/ -│ ├── web/admin/ build output of apps/admin, embedded via embed.FS -│ └── test/{complement,vectors}/ -├── apps/ -│ ├── ui/ shared Vue 3 component library + Matrix client store (Pinia) -│ ├── admin/ admin web panel (Vue) — built into server/web/admin -│ ├── web/ web client (Vue SPA) + Go host (`cmd/warp-web`) -│ ├── desktop/ Wails v3 app: Go host (mautrix-go) + apps/web frontend -│ └── mobile/ Flutter app: Android now, iOS later -├── docs/ -├── Dockerfile -├── Makefile -└── warp.yaml.example -``` - -Frontend packages are managed with `pnpm` workspaces (`apps/ui`, `apps/admin`, -`apps/web`); Go modules with `go.work` (`server`, `apps/web`, `apps/desktop`). - ---- - -## 7. Server phases - -Each phase ends with something runnable and a Complement subset passing. - -### Phase 0 — Bootstrap (1 week) - -- `go mod init`, layout above, `Makefile` (build/test/lint/complement), `golangci-lint`, GitHub Actions CI. -- Config loading, `slog` logging, graceful shutdown, `/healthz`, `/metrics`. -- HTTP server skeleton with Matrix error envelope (`errcode`, `error`), CORS, - `GET /_matrix/client/versions`, `/.well-known/matrix/client` (and `/server` - only when federation is enabled). -- Storage interfaces + migrations runner; `sqlite` driver first, `postgres` - second, `pocketbase` stub compiled but returning `ErrNotImplemented`. -- `warp generate-keys` (ed25519 signing key file). -- `server_settings` table and the runtime-settings service (federation flag, - registration flag). - -### Phase 1 — Accounts & sessions (1–2 weeks) - -- **userapi**: users, passwords (argon2id), devices, access tokens, - refresh tokens, account data, admin flag. -- C-S endpoints: `register` (UIA with `m.login.dummy` / registration token), - `login` (`m.login.password`), `logout`, `logout/all`, `account/whoami`, - `devices/*`, `profile/*`, `user/{id}/account_data/*`, `capabilities`. -- Auth middleware, per-endpoint rate limiting (`M_LIMIT_EXCEEDED`). -- `warp create-account [--admin]` CLI. -- **adminapi v1** skeleton: admin-only auth, `GET/PUT /federation`, - `GET/PUT /registration`, `GET /users`, `POST /users`, `GET /stats`. -- Complement: `TestRegistration`, `TestLogin`, `TestDeviceManagement`. - -### Phase 2 — Event core (2–3 weeks, the hard part) - -Pure library code under `internal/matrix`, no HTTP, no DB. - -- Canonical JSON, event/PDU model, hashing, redaction algorithm. -- ed25519 signing and verification, key IDs. -- Room version registry. -- Authorisation rules (full spec list). -- State resolution v2. - -### Phase 3 — Rooms, local only (2–3 weeks) - -- **roomserver** storage and input pipeline (validate → auth → resolve → - persist → emit). -- C-S: `createRoom`, `join`, `leave`, `invite`, `kick`, `ban`, - `send/{type}/{txnId}`, `state/*`, `redact`, `rooms/{id}/event/{id}`, - `members`, `joined_members`, aliases, `publicRooms`. -- Federation-disabled behaviours from §3 enforced here (remote joins/invites - rejected). -- adminapi: room list, room detail, purge room, force-leave. -- Complement: `TestRoomCreate`, `TestRoomState`, `TestMembership*`, `TestRedactions`. - -### Phase 4 — Sync (2 weeks) - -- **syncapi** read model, stream tokens, `GET /sync`, `/messages`, - `/context`, filters, lazy-loading members. -- EDUs: typing, receipts, presence (config flag), `to_device`. -- Complement: `TestSync*`, `TestMessagesPagination`, `TestTyping`, `TestReceipts`. - -→ **Milestone M1**: Element Web works against Warp with federation off. - The admin panel and web client (§8) can now be developed against a real server. - -### Phase 5 — Federation (3–4 weeks) - -- `federation.Module` with `Start/Stop`, route gate, allow/deny lists. -- Server keys, discovery, `X-Matrix` auth, `/send`, joins/leaves/invites, - `/state`, `/state_ids`, `/backfill`, `/get_missing_events`, queries. -- Outbound queue with backoff, blacklist, persistence. -- Complement federation tests. - -→ **Milestone M2**: Warp joins `#matrix:matrix.org`; toggling federation off - at runtime cleanly stops all remote traffic and back on resumes it. - -### Phase 6 — Media (1 week) - -- `mediaapi`, thumbnails, authenticated media, remote media cache (federation - only). PocketBase driver uses PocketBase's filesystem abstraction (local/S3). - -### Phase 7 — E2EE support (2 weeks) - -- `keyserver`: device keys, OTKs, fallback keys, cross-signing, device list - tracking (local always; federated only when enabled), key backup, - `sendToDevice`. - -### Phase 8 — Push & notifications (1 week) - -- Push rules, pushers, notification counts, push gateway client. Needed by the - mobile app. - -### Phase 9 — Application services & admin completion (1–2 weeks) - -- AS registration, namespaces, transactions, masquerading. -- adminapi: full user management (deactivate, reset password, devices, admin - flag), server notices, media quota, registration tokens, federation - allow/deny list editing, audit log. - -### Phase 10 — Hardening & ops - -- Simplified Sliding Sync (MSC4186), spaces, threads, search. -- PocketBase driver to parity with `sqlite`. -- Tracing, dashboards, Dockerfile, compose/Helm examples, load testing. - ---- - -## 8. Client sub-projects - -All clients speak plain Matrix C-S API, so they also work against Synapse — and -Element works against Warp. Nothing proprietary in the wire protocol except -the `/_warp/admin/*` API used by the admin panel. - -### 8.1 Admin web panel — Go + Vue (`apps/admin`) - -- **Stack**: Vue 3 + TS + Vite + Pinia + Tailwind CSS, components from - `apps/ui`. Built assets embedded into the `warp` binary and - served at `/_warp/admin/`; no separate process. -- **Backend**: `server/internal/adminapi` (Go). Auth = Matrix login of a user - with the admin flag; the panel uses the normal access token. -- **Features by priority**: - 1. Dashboard: users, rooms, events/min, federation status, storage driver, version. - 2. Federation switch, allow/deny list editor, per-server health (queue depth, backoff). - 3. Users: list/search, create, deactivate, reset password, devices, admin flag, registration tokens. - 4. Rooms: list/search, members, state viewer, purge, force-leave, alias management. - 5. Media: usage per user, quotas, purge remote media. - 6. Settings: registration on/off, rate limits, server notices, appservice registrations. - 7. Audit log of admin actions. -- **Starts**: after server Phase 1 (dashboard + users + federation switch), - grows with each server phase. - -### 8.2 Web client — Go + Vue (`apps/web`) - -- **Frontend**: Vue 3 SPA in `apps/web/frontend`, built on the shared - `apps/ui` component library and a Pinia-based Matrix store wrapping - `matrix-js-sdk` (sync, rooms, timeline) with `matrix-sdk-crypto-wasm` for E2EE. - Go is not a good fit for in-browser Matrix crypto; the Rust-Wasm crypto is - what Element uses and is audited. -- **Go host** (`apps/web/cmd/warp-web`): serves the SPA, injects runtime config - (homeserver URL, branding), handles `/.well-known` for client discovery, - optional reverse proxy to the homeserver to avoid CORS. Can also be embedded - into the `warp` binary behind `web_client.enabled: true`. -- **Feature order**: login/register → room list + timeline → send text/media → - read receipts, typing → invites/joins, room settings → E2EE (device - verification, key backup) → threads, reactions, spaces → voice messages, - calls (much later). -- **Starts**: after server M1. - -### 8.3 Desktop client — Go (`apps/desktop`) - -- **Stack**: Wails v3. The UI is the same Vue frontend as `apps/web` (imported - as a workspace package), so web and desktop share every screen. -- **Go side**: `mautrix-go` client with `goolm` (pure Go Olm/Megolm, no cgo) - for E2EE, local encrypted SQLite store for sync state and keys, OS keychain - for the access token, native notifications, tray icon, auto-update. - Rationale: on desktop, doing sync + crypto in Go instead of in the webview - gives a persistent local store, background sync, and lets the frontend stay - thin — the Vue store talks to Go via Wails bindings instead of - `matrix-js-sdk`. The store therefore has two backends (`js-sdk` in the - browser, `wails` on desktop) behind one interface. -- **Targets**: Windows, macOS, Linux. -- **Starts**: once the web client has a stable room list + timeline. - -### 8.4 Android app — Flutter (`apps/mobile`) - -- **Stack**: Flutter + `matrix_dart_sdk` (famedly) + `flutter_vodozemac` for - E2EE, `flutter_secure_storage`, FCM/UnifiedPush for push (via the server's - push gateway support from Phase 8), Riverpod for state. -- **Feature order**: login → room list → timeline + send → push notifications - → E2EE + verification → media → calls (later). -- Uses Simplified Sliding Sync when the server has it (Phase 10) for fast - startup; falls back to `/sync`. -- **Starts**: after server Phase 8 (push) for a useful MVP; UI work can begin - after M1. - -### 8.5 iPhone app — future - -- Same Flutter codebase; work items are APNs push (needs a push gateway such as - Sygnal, or a Warp-native one), iOS-specific secure storage and keychain, - App Store review. Deferred until the Android app is stable. - ---- - -## 9. Testing strategy - -| Level | Tool | What | -|---|---|---| -| Unit | `go test`, table-driven | `internal/matrix` (100% of spec examples); storage against all three drivers | -| Component | `httptest` | Each API package with a fake roomserver/userapi; federation gate tests (routes 404 when disabled, resume when enabled) | -| Integration | **Complement** (Docker) | Two profiles: `federation=off` (C-S tests only) and `federation=on`; track pass counts as the primary progress metric | -| Interop | Manual + scripted | Federate a dev instance with a Synapse in Docker; join `#matrix:matrix.org` | -| Fuzz | `go test -fuzz` | Canonical JSON, event parsing, X-Matrix header parsing, federation PDU intake | -| Frontend | Vitest + Playwright | `apps/ui` components; end-to-end login/send/receive against a Warp container | -| Mobile | Flutter `test` + `integration_test` | Against the same Warp container | - ---- - -## 10. Milestones - -| # | Milestone | Definition of done | -|---|---|---| -| M1 | "Hello Matrix" | Element Web can register, log in, create a room, send messages, see them after reload — with federation **off** (server Phases 0–4). Admin panel shows dashboard, users, federation switch. | -| M2 | "On the network" | Federation toggled on from the admin panel; Warp joins a room on matrix.org; Synapse users join a Warp room; toggling off stops remote traffic cleanly (Phase 5). | -| M3 | "Daily driver" | Warp web client: E2EE rooms with cross-signing and key backup, images/files, push (Phases 6–8). Desktop client runs the same UI via Wails. | -| M4 | "Pocket server" | Android app with push and E2EE; bridges (mautrix) run via the AS API; sliding sync; PocketBase driver at parity (Phases 9–10). | -| M5 | "Everywhere" | iOS build of the Flutter app in TestFlight. | - ---- - -## 11. Reference material - -- Spec: https://spec.matrix.org/latest/ — start with *Room Versions* and the - *Server-Server API* appendices. -- Dendrite (architecture reference; read, don't paste): https://github.com/element-hq/dendrite -- `gomatrixserverlib` (test vectors): https://github.com/matrix-org/gomatrixserverlib -- Complement: https://github.com/matrix-org/complement -- Federation tester: https://federationtester.matrix.org -- PocketBase as a Go framework: https://pocketbase.io/docs/go-overview/ -- `mautrix-go` + `goolm`: https://github.com/mautrix/go -- `matrix-js-sdk` / `matrix-sdk-crypto-wasm`: https://github.com/matrix-org/matrix-js-sdk -- Wails v3: https://v3alpha.wails.io -- `matrix_dart_sdk`: https://github.com/famedly/matrix-dart-sdk -- Synapse admin API (feature checklist for our admin panel): https://element-hq.github.io/synapse/latest/usage/administration/admin_api/ - ---- - -## 12. Open questions - -1. **State storage model**: Dendrite's snapshot + block NIDs vs. per-event - full-state rows. Decide in Phase 3 after a spike with a 10k-event room. -2. **Room version breadth** for federation compatibility (v6+ probably enough). -3. **Presence** default on/off. -4. **PocketBase scope**: full storage driver as specified, or only media - filesystem + admin auth on top of the `sqlite` driver? The latter is far - less work for most of the benefit. Decide after the `sqlite` driver is done. - *Decided in Phase 10:* close to the latter. The pocketbase driver runs the - sqlite schema and store on PocketBase's own database connection and puts - media in PocketBase's filesystem (local or S3); PocketBase's admin auth - and realtime are not wired up yet. See docs/deployment.md. -5. **Desktop sync engine**: Go (`mautrix-go`) as planned, or keep - `matrix-js-sdk` in the webview for maximum code sharing with web? Prototype - both in a week and pick. -6. **Push gateway for mobile**: run Sygnal, or build a small Warp-native - FCM/APNs gateway in Go? diff --git a/README.md b/README.md index 74c89de..96bd2f2 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,10 @@ own process, `warp-admin`, next to the homeserver: `docker compose --profile admin up -d`, or see [apps/admin](apps/admin/README.md). +The web client is a Matrix client for the browser. It is optional in the +same way, a process of its own, `warp-web`: +`docker compose --profile web up -d`, or see [apps/web](apps/web/README.md). + Also in place: argon2id passwords, hashed access and refresh tokens, per-endpoint rate limiting, YAML + environment configuration, structured logging, graceful shutdown, the storage interfaces with SQLite, PostgreSQL diff --git a/apps/README.md b/apps/README.md index 692d8fd..03e998c 100644 --- a/apps/README.md +++ b/apps/README.md @@ -2,10 +2,10 @@ | Directory | What | State | |---|---|---| -| [`ui/`](ui/README.md) | shared Vue 3 + Tailwind design system: tokens, fonts, icons, components | in use by admin; the Matrix store comes with web | +| [`ui/`](ui/README.md) | shared Vue 3 + Tailwind design system: tokens, fonts, icons, components | in use by admin and web | | [`admin/`](admin/README.md) | admin web panel, served by its own optional `warp-admin` binary | done for the current admin API | -| `web/` | web client (Vue SPA) + Go host | next | -| `desktop/` | Wails v3 shell reusing the web frontend | once web has a timeline | +| [`web/`](web/README.md) | web client (Vue SPA on matrix-js-sdk), served by its own optional `warp-web` binary | rooms, timeline, sending text and files, invites; E2EE next | +| `desktop/` | Wails v3 shell reusing the web frontend | next: web has a timeline | | `mobile/` | Flutter app, Android first | after web | The frontend packages are a pnpm workspace (`pnpm-workspace.yaml` at the @@ -14,4 +14,5 @@ repository root); the Go hosts join `go.work`. ```bash pnpm install pnpm admin:dev +pnpm web:dev ``` diff --git a/apps/ui/README.md b/apps/ui/README.md index 0dddd4c..4d91bcd 100644 --- a/apps/ui/README.md +++ b/apps/ui/README.md @@ -46,6 +46,9 @@ The stylesheet brings in: | `WBanner` | feedback; always icon and words, never colour alone | | `WDialog` | modal on the native `` | | `WSpinner` | progress | +| `WRoomItem` | a row of the room list: avatar, name, preview, time, unread counter | +| `WMessageBubble` | a message with sender, time and delivery status (`sending`, `sent`, `read`, `failed`) | +| `WComposer` | the message field: attach, Enter sends, Shift+Enter adds a line | ## Where things come from @@ -58,5 +61,7 @@ apps on the same grid: 24px, 1.75 stroke, round caps and joins. The fonts are Outfit, Figtree and JetBrains Mono under the SIL Open Font License. -The Matrix store (Pinia over matrix-js-sdk) joins this package with the web -client. +The three chat components are the brand package's `RoomItem`, +`MessageBubble` and `Composer`. The Matrix state itself is not here: it +lives with the web client (`apps/web/src/stores`, `apps/web/src/matrix`), +which the desktop app will reuse as a whole. diff --git a/apps/ui/src/chat.ts b/apps/ui/src/chat.ts new file mode 100644 index 0000000..709ece8 --- /dev/null +++ b/apps/ui/src/chat.ts @@ -0,0 +1,2 @@ +/** Delivery state of your own message, as the bubble shows it. */ +export type MessageStatus = 'sending' | 'sent' | 'read' | 'failed' diff --git a/apps/ui/src/components/WComposer.vue b/apps/ui/src/components/WComposer.vue new file mode 100644 index 0000000..5ddbd16 --- /dev/null +++ b/apps/ui/src/components/WComposer.vue @@ -0,0 +1,84 @@ + + +