Commit Graph

45 Commits

Author SHA1 Message Date
ZhenYi
5ff45770ec fix(room): fix model/ai list response parsing in RoomSettingsPanel
The SDK wraps API responses as { data: { code, message, data: [...] } }.
Code was incorrectly accessing resp.data['200'] which doesn't exist.
Fix to use resp.data.data to reach the actual array.
2026-04-17 23:15:14 +08:00
ZhenYi
a171d691c6 fix(room): align ReactionGroup types with frontend and guard reaction update handler
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
- Fix ReactionGroup.count: i64 -> i32 and users: Vec<Uuid> -> Vec<String>
  to match frontend ReactionItem (count: number, users: string[]).
  Mismatched types caused the WS reaction update to silently fail.
  Also update ReactionItem in api/ws_types.rs to match.
- Add activeRoomIdRef guard in onRoomReactionUpdated to prevent stale
  room state from processing outdated events after room switch.
- Switch from prev.map() to targeted findIndex+spread in onRoomReactionUpdated
  to avoid unnecessary array recreation.
2026-04-17 23:00:52 +08:00
ZhenYi
047782e585 fix(room): handle reaction updates in onRoomMessage WS handler
Root cause: publish_reaction_event sends a RoomMessageEvent (not
reaction_added) with reactions field set. The onRoomMessage handler
previously returned prev immediately when a duplicate ID was found,
skipping the reaction update entirely.

Fix:
- Add reactions field to RoomMessagePayload so TypeScript knows it's there
- When a duplicate message ID is found AND payload carries reactions,
  update the existing message's reactions field instead of ignoring
- ReactionItem and ReactionGroup have identical shapes, so assignment works
2026-04-17 22:37:20 +08:00
ZhenYi
7152346be8 fix(room): make reaction Popover controlled so it closes after select 2026-04-17 22:16:40 +08:00
ZhenYi
a1ddb5d5bc fix(room): add HTTP batch reactions endpoint and clean up dead code
Backend:
- Add reaction_batch handler: GET /api/rooms/{room_id}/messages/reactions/batch?message_ids=...
- Register route in libs/api/room/mod.rs
- Backend already had message_reactions_batch service method, just needed HTTP exposure

Frontend:
- Add ReactionListData import to room-context.tsx
- Fix thisLoadReactions client type (was using broken NonNullable<ReturnType<>>)
- Remove unused oldRoomId variable
- Delete unused useRoomWs.ts hook (RoomWsClient has no on/off methods)
- Remove unused EmojiPicker function and old manual overlay picker from RoomMessageBubble
- Remove unused savedToken variable in room-ws-client
2026-04-17 22:12:10 +08:00
ZhenYi
2f1ed69b31 fix(room): restore useState import removed in refactor 2026-04-17 22:02:55 +08:00
ZhenYi
ef1adb663d fix(room): replace manual emoji picker positioning with Popover
Manual getBoundingClientRect positioning caused the picker to appear at
the far right of the room and shift content. Replaced with shadcn
Popover which handles anchor positioning, flipping, and portal rendering
automatically.
2026-04-17 21:50:50 +08:00
ZhenYi
4767e1d692 fix(room): IDB load no longer waits for WS connection
loadMore(null) fires immediately — cached messages render instantly.
WS connect runs in parallel; subscribeRoom and reaction batch-fetch
use WS-first request() which falls back to HTTP transparently.
2026-04-17 21:45:55 +08:00
ZhenYi
44c9f2c772 fix(room): ensure WS connect before subscribe; reactions load for IDB path
- setup() now awaits client.connect() before subscribeRoom and loadMore,
  ensuring the connection is truly open so WS is used for both
- subscribeRoom / reactionListBatch: already WS-first via RoomWsClient.request()
- IDB paths (initial + loadMore) now call thisLoadReactions to batch-fetch
  reactions via WS with HTTP fallback, fixing the missing reactions bug
2026-04-17 21:44:45 +08:00
ZhenYi
50f9cc40fe fix(room): load reactions for IDB-cached messages via WS-first fallback
- thisLoadReactions helper: batch-fetches reactions for loaded messages
  via WS (RoomWsClient.request() does WS-first → HTTP fallback automatically)
- Called after both IDB paths (initial load + loadMore) so reactions are
  populated even when messages come from IndexedDB cache
- Also deduplicated API-path reaction loading to use the same helper
2026-04-17 21:43:47 +08:00
ZhenYi
b70d91866c fix(room-ws): try reconnect with existing token before requesting new
On auto-reconnect (scheduleReconnect), attempt connection with the stored
wsToken first. If the WS closes immediately (server rejected the token),
fall back to fetching a fresh token and retrying. Only requests a new
token when the existing one fails or when connect() is called manually
with forceNewToken=true.
2026-04-17 21:40:07 +08:00
ZhenYi
309bc50e86 perf(room): prioritize IndexedDB cache for instant loads
- Remove clearRoomMessages on room switch: IDB cache now persists across
  visits, enabling instant re-entry without API round-trip
