Repository navigation
Conversation
* chore: scaffold axum backend crate with live check gate Adds an Axum + Tokio crate (edition 2021, Rust 1.83) with config-from-env, tracing, TraceLayer, and a /healthz probe. Cargo.lock is committed so every check step runs with --locked; hyper-util pinned to 0.1.10 so the dependency set stays on the 1.83 toolchain (newer releases need cargo's edition2024). make check now exercises fmt, clippy, test, and build in backend/ instead of printing 'skipped'. * chore: avoid logging database credentials at startup
* feat: postgres schema, models, and storage layer Adds sqlx migrations (users, rooms, room_memberships, messages), the FromRow model types, and a storage module with compile-time-checked queries for users/rooms/memberships/messages. The server now connects to Postgres on startup and applies pending migrations. Fidelity details: - messages(room_id, created_at, id) index backs history pagination; the cursor join is scoped to the room; limit clamped to 1..=100. - created_by and messages.user_id use ON DELETE RESTRICT so deleting a user cannot silently destroy rooms or punch holes in shared history. - messages carry uuid ids + timestamptz created_at for stable ordering across reconnects (save-before-broadcast comes in a later issue). Also bumps the toolchain to Rust 1.99 (rust-toolchain.toml, mise.toml, Dockerfile, edition 2024): current sqlx/hyper-util manifests require cargo's edition2024 feature (stable since 1.85), so the 1.83 pin could not build a modern dependency tree. The committed .sqlx/ offline cache lets CI build without a database. * ci: resolve sqlx queries from the offline cache in the gate and CI The check target and CI job set SQLX_OFFLINE=true so compile-time sqlx macros use the committed .sqlx/ cache instead of a live database. This makes the gate deterministic and matches CI (which has no DB service), and fails loudly when a query changed without regenerating the cache. DB-backed integration tests still connect at runtime via DATABASE_URL.
* feat: register/login with argon2 and JWT HS256
Adds the auth surface: POST /api/auth/register, POST /api/auth/login, and
GET /api/me, plus an AuthUser extractor other handlers can reuse.
- Passwords hashed with argon2id (random per-hash salt); the hash is never
serialized or logged.
- Stateless HS256 JWTs (sub = user id, 24h expiry) signed with JWT_SECRET.
- Login returns the same 401 for unknown user and wrong password, and burns
comparable hashing time on the unknown-user path to blunt enumeration.
- Validation: username 3-32 chars [a-z0-9_], password 8-128 chars.
- Unified AppError -> JSON {error} responses (400/401/403/404/409/500).
- Unit tests for hashing, JWT, and validation; DB-backed integration tests
over the real router that skip gracefully when no database is reachable.
- Wire JWT_SECRET through docker-compose and document it in the env template.
* refactor: harden auth input handling, token parsing, and secret default
Review follow-ups:
- Bound login input (username/password length) before the DB query and
argon2 verify, returning the generic 401 so an over-long body cannot burn
hashing time or reach the database.
- Accept any case for the Bearer scheme (RFC 7235) instead of exact
'Bearer '; add parse_bearer unit tests.
- Warn at startup when JWT_SECRET is unset (committed insecure default),
and expose Config::uses_default_jwt_secret with tests.
- Add a JWT test pinning HS256: a token whose header claims HS384 is
rejected (algorithm-confusion defense).
- Extract username length bounds into validation consts; drop the unused
UNKNOWN_USER_SUBJECT const.
Username enumeration on register (409) is a conscious, documented trade-off:
registration reveals availability by design.
* ci: run DB-backed integration tests against a Postgres service
The auth integration tests skip when no database is reachable, and CI had
no database, so they never actually ran there -- 'skipped is not passed'.
Add a throwaway postgres:16 service to the rust job and point DATABASE_URL
at it, so register/login/me are exercised on every release PR. Compile-time
queries still resolve from the committed .sqlx cache (SQLX_OFFLINE).
* feat: rooms, membership, and message-history API
Adds the authenticated room surface and enforces membership on history:
- POST /api/rooms (creator auto-joined), GET /api/rooms (with 'member'
flag), GET /api/rooms/{id}, POST /api/rooms/{id}/join and /leave
(idempotent), GET /api/rooms/{id}/messages (newest-first, limit 1-100
clamped, 'before' message-id cursor).
- RoomService orchestrates create/list/get/join/leave/history; history
returns 403 when the caller is not a member, 404 for unknown rooms.
- Room names validated: trimmed, non-empty, <= 64 chars.
- History responses join the author username (MessageWithAuthor) so the
UI renders authors without a second lookup; ids + timestamps keep the
ordering stable across reconnects.
- Storage: RoomMemberships::leave, Messages::list_with_author (two
pagination variants); new queries added to the .sqlx offline cache.
- 9 DB-backed integration tests (auto-join, name validation, auth gate,
membership flag, join/leave idempotency, 403/404 history access,
pagination + cursor, unknown cursor); skip gracefully without a DB.
make check green: 16 unit + 18 integration tests, clippy -D warnings.
* refactor: rooms review follow-ups
Review findings addressed:
- Create a room and join the creator atomically: single CTE statement in
Rooms::create, so a room row can never exist without the creator's
membership (replaces two non-transactional statements).
- Reject control characters in room names with a clean 400 instead of a
Postgres 500.
- Make the history test deterministic: seed messages with explicit spaced
created_at so the random-UUID tie-break cannot reorder the page.
- RoomResponse::from_storage now consumes its argument (no name clone;
list_rooms uses into_iter).
- Drop the unused rows-affected claim on RoomMemberships::leave.
Deliberately unchanged (documented): rooms are public by design — any
authenticated user can join, so 403-on-history is a membership gate, not
privacy; room names are not unique (rooms are id-addressed).
* feat: WebSocket chat with save-before-broadcast and room isolation Adds GET /api/ws?token=..&room_id=..: JWT + room + membership checked before the upgrade, then an in-memory per-room broadcast session. - run_session: subscribe-then-join, split socket, merged broadcast + direct-error send lane, lag-tolerant receive, presence on join/leave - save-before-broadcast: message is INSERTed first; the row's id and timestamptz are what every client (sender included) receives - per-message validation: trimmed non-empty body, 4000-char cap, membership re-check on every send; failed frames return an error event and keep the connection alive - RoomRegistry: lazy per-room broadcast::channel + user map, single instance by design (Redis fan-out is a later milestone) - AppState gains registry; axum ws feature + futures-util deps - integration tests over a real socket on an ephemeral port: handshake rejections (401/404/403/101), intra-room broadcast with presence and cross-room isolation, persistence via history, validation-error resilience (3 new tests; 40 total green) Closes #18 * refactor: harden WebSocket session against review findings - enforce room access on the receive path too: a session whose membership was revoked over HTTP is closed at the next message instead of receiving room content forever (per-message is_member check before delivery) - reference-count connected users in the registry so multiple tabs emit a single joined/left presence transition (0->1 joined, 1->0 left) - stop leaking the JWT query parameter into logs: custom trace span logs method + path only, never the query string - cap WebSocket message size at 64 KiB before JSON parsing (axum default allows 64 MiB) so oversized frames cannot force a big allocation - validation: message bodies reject control characters like room names - error events go out over try_send so a slow/non-reading client can never stall the receive loop - poison-safe registry locks; drop the redundant outer Arc around RoomRegistry in AppState - tests: own-joined sequencing removes the connect/subscribe race; added revoked-membership teardown + duplicate-connection presence tests
* docs: add API and WebSocket protocol reference - docs/api.md: auth, rooms, history, WS handshake + event protocol, validation rules, save-before-broadcast semantics, error shape - README: replace stale 'scaffold only' status with the implemented feature set and link the new reference Closes #20 * docs: correct error-shape, token-issuance, and upgrade-request claims Fact-check follow-ups on docs/api.md: - error shape only applies to the app's own AppError responses; extractor rejections (missing Content-Type -> 415, malformed JSON/UUID -> 400, wrong shape -> 422, non-upgrade /api/ws -> 426) are plain text - Content-Type: application/json is mandatory on the JSON POST endpoints - tokens are issued by register and login only (not /api/me) - note the case-insensitive bearer scheme - WS requires a genuine upgrade request; a plain GET is rejected before the 401/404 checks; bad query params -> plain-text 400
* feat: scaffold React + Vite + TypeScript frontend Add the frontend component with the project's chosen toolchain: Vite + React + TypeScript on npm, Oxlint for linting and Oxfmt for formatting (no ESLint/Prettier). The component Makefile exposes a `check` gate that runs format check -> lint -> typecheck -> production build and fails on the first problem. * docs: document the frontend component Topology, directory layout, dev commands, key commands, and agent list now include the React + Vite + TypeScript frontend. AGENTS.md and PROJECT_CONTEXT.md stay in sync. * chore: wire frontend into build, CI, dependabot, and agents Add frontend to COMPONENTS and root clean; add an npm Dependabot entry for /frontend targeting dev; add the CI frontend job (Node 24, npm ci, format check, lint, typecheck, build) and include frontend/** in the paths filter; add the frontend and visual subagents and register them in opencode.json. * fix: address review findings on frontend scaffold - Root Makefile: accumulate per-component status so a failing component is no longer masked by a later passing one (the loop now exits non-zero). - frontend tsconfigs: add "strict": true so the new typecheck/build gate is actually strict. - frontend/Makefile: skip only when npm is absent; missing node_modules fails loudly with instructions (consistent with backend). - Makefile clean: remove frontend/dist even without node_modules. - opencode.json: visual agent bash is deny-by-default; frontend edit keeps the secret-file deny rules. - Docs: distinguish skipped (toolchain absent) from failed (deps missing).
* feat: implement chat UI (auth, rooms, real-time messaging) Typed API client for every endpoint in docs/api.md (ApiError surfaces the backend message; extractor plain-text rejections pass through), a WebSocket helper that builds /api/ws?token=&room_id=, and pure message merge/dedupe helpers. Hooks for auth (token persistence + /api/me validation) and a live room (history + socket with capped backoff reconnect). Login, room-list, and chat-panel components; App composes auth + rooms + chat. Dev server proxies /api (HTTP + WS) and /healthz to the backend. Removes the Vite demo assets. * test: cover client logic and run tests in the frontend gate Add vitest unit tests for URL/request building, error parsing, and message merge/dedupe/ordering (17 tests, no DOM). Wire npm run test into the frontend Makefile check and the CI frontend job. Disable oxlint's set-state-in-effect rule with a rationale (data-loading effects legitimately drive state). Docs: note the test step and the dev proxy in the frontend README; add the test step to the AGENTS.md component description.
Backend + frontend MVP is on dev, but the root README still called the frontend 'planned'. Make the status, stack, layout, and run instructions reflect the implemented React + Vite + TS client and note that make check covers both components. Also refresh the now-stale frontend note in PROJECT_CONTEXT.md.
* chore: start the frontend with make dev-up Add a frontend service to the compose dev stack so one command brings up Postgres + API + Vite. A small node:24-alpine dev image bakes node_modules into a layer; compose bind-mounts the source and keeps deps in a named volume, and sets VITE_API_PROXY_TARGET=http://dev:8080 (service name) since 'localhost' inside the container is the frontend itself. Polling is enabled for reliable HMR through bind mounts, ports are overridable (FRONTEND_HOST_PORT), and --strictPort fails loudly on a collision. Documented in README, AGENTS.md ports table, PROJECT_CONTEXT.md, and frontend/README.md. * fix: keep frontend deps fresh and wait for a healthy API A named volume is seeded from the image only when empty, so 'up --build' would shadow a rebuilt image's node_modules with the stale volume. Install deps at container start-up instead, guarded by a package-lock.json hash so it only reinstalls when the lockfile changes, and drop the unused image-baked 'npm ci' layer. Also give the dev service a healthcheck on /healthz and gate the frontend on service_healthy so proxied /api calls don't 502 during cargo's first build. * fix: harden the frontend dev entrypoint and API health gate Fail loudly when package-lock.json is missing and compute its hash without a pipeline so a sha256sum failure isn't masked by cut. Widen the dev healthcheck window (start_period 300s, retries 60) so a cold cargo build can't leave the frontend waiting on service_healthy.
Use the shorter /health path (handler renamed to match) and update every reference: the Vite dev proxy, the new compose dev healthcheck, and the docs (README, docs/api.md, frontend/README.md). Response body unchanged; no external consumer of /healthz exists on dev.
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 7. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@v4...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the tokio group in /backend with 2 updates: [tower-http](https://github.com/tower-rs/tower-http) and [tokio-tungstenite](https://github.com/snapview/tokio-tungstenite). Updates `tower-http` from 0.6.11 to 0.7.1 - [Release notes](https://github.com/tower-rs/tower-http/releases) - [Commits](tower-rs/tower-http@tower-http-0.6.11...tower-http-0.7.1) Updates `tokio-tungstenite` from 0.26.2 to 0.29.0 - [Changelog](https://github.com/snapview/tokio-tungstenite/blob/master/CHANGELOG.md) - [Commits](snapview/tokio-tungstenite@v0.26.2...v0.29.0) --- updated-dependencies: - dependency-name: tower-http dependency-version: 0.7.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: tokio - dependency-name: tokio-tungstenite dependency-version: 0.29.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: tokio ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the sqlx group in /backend with 1 update: [sqlx](https://github.com/launchbadge/sqlx). Updates `sqlx` from 0.8.6 to 0.9.0 - [Changelog](https://github.com/transact-rs/sqlx/blob/main/CHANGELOG.md) - [Commits](transact-rs/sqlx@v0.8.6...v0.9.0) --- updated-dependencies: - dependency-name: sqlx dependency-version: 0.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: sqlx ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Felipe Inoue <felipeinoue@yahoo.com.br>
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.19.2 to 26.6.4. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.6.4 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the tooling group in /frontend with 1 update: [typescript](https://github.com/microsoft/TypeScript). Updates `typescript` from 6.0.3 to 7.0.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](microsoft/TypeScript@v6.0.3...v7.0.2) --- updated-dependencies: - dependency-name: typescript dependency-version: 7.0.2 dependency-type: direct:development update-type: version-update:semver-major dependency-group: tooling ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Owner
Author
|
CI passed on this release PR — rust ✅ (3m52s) and frontend ✅ (11s). Awaiting explicit user confirmation to merge |
* feat: design system, brand mark, favicon and inline icon set
- Rewrite index.css with a teal/emerald dark design system: surfaces,
type scale, radii, shadows, focus ring and prefers-reduced-motion
- Add Logo/LogoMark (speech bubble + pulse) and a self-contained inline
SVG Icon set (no icon dependency)
- Replace the Vite default favicon with the custom mark; update the
document title/theme-color
- Split App.css into styles/{primitives,auth,shell,chat}.css
Refs #39
* refactor: address review feedback on brand/a11y tokens
- Make LogoMark decorative when paired with the wordmark (avoid double
accessible name); use useId for the gradient
- Restore accessible loading status on the splash screen
- Add --panel-hover/--border-hover/--accent-border tokens; lighten
--faint to meet AA contrast
- Drop redundant scoped :disabled rules covered by the global base
Refs #39
* feat: redesigned register/login screen with brand panel - Split layout: brand panel (logo, gradient tagline, features) + form card - Segmented animated tabs, icons on fields, password visibility toggle, live validation checklist while registering, error banner, busy spinner - Add src/auth/validation.ts (pure rules) with unit tests Refs #40 * refactor: address auth review feedback - Password checklist rule now reflects the full 8-128 range so it stays in sync with credentialsValid - Require non-empty credentials before submitting in login mode - Roving tabindex + arrow-key navigation for the mode tabs - Gradient tagline color fallback for non-clip browsers Refs #40
* feat: app shell, room list and chat visual overhaul - Topbar: user avatar, icon logout, mobile menu toggle; rooms become an off-canvas drawer under 860px - Room list: header with count, create form with plus icon, live filter with clear button, hash icons, join tags, empty states and shimmer skeletons - Chat: avatars with deterministic colors, author grouping, date dividers, hover timestamps, auto-resize composer with send icon, online presence avatars, jump-to-newest button, pinned-scroll logic that survives older-page loads, error banner - New pure helpers (isSameDay/shouldGroup/formatMessageTime/ formatDayLabel, avatarColor/initialsOf) with unit tests Refs #41 * refactor: clear stale older-load scroll guard when the load settles A failed or empty load-older request never changes messages, so the scroll-preservation ref could stay latched and corrupt the next pinned scroll; sync it with the loadingOlder prop instead. Refs #41
* feat: client-side chat niceties (emoji, linkify, sound, toasts, unread) - Emoji picker in the composer (curated built-in set, inserts at cursor) - Auto-linkify message URLs with safe rel/target - Web Audio chime for incoming messages; browser notifications while the tab is hidden (permission requested on login) - Per-room unread badges + tab-title count, persisted to localStorage - Toast notifications for room create/join/errors - Ctrl/Cmd+K room search shortcut; persisted dark/light theme toggle Refs #42 * fix: address review — link brackets, light-theme contrast, a11y, cleanup - linkify: only strip unbalanced closing brackets (keeps /Foo_(bar) intact) - light theme: add --good/--warn/--bad, darken accent gradient for contrast - toasts: clear pending timers on unmount; cap at 3; keep live region mounted - room unread badge: aria-hidden glyph + visually-hidden count text - theme: update theme-color meta + inline pre-paint script (no FOUC) - emoji picker: aria-haspopup/controls + focus return on Escape - drop dead exports (EMOJI_BY_NAME, hasAnyLink); test the bracket cases Refs #42
* feat: typing indicators over WebSocket - Backend: add Incoming::Typing and WsOutgoing::Typing; membership-checked, relayed per session with a 1.5s throttle, never persisted - docs/api.md: document the typing frame on both directions - Frontend: extend ServerEvent/ClientSend, encodeTyping, useChatRoom exposes sendTyping (client-throttled) + typing list with a 3.5s TTL - ChatPanel: 'X is typing…' indicator with animated dots; hint on composer input - Integration test: room-scoped typing, throttle, and non-persistence - Unit tests for typingLabel and frame encoding Closes #43 * fix: don't echo own typing; throttle before DB; harden client - Backend: skip relaying a user's own typing event back to them; move the throttle gate ahead of the membership query so spamming can't hit Postgres - Client: defensively filter own user from the indicator; reset the typing send-gate per room; guard onmessage against post-cleanup frames - Test: assert no self-echo, send the throttled frame immediately, fix comment - docs: note no self-echo and that clients ignore their own typing Refs #43
- Backend: migration for message_reactions (composite PK, FKs, cascade) - Storage: reactions module (insert/remove/toggle/list/group_by_message, MAX_EMOJI_LEN 16), unit tests - Services/routes/WS: include reactions in MessageResponse/history, handle_react/Reaction frames (membership + message-in-room validation), validate emoji, broadcast to room - Tests: reactions integration in ws_chat; validate_emoji unit tests; grouping unit tests - Frontend: types/ws/encodeReact, applyReaction helper + tests, useChatRoom.react, ChatPanel chips + hover quick reactions, styles, update history types; encodeReact test; reactions test - Docs: API updates (reactions in history/message, react client frame, reaction server frame, semantics) - .sqlx: regenerate query cache entries
* feat: unread counts and read receipts (#45) Per-member read cursors backed by a new message_reads table, an HTTP mark-read endpoint, ephemeral WS read receipts, and server-counted unread totals surfaced through room responses. Backend: - migration 0006: message_reads(room_id, user_id, last_read_message_id); nullable cursor = everything unread; advance-only upsert in Reads - Rooms: list/get return RoomSummary { room, unread_count, last_message } (DISTINCT ON newest message per room); mark_read validates membership, message ownership, and never moves the cursor backwards - POST /api/rooms/{id}/read marks all (or up to a message) read and broadcasts WsOutgoing::Read so members see who caught up - tests: read_receipts.rs (cursor math, 403/404/400) + WS broadcast test Frontend: - Room { unread_count, last_message }, ServerEvent read, api.read() - useChatRoom tracks read cursors; ChatPanel shows Seen on the viewer's newest own message; RoomList shows last-message previews - App reconciles local unread badges with server counts (syncFromRooms) and marks read on enter/tab-focus (immediate) and on visible incoming messages (throttled, trailing) docs/api.md updated for the new endpoint, room fields, and read frame. * fix: address review findings for unread/read receipts (#45) - Reads::mark_read returns the effective cursor (Some only when it moved) and re-checks the advance-only condition on the conflicting row, so concurrent out-of-order marks cannot move the cursor backwards - unread_counts/unread_count exclude the caller's own messages - Room list/get gate last_message and unread_count on membership: non-members see no preview and no badge (matches member-gated history) - Only broadcast a read receipt when the cursor actually advanced - syncFromRooms is server-authoritative: assign server counts and drop stale local bumps (a bump implies the server already persisted, so a later fetch reports >= 1); test updated - Clear the trailing read cursor when leaving a room (not just switching) - docs/api.md: member-gated preview, reminder that a JSON body is required (JSON {} with null message_id), and effective-cursor semantics Tests: read_receipts.rs now seeds from a second author, asserts own messages never count, asserts the cursor never reports a stale re-mark, and covers non-member gating. Full make check green.
Bumps the tokio group in /backend with 1 update: [tokio-tungstenite](https://github.com/snapview/tokio-tungstenite). Updates `tokio-tungstenite` from 0.29.0 to 0.30.0 - [Changelog](https://github.com/snapview/tokio-tungstenite/blob/master/CHANGELOG.md) - [Commits](snapview/tokio-tungstenite@v0.29.0...v0.30.0) --- updated-dependencies: - dependency-name: tokio-tungstenite dependency-version: 0.30.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: tokio ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: Playwright screenshot harness (#46) Seed a two-user room over the public API + WebSocket, then capture the key UI states (auth, room list with unread badge, live chat, read receipt) in dark and light themes via a headless browser. - frontend/scripts/screenshots.mjs: stack wait + seeding + captures - frontend/playwright.config.ts: canonical browser settings - make screenshots target (root + frontend Makefiles), npm script - frontend/screenshots/ gitignored; README run instructions - Verified end-to-end in the sandbox up to browser launch; the seed path (register/create/join/WS reactions+messages) is exercised against the real stack. PNG production needs Chromium system deps (npx playwright install --with-deps chromium on Linux). * fix: address review findings for screenshot harness (#46) - register() now returns the password it used, so login() can fill the current-password field (playwright fill rejects undefined) - wait for .status-open instead of the nonexistent .status-live (the pill class is status-${status} with status "open"; "live" is only the display label) - close the browser/contexts on every failure path via try/finally - guard for the global WebSocket (Node 22+) with a clear error - abort the stack-wait fetches after 3s so the 20s deadline holds - close the seed socket and reject cleanly on WS error / non-JSON data
* feat: optional Redis Pub/Sub cross-instance broadcasting
Room events now fan out through Redis Pub/Sub when REDIS_URL is set:
- RoomRegistry gains with_redis(); send() publishes to room:{uuid} and
sessions consume their own subscription, so the local channel is never
written in pub/sub mode (no double delivery)
- Presence join/leave announces through the same publish path; per-instance
refcounts stay as designed (documented cross-instance limitation)
- Redis connect failure at startup logs a warning and falls back to in-memory
- WsOutgoing/UserRef/MessageResponse are now Deserialize: the JSON sent to
clients doubles as the cross-instance payload
- tests/pubsub.rs: two in-process instances on one DB + one Redis prove
cross-instance delivery and absence of double delivery; skips when
REDIS_URL is unset
Closes #9 once merged
* docs: wire up REDIS_URL in compose, env template, and project docs
- docker-compose.yml: add an enabled redis service (host 6381, healthcheck)
and pass REDIS_URL through the dev service
- env template: document and uncomment the REDIS_HOST_PORT / REDIS_URL pair
- AGENTS.md/PROJECT_CONTEXT.md/README.md: Redis is no longer 'later'; it is
optional and enabled via REDIS_URL with in-memory fallback
* fix: review follow-ups for redis pub/sub
- subscribe failure no longer silently blinds a session: events() returns
RoomEvents::Unavailable, the session closes, and the client's existing
reconnect loop retries the subscription
- a single malformed redis payload skips that event instead of severing
every session in the room (only a dropped connection closes)
- compose: bind the redis service to 127.0.0.1 (unauthenticated chat
traffic must not be published to the LAN); fix comment typo
- docs: mid-flight publish failures drop the event (history recovers
messages); presence refcounts are per-instance (AGENTS/PROJECT_CONTEXT)
…) (#58) * fix: tolerate missing optional fields from version-skewed backends (#57) RoomList white-screened with "Cannot read properties of undefined (reading 'username')" against a backend older than #45: the guard at RoomList.tsx tested `!== null`, but an absent JSON key is `undefined`, so the null check passed and the nested read threw. - normalize rooms and messages at the API boundary (listRooms, createRoom, history) via normalizeRoom/normalizeMessage, with payload types that model older backends' shapes - apply the same normalization to the WS message ingest in useChatRoom - RoomList: use `!= null` so absent keys are treated as "no preview" - tests for the helpers and the endpoints (87 frontend tests) * refactor: normalize server frames at the decode boundary (#58 review) Review follow-up (PR #58): the WS ingest normalization was untestable inside the hook and createRoom normalization had no coverage. - move the payload normalizers to api/normalize.ts (shared by HTTP and WS) - add decodeServerEvent to ws.ts: parse + normalize a message frame at the single WS trust boundary; the hook now decodes through it - hook drops its inline normalizeMessage call (frames arrive normalized) - move helper tests to normalize.test.ts; add decodeServerEvent tests and a createRoom normalization test (92 frontend tests)
Owner
Author
|
Closing as superseded. This PR was opened before the v0.2-polish and v0.3 milestones and only integrated dependency/CI bumps (its body still lists v0.1-MVP issues). A fresh release PR now brings the full v0.2 + v0.3 work from |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Release PR integrating the dependency and CI updates merged to
devsince thev0.1-MVP release (
main@db04adb).No application behavior changes — only dependency manifest/lockfile bumps plus
one CI action bump.
mainalready contains all v0.1-MVP feature work; the onlycontent difference between
mainanddevis these bumps.Type of Change
Related Issues
Closes nothing. This release PR closes no issues — all v0.1-MVP issues were
closed when their feature PRs merged to
dev, andCloses #Nis a no-op whenthe base is not the default branch.
Changes Made
ci:bumpactions/setup-node4 → 7deps:bump tower-http 0.6.11 → 0.7.1 and tokio-tungstenite 0.26.2 → 0.29.0deps:bump sqlx 0.8.6 → 0.9.0deps:bump@types/node24.19.2 → 26.6.4deps:bump typescript 6.0.3 → 7.0.2Testing
make check) — verified locally ondev: backend 43 tests, frontend 17 testscargo clippy --all-targets --locked -- -D warnings)cargo build --locked)Checklist