Commit Graph

138 Commits

Author SHA1 Message Date
ZhenYi
3cd5b3003c fix(room): fix mention Enter key by reading textarea DOM directly
Problem: showMentionPopover state is stale when handleKeyDown fires
immediately after handleChange (both in same event loop), causing Enter
to be silently swallowed.

Solution:
- Read textarea.value directly in handleKeyDown to detect @ mentions
- Module-level refs (mentionSelectedIdxRef, mentionVisibleRef) share
  selection state between MentionPopover and ChatInputArea
- handleMentionSelect reads DOM instead of relying on props state
2026-04-18 00:25:03 +08:00
ZhenYi
13f5ff328c fix(room): hoist mentionConfirmRef to module scope to fix TDZ error
ChatInputArea is defined before RoomChatPanel in the file, so its JSX
runs before mentionConfirmRef is declared inside RoomChatPanel. Moving the
ref to module level ensures it's initialized before either component renders.
2026-04-18 00:17:47 +08:00
ZhenYi
14bcc04991 fix(room): update keyboard shortcuts — Ctrl+Enter sends, Shift+Enter newlines, Enter only for mention select
- Ctrl+Enter: send message (was plain Enter)
- Shift+Enter: insert newline (textarea default, passes through)
- Enter alone: only triggers mention selection when popover is open, otherwise does nothing
- Update MentionPopover footer/header hints to reflect new shortcuts
2026-04-18 00:11:13 +08:00
ZhenYi
9b966789fd fix(room): resolve mention IDs to display names when rendering messages
- Pass members/repos/aiConfigs lists to MessageContentWithMentions
- Add resolveName() that looks up ID → display name per mention type
- RoomMessageBubble now resolves user/repository/AI mention UIDs to real names
2026-04-18 00:10:12 +08:00
ZhenYi
a9fc6f9937 feat(room): redesign MentionPopover with modern UI/UX
Visual improvements:
- Glassmorphism backdrop blur and refined shadows
- Color-coded categories: user(sky), repository(violet), AI(emerald)
- Gradient backgrounds and smooth transitions
- Custom avatar icons for each mention type

Interaction enhancements:
- Search text highlighting with yellow background
- Auto-scroll selected item into view
- Selection indicator dot and left border accent
- Keyboard navigation (↑↓) with visual feedback

Components:
- CategoryHeader: icon + label with color theme
- SuggestionItem: avatar + highlighted text + category badge
- LoadingSkeleton: Shimmer loading state
- EmptyState: Illustrated empty/loading states

Uses ScrollArea, Avatar, Skeleton from design system
2026-04-18 00:02:09 +08:00
ZhenYi
aacd9572d1 fix(room): replace useMemo+categories with plain const to fix SWC parser error 2026-04-17 23:53:31 +08:00
ZhenYi
9246a9e6ab fix(room): move eslint-disable comment inside array to fix SWC syntax error 2026-04-17 23:52:27 +08:00
ZhenYi
f9a3b51406 perf(room): optimize MentionPopover with caching, stable refs, and loading states
- Position caching: skip recalculation when text+cursor unchanged
- TempDiv reuse: cached DOM element on textarea, created once
- Stable refs pattern: avoid stale closures in keyboard handler
- Auto-selection: reset to first item on category/list change
- Loading states: reposLoading + aiConfigsLoading wired from context
2026-04-17 23:48:26 +08:00
ZhenYi
26682973e7 feat(room): redesign mention system with AST-based format
Backend:
- Add MENTION_TAG_RE matching new `<mention type="..." id="...">label</mention>` format
- Extend extract_mentions() and resolve_mentions() to parse new format (legacy backward-compatible)

Frontend:
- New src/lib/mention-ast.ts: AST types (TextNode, MentionNode, AiActionNode),
  parse() and serialize() functions for AST↔HTML conversion
- MentionPopover: load @repository: from project repos, @ai: from room_ai configs
  (not room members); output new HTML format with ID instead of label
- MessageMentions: use AST parse() for rendering (falls back to legacy parser)
- ChatInputArea: insertMention now produces `<mention type="user" id="...">label</mention>`
- RoomParticipantsPanel: onMention passes member UUID to insertMention
- RoomContext: add projectRepos and roomAiConfigs for mention data sources
2026-04-17 23:43:26 +08:00
ZhenYi
0ea6440ea3 fix(deploy): add proxy-http-version annotation for WebSocket support 2026-04-17 23:18:00 +08:00
ZhenYi
6431709669 fix(room): show model name instead of UID in settings panel
- Load model list on settings panel mount so names are always available.
- SelectValue now displays the selected model's name by looking up
  availableModels, falling back to UID if name not found.
