- make_persist_fn now accepts embed_service, collects persisted text messages
- Filters non-text, non-empty, non-system/tool messages
- Groups by room→project_name, batch-embeds via embed_memories_batch
- Removes old per-message synchronous embed_memory call
- Workers thread embed_service through to persist_fn
- Save thinking_content as {"__chunks__": [{type, content}]} for replay
- Tool call sanitization — don't expose raw results to frontend
- Billing record_ai_usage integration
- Room service module refactoring into service/ directory
- Add sender_type field to TypingEvent (user/ai)
- Change Redis TTL from 10s to 60s for AI typing persistence
- Broadcast typing.start/stop with sender_type=ai when AI stream starts/ends
- Replay active AI typing events from Redis on new WS subscribe
- Fix ai.stream_chunk WS payload missing display_name and chunk_type
- Add initial thinking chunk on AI stream start for immediate indicator
RoomConnectionManager now holds a cache field and typing_inner broadcast
map. broadcast_typing() persists start/stop to Redis (SETEX 10s / DEL)
and broadcasts via tokio channel. ws_universal.rs handles TypingStart/
TypingStop actions and streams typing events to WS clients.
- Remove all use slog::* imports and log: slog::Logger fields
- Replace slog macros with tracing::{info!, warn!, error!, debug!}
- metrics.rs: upgrade metrics 0.21→0.22, remove register_*! macros,
use functional API: metrics::gauge!(), metrics::counter!(),
metrics::histogram!(), metrics::describe_gauge!() etc.
- RoomMetrics: all fields now use functional metrics API, dynamic
room_id labels passed as owned String to avoid lifetime issues
- RoomService: remove pub log: slog::Logger field
- connection.rs: remove log from subscribe_room_events,
subscribe_project_room_events, subscribe_task_events_fn
Frontend:
- P0: Replace constant estimateSize(40px) with content-based estimation
using line count and reply presence for accurate virtual list scroll
- P1: Replace Shadow DOM custom elements with styled spans for @mentions,
eliminating expensive attachShadow calls per mention instance
- P1: Remove per-message ResizeObserver (one per bubble), replace with
static inline toolbar layout to avoid observer overhead
- P2: Fix WS token re-fetch on every room switch by preserving token
across navigation and not clearing activeRoomIdRef on cleanup
Backend:
- P1: Fix reaction check+insert race condition by moving into transaction
instead of separate query + on-conflict insert
- P2: Fix N+1 queries in get_mention_notifications with batch fetch
for users and rooms using IN clauses
- P2: Update room_last_activity in broadcast_stream_chunk to prevent
idle room cleanup during active AI streaming
- P3: Use enum comparison instead of to_string() in room_member_leave
Backend:
- Atomic seq assignment via Redis Lua script: INCR + GET run atomically
inside a Lua script, preventing duplicate seqs under concurrent requests.
DB reconciliation only triggers on cross-server handoff (rare path).
- Broadcast channel capacity: 10,000 → 100,000 to prevent message drops
under high-throughput rooms.
Frontend:
- Optimistic sendMessage: adds message to UI immediately (marked
isOptimistic=true) so user sees it instantly. Replaces with
server-confirmed message on success, marks as isOptimisticError on
failure. Fire-and-forget to IndexedDB for persistence.
- Seq-based dedup in onRoomMessage: replaces optimistic message by
matching seq, preventing duplicates when WS arrives before REST confirm.
- Reconnect jitter: replaced deterministic backoff with full jitter
(random within backoff window), preventing thundering herd on server
restart.
- Visual WS status dot in room header: green=connected, amber
(pulsing)=connecting, red=error/disconnected.
- isPending check extended to cover both old 'temp-' prefix and new
isOptimistic flag, showing 'Sending...' / 'Failed' badges.