- Increase initial API limit from 50 → 200: more messages cached upfront
- Add loadOlderMessagesFromIdb: uses 'by_room_seq' compound index to
  serve scroll-back history from IDB without API call
- loadMore now tries IDB first before falling back to API
2026-04-17 21:36:55 +08:00
ZhenYi
bab675cf60 perf(room): increase virtualizer overscan to 30 for smoother scrolling 2026-04-17 21:31:36 +08:00
ZhenYi
5cd4c66445 perf(room): simplify scroll handler and stabilize callback refs
- Remove useTransition/useDeferredValue from RoomMessageList
- Wrap component in memo to prevent unnecessary re-renders
- Use requestAnimationFrame to defer scroll state updates
- Remove isUserScrolling state (no longer needed)
- Simplify auto-scroll effect: sync distance check + RAF deferred scroll
- Add replyMap memo to decouple reply lookup from row computation
- Stabilize handleEditConfirm to depend on editingMessage?.id only
- Remove Performance Stats panel (RoomPerformanceMonitor)
2026-04-17 21:28:58 +08:00
ZhenYi
991d86237b fix: remove stale onRenderedCountChange prop from RoomMessageList usage 2026-04-17 21:23:03 +08:00
ZhenYi
70381006cf chore(room): remove Performance Stats panel
Unused debug overlay that was tracking virtualized row counts.
2026-04-17 21:21:59 +08:00
ZhenYi
f2a2ae5d7f fix(room): use WS for message fetching, eliminate duplicate WS connection
- Fix initial room load being skipped: `setup()` called `loadMoreRef.current`
  which was null on first mount (ref assigned in later effect). Call `loadMore`
  directly so the initial fetch always fires. WS message.list used when
  connected, HTTP fallback otherwise.
- Rewrite useRoomWs to use shared RoomWsClient instead of creating its own
  raw WebSocket, eliminating duplicate WS connection per room.
- Remove dead loadMoreRef now that setup calls loadMore directly.
2026-04-17 21:18:56 +08:00
ZhenYi
cf5c728286 fix(room): fix scrolling lag, N+1 queries, and multiple WS token requests
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
2026-04-17 21:08:40 +08:00
ZhenYi
60d8c3a617 fix(room): resolve remaining defects from second review
- reaction.rs: query before insert to detect new vs duplicate reactions,
  only publish Redis event when a reaction was actually added
- room.rs: delete Redis seq key on room deletion to prevent seq
  collision on re-creation
- message.rs: use Redis-atomic next_room_message_seq_internal for
  concurrent safety; look up sender display name once for both
  mention notifications and response body; add warn log when
  should_ai_respond fails instead of silent unwrap_or(false)
- ws_universal.rs: re-check room access permission when re-subscribing
  dead streams after error to prevent revoked permissions being bypassed
- RoomChatPanel.tsx: truncate reply preview content to 80 chars
- RoomMessageList.tsx: remove redundant inline style on message row div
2026-04-17 20:28:45 +08:00
ZhenYi
5256e72be7 fix(room): add access check to ws_universal subscribe and fix stream handling
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
- Add `check_room_access` before `manager.subscribe()` in ws_universal
  to prevent unauthorized room subscription (security fix)
- Fix busy-wait in `poll_push_streams`: sleep 50ms when streams are
  empty, yield only when there are active streams
- Re-subscribe dead rooms after stream errors so events are not
  permanently lost until manual reconnect
- Fix streaming message placeholder using fake content as room_id:
  use chunk.room_id from backend instead
- Show toast error on history load failures instead of silent fallback
2026-04-17 17:15:34 +08:00
ZhenYi
d0fc590491 fix(frontend): remove unused Zap import in landing-sections.tsx
Some checks are pending
CI / Frontend Build (push) Blocked by required conditions
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
2026-04-16 21:10:39 +08:00
ZhenYi
5a59f56319 fix(room): revert stale edited_at if messageGet fetch fails
onMessageEdited optimistically set edited_at, then fetched the full
message. If the fetch failed the "Edited" indicator persisted even though
the content was stale. Fix by capturing the original edited_at and
reverting it in the catch block — consistent with editMessage rollback.
2026-04-16 19:34:59 +08:00
ZhenYi
beea8854ce fix(room): fix stale room subscribe after async connect
connect() is async/fire-and-forget — if the user switches rooms while
WS is still connecting, the subscribeRoom() call captures the stale
(activeRoomId) closure value and subscribes to the wrong room. Fix by
re-reading activeRoomIdRef.current after the await so we always subscribe
to the room that is active when the connection actually opens.
2026-04-16 19:34:07 +08:00
ZhenYi
7989f7ba4b fix(room): fix StrictMode reconnect loop, add revokeMessage rollback
- useEffect([wsClient]): remove wsClient from deps to prevent
  React StrictMode double-mount from disconnecting the real client.
  First mount connects client-1; StrictMode cleanup disconnects it.
  Second mount connects client-2; first mount's second cleanup would
  then disconnect client-2, leaving WS permanently unconnected.
  Changing to useEffect([]) + optional chaining fixes this.