- Existing AI configs list also shows model name instead of raw UID.
2026-04-17 23:17:56 +08:00
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
4f1ea95b58 fix(room): add missing stream column to room_ai table
Create migration m20260417_000001_add_stream_to_room_ai that adds
the `stream BOOLEAN NOT NULL DEFAULT true` column to room_ai.
The model definition includes this field but the original migration
was missing it.

Also format libs/api/room/ws.rs and add gitdata.ai to allowed
WS origins.
2026-04-17 23:09:55 +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
0cbf6d6aa1 fix(room): accept comma-separated message_ids string in batch reaction endpoint 2026-04-17 22:28:24 +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
afb1bbeb71 fix(service): use default_branch for graph and reflog endpoints
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
- git_commit_graph: use default_branch when rev is None
- git_commit_graph_react: use default_branch when rev is None
- git_commit_reflog: fall back to default_branch when HEAD is detached

Fixes errors:
- reference 'refs/heads/master' not found (graph-react endpoint)
- HEAD has no name (reflog endpoint when HEAD is detached)
2026-04-17 16:36:44 +08:00
ZhenYi
a4dc507b66 fix(git): multiple branch and upstream query bugs
1. branch_list: Fix is_head comparison
   - head_name now keeps full ref (e.g., "refs/heads/main")
   - Previously stripped to short name causing is_head to always be false

2. branch_get/branch_exists: Fix name resolution for branches with '/'
   - Previously, "feature/testing" was assumed to be a remote branch
   - Now tries both refs/heads/ and refs/remotes/ candidates
   - Correctly handles local branches like "feature/testing"

3. sync_refs: Fix upstream reference format
   - Was storing "refs/remotes/{}/branch" (broken pattern)
   - Now properly queries git2 for actual upstream branch name
2026-04-17 16:30:58 +08:00
ZhenYi
ee4ff6c752 fix(api): fix branch route order to prevent shadowing
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
Move all specific branch routes before /branches/{name} to prevent
route shadowing. Previously, routes like /branches/rename, /branches/move,
/branches/upstream, /branches/diff, etc. were shadowed by /branches/{name}.
2026-04-17 16:11:23 +08:00
ZhenYi
1272615d50 fix(api): fix refs route order to prevent shadowing
Move specific routes (/refs/rename, /refs/update) before parameterized
routes (/refs/{name}) to avoid route shadowing.
2026-04-17 16:10:23 +08:00
ZhenYi
4cee9975d5 fix(api): fix commit route order to prevent InvalidOid("reflog") error
Move specific routes (/commits/reflog, /commits/branches, /commits/tags)
before parameterized routes (/commits/{oid}) to avoid route shadowing.
Previously, /commits/reflog was matched by /commits/{oid} with oid="reflog",
causing InvalidOid("reflog") errors.