- revokeMessage: add optimistic removal + rollback on server rejection,
  consistent with editMessage pattern. Previously a failed delete left the
  message visible with no feedback.
2026-04-16 19:33:14 +08:00
ZhenYi
7416f37cec fix(room): prevent double-send, log resubscribe errors, dim pending messages
- sendMessage: guard with sendingRef to prevent concurrent in-flight
  sends (was missing — rapid clicks could create duplicate messages)
- resubscribeAll: log at warn level instead of silently swallowing,
  so operators can observe auth expiry or persistent failure patterns
- RoomMessageBubble: apply opacity-60 when isPending or isFailed,
  and hide action toolbar for pending messages (can't react/act on
  unconfirmed messages)
2026-04-16 19:29:34 +08:00
ZhenYi
677e88980b fix(room): add edit rollback, clean stream channels on room shutdown/idle 2026-04-16 19:28:23 +08:00
ZhenYi
c89f01b718 feat(room): improve robustness — optimistic send, atomic seq, jitter reconnect
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.
2026-04-16 19:23:06 +08:00
ZhenYi
5482283727 feat(seo): add useHead to all landing pages with Command as Service titles and descriptions
Some checks are pending
CI / Frontend Build (push) Blocked by required conditions
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
2026-04-16 19:12:06 +08:00
ZhenYi
022b45d44f docs(landing): replace 'Real-time' with 'Command-first' in nav, align Enterprise nav desc
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 19:08:20 +08:00
ZhenYi
e59edf6c1a docs(landing): align docs page hero with Command as Service platform
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 19:05:57 +08:00
ZhenYi
757a7044bf docs(landing): align Enterprise pricing and Public Rooms nav with Command as Service
Some checks are pending
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
2026-04-16 19:04:34 +08:00
ZhenYi
260d154db2 docs(landing): final alignment — rooms features, API endpoint, and Pro pricing tagline
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 19:03:27 +08:00
ZhenYi
c9f4e2dbe7 docs(landing): replace 'chat rooms' with 'command-first rooms' in feature descriptions
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 19:02:26 +08:00
ZhenYi
84f2604106 docs(landing): align remaining landing pages with Command as Service concept
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 19:01:27 +08:00
ZhenYi
4c77426965 docs(landing): align all non-business pages with Command as Service concept
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 18:59:37 +08:00
ZhenYi
4eb93da28b docs(landing): update README and SEO defaults to Command as Service tagline
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 18:58:28 +08:00
ZhenYi
a4cb18580b docs(landing): rewrite tagline and features around Command as Service
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
- Badge: 'Command as Service · Human + Agent Engineering'
- Subtitle: 'Every action is a command. Every command is versioned,
  auditable, and composable.'
- Terminal demo: show service deploy, skill run, and replay commands
- Features: lead with Command as Service, rewrite all descriptions
  around the command stream paradigm
- Section header: 'Build. Review. Automate. All via Commands.'
- Highlight: 'Your CLI is also your API'
- Nav/Footer: surface Command as Service in Platform menu
2026-04-16 18:54:50 +08:00
ZhenYi
89deebced6 fix(api): add clone url
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
2026-04-16 18:31:05 +08:00
ZhenYi
8b433c9169 feat(repo): return ssh/https clone URLs from backend
Some checks are pending
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
Add ssh_clone_url and https_clone_url to ProjectRepositoryItem,
constructed from config.ssh_domain() and config.git_http_domain().
Update frontend RepoInfo interface and header.tsx to use the
server-provided URLs instead of hardcoded values.
2026-04-16 18:20:48 +08:00
ZhenYi
b1e93a7cfc fix(init): use resp.error to detect project_not_found instead of HTTP status
Some checks are pending
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
The generated client returns AxiosError as resp when throwOnError=false.
Check resp.error (set by the client to err.response?.data) for the
business-level code/error fields rather than relying on HTTP status.
2026-04-16 18:05:44 +08:00
ZhenYi
41f96af064 fix(init): use isAxiosError to correctly detect 404 on project name availability check 2026-04-16 17:22:25 +08:00
ZhenYi
2afad11cb6 fix(frontend): use workspaceInfo API to get full workspace data for sidebar 2026-04-15 22:55:01 +08:00
ZhenYi
a1ebe47564 fix(frontend): remove unused imports and fix workspace type in homepage 2026-04-15 22:45:07 +08:00
ZhenYi
afbc58d9bf feat(frontend): landing pages with Command as Service concept
- Add landing subpages: pricing, skills, solutions, network, about, docs
- Nav pop cards link to all subpages with nested routes
- Homepage: full landing content with top nav (no sidebar) for logged-in users
- Rewrite copy based on real backend: Git repos, Issues/PRs, Rooms, AI Agents
- Introduce "Command as Service" as core product concept
- Terminal demo shows realistic gitdata CLI commands
- Footer links updated to real routes
- Fix workspace redirect slug guard (undefined route)
2026-04-15 21:45:30 +08:00
ZhenYi
93cfff9738 init 2026-04-15 09:08:09 +08:00