Also fixes other potential route shadowing issues in commit routes.
2026-04-17 16:07:01 +08:00
ZhenYi
82ed726848 fix(git): convert default_branch to full ref name (refs/heads/*)
The database stores short branch names (e.g., "main"), but git2's push_ref()
requires full reference names (e.g., "refs/heads/main"). This fixes all
service-layer endpoints to convert default_branch to the full ref format.

Fixed endpoints:
- git_readme: convert to refs/heads/{branch}
- git_commit_count: convert to refs/heads/{branch}
- git_contributors: convert to refs/heads/{branch}
- git_commit_log: convert to refs/heads/{branch}
- git_commit_walk: convert to refs/heads/{branch}

Resolves errors:
- Internal("the given reference name 'main' is not valid")
2026-04-17 15:56:08 +08:00
ZhenYi
4c49953572 fix(git): use default_branch in git_commit_log and update API docs
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
- git_commit_log: default to repo.default_branch when rev is None
- Update API documentation to reflect default_branch instead of HEAD
2026-04-17 15:16:18 +08:00
ZhenYi
2bd1339d6c fix(git): use repo.default_branch instead of HEAD to avoid master/branch mismatch
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 #[serde(default)] to MergeAnalysisQuery.their_oid and MergeRefAnalysisQuery fields
  since they come from path parameters, not query strings
- git_readme/git_commit_count/git_contributors: default to repo.default_branch
  instead of HEAD to avoid errors when HEAD points to refs/heads/master but
  the actual default branch is main
2026-04-17 15:13:08 +08:00
ZhenYi
be423aabb6 feat(error): add error print info to system command
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-17 14:42:51 +08:00
ZhenYi
0d9256793c test sync 2026-04-17 14:33:32 +08:00
ZhenYi
845aba443b fix(deploy): add APP_REPOS_ROOT env to git-hook deployment
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
Missing env var — git-hook worker needs APP_REPOS_ROOT to resolve repo
storage paths. Added explicit env block with APP_REPOS_ROOT=/data/repos,
consistent with gitserver deployment.
2026-04-17 14:21:15 +08:00
ZhenYi
3354055e6d fix(operator): mount /data PVC into git-hook deployment
GitHook controller was generating a Deployment without any persistent
storage — only a ConfigMap volume at /config. The worker needs /data to
access repo storage paths (APP_REPOS_ROOT defaults to /data/repos).

Changes:
- GitHookSpec: added storage_size field (default 10Gi), matching the
  pattern already used by GitServerSpec
- git_hook.rs reconcile(): now creates a PVC ({name}-data) before the
  Deployment, mounts it at /data, and sets APP_REPOS_ROOT=/data/repos
- git-hook-crd.yaml: synced storageSize field into the CRD schema
2026-04-17 14:15:38 +08:00
ZhenYi
e50c31daa6 fix(deploy): mount /data PVC into git-hook container
The git-hook worker needs /data to access repo storage paths, but the
deployment defined the shared-data PVC volume at pod level without
attaching it to the container via volumeMounts. Added the missing
volumeMounts block (name: shared-data, mountPath: /data) so the PVC is
properly mounted into the git-hook container, consistent with app and
static deployments.
2026-04-17 14:06:24 +08:00
ZhenYi
7c042c7b9d fix(git-hook): use HookService instead of non-existent GitServiceHooks
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
GitServiceHooks was renamed to HookService in the hook module refactor.
Updated main.rs to:
- Use HookService::new() with correct parameters (no http client)
- Call start_worker() which returns CancellationToken
- Wait on cancel.cancelled() instead of double-awaiting ctrl_c
- Clone token before moving into signal handler task
2026-04-17 13:54:17 +08:00
ZhenYi
7a2a3c51c4 fix(git/hook): make sync_tags Send-safe by collecting git2 data synchronously
sync_tags held git2 types (StringArray, Repository) across .await points
on sea-orm DB operations. Applied the same pattern as sync_refs:
- Added collect_tag_refs() sync helper that collects all tag metadata
  (name, oid, description, tagger) into owned TagTip structs.
- sync_tags now calls collect_tag_refs() before any .await, so no git2
  types cross the async boundary.
- Removed #[allow(dead_code)] from TagTip now that it's consumed.
2026-04-17 13:48:05 +08:00
ZhenYi
01b18c97df fix(git/hook): address review findings — webhooks, git2 Send, touch_count, rollback
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
- pool/worker.rs: only dispatch webhooks after sync succeeds (skip on error);
  pass max_retries from PoolConfig to HookWorker
- sync/branch.rs: replaced with stub (sync_refs moved to commit.rs)
- sync/commit.rs: add collect_git_refs() that collects BranchTip/TagTip from
  git2 entirely within one sync call; sync_refs now uses owned data so no
  git2 types cross .await boundaries (future is Send)
- sync/fsck.rs: remove extraneous "HEAD" arg from rollback git update-ref
  (correct syntax is: update-ref -m <msg> <ref> <new-sha> — no old-sha)
- webhook_dispatch.rs: touch_count uses Expr::col().add(1) for atomic
  increment instead of overwriting with Expr::value(1)
- pool/mod.rs: pass config.redis_max_retries to HookWorker
2026-04-17 13:44:33 +08:00
ZhenYi
1fed9fc8ab fix(git/hook): address review findings — fs blocking, redis timeout, backoff, slog
- sync/mod.rs: wrap scan_skills_from_dir in spawn_blocking to avoid
  blocking the async executor; use to_path_buf() to get owned PathBuf
- pool/worker.rs: replace 500ms poll sleep with cancellation-is_cancelled
  check (eliminates artificial latency); add exponential backoff on Redis
  errors (1s base, 32s cap, reset on success)
- pool/redis.rs: add 5s timeout on pool.get() for all three methods
  (next, ack_raw, nak_with_retry) to prevent indefinite blocking on
  unresponsive Redis
- sync/gc.rs: add comment explaining why git gc --auto non-zero exit
  is benign
- webhook_dispatch.rs: remove unnecessary format! wrappers in slog macros
- config/hook.rs: document max_concurrent intent (K8s operator/HPA, not
  the single-threaded worker itself)
2026-04-17 13:20:31 +08:00