Skip to content

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Jan 14, 2026

Summary

  • Add Git visualization components to Dashboard for branch/commit tracking
  • Implement type definitions, API client, and state management for Git data
  • Create 4 reusable components: GitBranchIndicator, CommitHistory, BranchList, GitSection
  • Add WebSocket handlers for real-time commit_created and branch_created events
  • Integrate GitSection into Dashboard Overview tab (active/review/complete phases)

Test plan

  • 74 new tests for Git types, API client, reducer actions, and components
  • All 1743 tests passing
  • ESLint passes with no errors
  • TypeScript check passes
  • Components render correctly with loading/error states
  • WebSocket message mapper handles Git events

Summary by CodeRabbit

  • New Features

    • Git section added to Dashboard showing current branch with dirty-change indicator
    • Recent commits view with short hashes, file-change counts, relative timestamps, and item limits
    • Branch list with status badges (active, merged, abandoned), merge info, filtering, and click handling
    • Real-time updates for new commits and branches
  • Tests

    • Comprehensive unit and component tests covering API client, state handling, UI, and websocket mappings

✏️ Tip: You can customize this high-level summary in your review settings.

Add Git branch and commit visualization to the Dashboard Overview tab:

- Add Git types (GitBranch, GitCommit, GitStatus, GitState)
- Add Git API client for consuming endpoints from ticket #270
- Add 7 Git reducer actions for state management
- Create GitBranchIndicator component (current branch with dirty indicator)
- Create CommitHistory component (recent commits with relative timestamps)
- Create BranchList component (branches with status badges)
- Create GitSection container with SWR data fetching (30s refresh)
- Integrate GitSection in Dashboard Overview tab (active/review/complete phases)
- Add WebSocket handlers for commit_created and branch_created events

Test coverage: 74 new tests (all passing)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a complete Git UI subsystem: typed Git API client, Git TypeScript types and initial state, reducer actions and WebSocket handlers for commit/branch events, four React components (GitSection, GitBranchIndicator, CommitHistory, BranchList) with dashboard integration, and extensive unit tests across layers.

Changes

Cohort / File(s) Summary
Types & State
web-ui/src/types/git.ts, web-ui/src/types/agentState.ts, web-ui/src/types/index.ts
New Git types (GitBranch, GitCommit, GitStatus, GitState, BranchStatus), INITIAL_GIT_STATE, helpers (isBranchActive/isBranchMerged); extend AgentState with gitState; add Git action interfaces and add branch_created WebSocket type.
API Client
web-ui/src/api/git.ts
New typed Git REST client: getGitStatus, getCommits (branch/limit), getBranches (status), getBranch, getCurrentBranch, getRecentCommits; builds queries with URLSearchParams and uses authFetch.
Reducer & WebSocket
web-ui/src/reducers/agentReducer.ts, web-ui/src/lib/websocketMessageMapper.ts
Add Git handlers to reducer (GIT_STATUS_LOADED, GIT_COMMITS_LOADED, GIT_BRANCHES_LOADED, COMMIT_CREATED, BRANCH_CREATED, GIT_LOADING, GIT_ERROR) with immutability and limits; websocket mapper adds dedicated commit_created and branch_created cases emitting corresponding actions.
UI Components
web-ui/src/components/git/...
web-ui/src/components/git/GitBranchIndicator.tsx, .../CommitHistory.tsx, .../BranchList.tsx, .../GitSection.tsx, .../index.ts
New memoized components: GitBranchIndicator (branch + dirty dot), CommitHistory (recent commits, relative times, maxItems), BranchList (filterable with status badges), GitSection (SWR-driven aggregation, refreshInterval, composes children); barrel export.
Dashboard Integration
web-ui/src/components/Dashboard.tsx
GitSection added to Dashboard Overview for specific project phases (active, review, complete).
Tests — Components & API & Types
web-ui/__tests__/components/git/*.test.tsx, web-ui/__tests__/api/git.test.ts, web-ui/__tests__/types/git.test.ts
Comprehensive unit tests for components (rendering, loading/error states, styling, filtering), API client (auth header, query params, error handling), and Git types.
Tests — Reducer & WebSocket
web-ui/__tests__/reducers/gitActions.test.ts, web-ui/__tests__/lib/websocketMessageMapper.test.ts
Reducer tests for Git actions (init, FIFO commit prepend, branch append, loading/error); websocket mapper tests for commit_created/branch_created validation and mapping.
Test Fixtures
web-ui/test-utils/agentState.fixture.ts
AgentState fixtures updated to include gitState: null.
Barrel & Exports
web-ui/src/components/git/index.ts, web-ui/src/types/index.ts
Re-exports added for Git components and types to centralize imports.
New Tests Added
web-ui/__tests__/api/git.test.ts, web-ui/__tests__/components/git/*.test.tsx, web-ui/__tests__/types/git.test.ts, web-ui/__tests__/reducers/gitActions.test.ts, web-ui/__tests__/lib/websocketMessageMapper.test.ts
+ multiple new test files (mocking SWR, fetch, localStorage, WebSocket payloads).

Sequence Diagram(s)

sequenceDiagram
    participant UI as GitSection (UI)
    participant SWR as SWR (fetch layer)
    participant API as Git API client
    participant Backend as Backend HTTP API
    participant WS as WebSocket
    participant Reducer as Agent Reducer / Store

    UI->>SWR: subscribe to status, commits, branches
    SWR->>API: getGitStatus / getCommits / getBranches
    API->>Backend: HTTP requests (/api/projects/:id/git/...)
    Backend-->>API: respond with JSON (status/commits/branches)
    API-->>SWR: resolve promises
    SWR-->>UI: provide data -> UI renders children

    Note over WS,Reducer: real-time events
    WS->>Reducer: receive commit_created / branch_created -> dispatch action
    Reducer->>Reducer: update gitState (prepend commit / append branch)
    Reducer-->>UI: store notifies subscribers -> UI updates
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 Hops of joy for branches bright,

Commits glitter in the night;
Status badges, tidy and quick,
New Git views — a rabbit's pick! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding Git visualization components to the UI, which is the core objective of this changeset.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/272-git-visualization

🧹 Recent nitpick comments
web-ui/__tests__/lib/websocketMessageMapper.test.ts (1)

755-777: Consider adding a test for empty string fields.

The validation check !msg.commit_hash would pass for an empty string "", which is falsy. This is correct behavior, but an explicit test would document this edge case.

Optional test for empty string validation
it('should return null when commit_hash is empty string', () => {
  const message = {
    type: 'commit_created' as const,
    project_id: projectId,
    timestamp: baseTimestamp,
    commit_hash: '',
    commit_message: 'feat: Add new feature',
  };

  const action = mapWebSocketMessageToAction(message);

  expect(action).toBeNull();
});
web-ui/src/reducers/agentReducer.ts (1)

450-465: Add idempotency check to prevent duplicate branches on reconnection.

The BRANCH_CREATED reducer action appends branches without checking if a branch with the same ID already exists. WebSocket reconnection or message replay could cause duplicates to accumulate.

The WebSocket layer and message mapper do not deduplicate events. Add a check before appending:

Suggested implementation
 case 'BRANCH_CREATED': {
   const { branch } = action.payload;
   const currentGitState = state.gitState ?? { ...INITIAL_GIT_STATE };

   // Defensive: ensure branches is an array even if state was partially initialized
   const currentBranches = currentGitState.branches || [];
+
+  // Skip if branch already exists (idempotent handling for replayed events)
+  if (currentBranches.some((b) => b.id === branch.id)) {
+    newState = state;
+    break;
+  }

   newState = {
     ...state,
     gitState: {
       ...currentGitState,
       branches: [...currentBranches, branch],
     },
   };
   break;
 }

(Check by id rather than name since id is the primary identifier in GitBranch.)

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3db9c7 and 2bc4ff1.

📒 Files selected for processing (7)
  • web-ui/__tests__/components/git/GitBranchIndicator.test.tsx
  • web-ui/__tests__/lib/websocketMessageMapper.test.ts
  • web-ui/src/api/git.ts
  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/git/GitSection.tsx
  • web-ui/src/lib/websocketMessageMapper.ts
  • web-ui/src/reducers/agentReducer.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • web-ui/tests/components/git/GitBranchIndicator.test.tsx
  • web-ui/src/api/git.ts
🧰 Additional context used
📓 Path-based instructions (4)
web-ui/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web-ui/src/**/*.{ts,tsx}: Use TypeScript 5.3+ for frontend development
Use React 18 and Next.js 14 for frontend development
Use Tailwind CSS for styling with Nova design system template
Use shadcn/ui components from @/components/ui/ for UI elements
Use Hugeicons (@hugeicons/react) for all icons, never mix with lucide-react
Use Nova color palette variables (bg-card, text-foreground, etc.) instead of hardcoded color values
Use cn() utility for conditional CSS classes in React components
Use process.env.NEXT_PUBLIC_API_URL with fallback to http://localhost:8080 for API endpoint configuration
Include auth token as query parameter in WebSocket connections (?token=TOKEN)
Store auth tokens in localStorage with key 'auth_token' and include token in API requests via Authorization header

Files:

  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/git/GitSection.tsx
  • web-ui/src/reducers/agentReducer.ts
  • web-ui/src/lib/websocketMessageMapper.ts
web-ui/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web-ui/src/components/**/*.{ts,tsx}: Use React Context with useReducer for centralized state management in Dashboard
Wrap AgentStateProvider with ErrorBoundary component for graceful error handling
Use React.memo on Dashboard sub-components and useMemo for derived state to optimize performance

Files:

  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/git/GitSection.tsx
web-ui/src/reducers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use useReducer with 13+ action types for agent state management; implement timestamp conflict resolution with last-write-wins strategy

Files:

  • web-ui/src/reducers/agentReducer.ts
web-ui/src/lib/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Implement WebSocket automatic reconnection with exponential backoff (1s → 30s) and full state resync

Files:

  • web-ui/src/lib/websocketMessageMapper.ts
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/components/**/*.{ts,tsx} : Use React Context with useReducer for centralized state management in Dashboard
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/components/**/*.{ts,tsx} : Use React.memo on Dashboard sub-components and useMemo for derived state to optimize performance

Applied to files:

  • web-ui/src/components/git/CommitHistory.tsx
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Applies to docs/web-ui/src/components/**/*.{ts,tsx} : Use functional React components with TypeScript interfaces

Applied to files:

  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/git/GitSection.tsx
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/**/*.{ts,tsx} : Use shadcn/ui components from @/components/ui/ for UI elements

Applied to files:

  • web-ui/src/components/git/CommitHistory.tsx
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/lib/**/*.{ts,tsx} : Implement WebSocket automatic reconnection with exponential backoff (1s → 30s) and full state resync

Applied to files:

  • web-ui/__tests__/lib/websocketMessageMapper.test.ts
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Applies to docs/web-ui/**/__tests__/**/*.test.{ts,tsx} : Create JavaScript test files colocated or in __tests__/ as *.test.ts

Applied to files:

  • web-ui/__tests__/lib/websocketMessageMapper.test.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/reducers/**/*.{ts,tsx} : Use useReducer with 13+ action types for agent state management; implement timestamp conflict resolution with last-write-wins strategy

Applied to files:

  • web-ui/src/reducers/agentReducer.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/components/**/*.{ts,tsx} : Use React Context with useReducer for centralized state management in Dashboard

Applied to files:

  • web-ui/src/reducers/agentReducer.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/components/**/*.{ts,tsx} : Wrap AgentStateProvider with ErrorBoundary component for graceful error handling

Applied to files:

  • web-ui/src/reducers/agentReducer.ts
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description

Applied to files:

  • web-ui/src/lib/websocketMessageMapper.ts
🧬 Code graph analysis (4)
web-ui/src/components/git/CommitHistory.tsx (1)
web-ui/src/types/git.ts (1)
  • GitCommit (39-46)
web-ui/src/components/git/GitSection.tsx (4)
web-ui/src/components/git/index.ts (1)
  • GitSectionProps (17-17)
web-ui/src/types/index.ts (3)
  • GitStatus (252-252)
  • GitCommit (251-251)
  • GitBranch (250-250)
web-ui/src/types/git.ts (3)
  • GitStatus (55-61)
  • GitCommit (39-46)
  • GitBranch (22-30)
web-ui/src/api/git.ts (3)
  • getGitStatus (36-40)
  • getCommits (64-85)
  • getBranches (99-116)
web-ui/src/reducers/agentReducer.ts (1)
web-ui/src/types/git.ts (1)
  • INITIAL_GIT_STATE (113-119)
web-ui/src/lib/websocketMessageMapper.ts (2)
web-ui/src/types/index.ts (1)
  • WebSocketMessage (126-204)
specs/005-project-schema-refactoring/contracts/agent-state-api.ts (1)
  • WebSocketMessage (316-321)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: Backend Unit Tests
  • GitHub Check: E2E Smoke Tests (Chromium)
  • GitHub Check: opencode-review
  • GitHub Check: Macroscope - Correctness Check
🔇 Additional comments (21)
web-ui/src/lib/websocketMessageMapper.ts (1)

250-278: LGTM! Well-structured commit message handler with proper validation.

The validation ensures commits with empty hashes are rejected before they can break downstream lookups. The derived short_hash and files_changed count are handled correctly.

web-ui/__tests__/lib/websocketMessageMapper.test.ts (4)

695-725: Good test coverage for commit_created validation.

The test validates the complete payload structure including derived fields (short_hash, files_changed count) and timestamp parsing. The coverage for missing commit_hash and commit_message ensures the validation logic is exercised.


780-808: Good test structure for branch_created mapping.

The test validates the expected payload structure including the status: 'active' default and created_at from the message timestamp.


842-853: Good coverage for missing data object edge case.

This test ensures the optional chaining (msg.data?.branch_name) handles the case where data is entirely absent, preventing runtime errors.


873-897: Development warning test properly manages environment state.

The test correctly saves and restores process.env to avoid polluting other tests. The mockImplementation() prevents console noise during test runs.

web-ui/src/components/git/CommitHistory.tsx (4)

1-15: LGTM!

The file header, imports, and type import are well-structured. Using @hugeicons/react for icons and the @/types/git path alias aligns with coding guidelines.


16-27: LGTM!

Well-documented props interface with appropriate TypeScript types and JSDoc annotations.


55-88: LGTM!

The CommitItem subcomponent is well-implemented:

  • Properly memoized for performance optimization per coding guidelines.
  • Semantic <time> element without the previously flagged invalid ARIA role.
  • Correct pluralization logic for files_changed.
  • Clean accessibility attributes with data-testid.

93-185: LGTM!

Excellent implementation:

  • Component properly memoized with useMemo for derived state per coding guidelines.
  • Comprehensive state handling (loading, error, empty, populated).
  • Correct use of commit.hash as the React key for stable identity.
  • Footer correctly shows "Showing X of Y commits" only when truncated.
  • Uses Nova design system color variables consistently.
web-ui/src/components/git/GitSection.tsx (6)

1-24: LGTM!

Clean file structure with proper imports. Uses @hugeicons/react for icons and types from @/types/git per coding guidelines.


25-32: LGTM!

Props interface is well-documented with sensible defaults.


53-62: Verified: SWR cache key now includes maxCommits.

The cache key git-commits-${projectId}-${maxCommits} correctly incorporates the limit parameter, ensuring proper cache invalidation when maxCommits changes.


75-80: LGTM!

Good pattern for combined state management. The derived isLoading and hasError are used in conjunction with data availability checks (lines 83, 98), allowing partial data display while still showing sub-component loading/error states.


82-112: LGTM!

Appropriate loading and error state handling with graceful degradation. The conditions correctly show these states only when no data is available from any source, allowing partial content to display once some data arrives.


114-155: LGTM!

Clean component composition with proper nullish coalescing defaults. Each child component receives its own loading and error states, enabling granular UI feedback. The component follows memoization guidelines with memo() and displayName.

web-ui/src/reducers/agentReducer.ts (6)

13-13: LGTM!

Import is correctly added to support the null-coalescing fallback pattern used in Git action handlers.


23-33: LGTM!

Lazy initialization of gitState: null is a sensible approach—Git state is only hydrated when the first Git action fires, avoiding unnecessary overhead for users who don't need Git features.


379-397: LGTM!

The GIT_STATUS_LOADED handler correctly initializes Git state on first use via null-coalescing, updates status immutably, and clears loading/error states. Clean pattern that's reused consistently across all Git load actions.


399-429: LGTM!

GIT_COMMITS_LOADED and GIT_BRANCHES_LOADED follow the same immutable update pattern with proper state initialization fallback and loading/error state management.


431-448: LGTM!

COMMIT_CREATED correctly:

  • Defensively handles missing recentCommits array
  • Implements FIFO with 10-item limit (slice(0, 9) + prepend)
  • Preserves other gitState fields

467-492: LGTM!

GIT_LOADING and GIT_ERROR handlers are clean and correctly manage loading/error state transitions with proper null-coalescing initialization.


Comment @coderabbitai help to get the list of available commands and usage tips.

@frankbria frankbria linked an issue Jan 14, 2026 that may be closed by this pull request
4 tasks
@macroscopeapp
Copy link

macroscopeapp bot commented Jan 14, 2026

Add Git visualization to the Dashboard and implement authenticated Git API clients in web-ui/src/components/git/GitSection.tsx and web-ui/src/api/git.ts

Introduce GitSection with GitBranchIndicator, CommitHistory (max 5), and BranchList, wire reducer/actions/state for Git data, and add API clients for status, commits (limit 10 helper), branches, and branch fetch; update websocket mapping for commit_created and branch_created.

📍Where to Start

Start with GitSection data flow and SWR hooks in GitSection.tsx, then review API client functions in git.ts and reducer updates in agentReducer.ts.


Macroscope summarized 2bc4ff1.

Comment on lines +557 to +559
{(projectData.phase === 'active' ||
projectData.phase === 'review' ||
projectData.phase === 'complete') && (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase check here bypasses normalizePhase(...). Consider using it so 'development' (or future aliases) still show GitSection.

Suggested change
{(projectData.phase === 'active' ||
projectData.phase === 'review' ||
projectData.phase === 'complete') && (
{(normalizePhase(projectData.phase) === 'development' ||
normalizePhase(projectData.phase) === 'review' ||
normalizePhase(projectData.phase) === 'complete') && (

🚀 Want me to fix this? Reply ex: "fix it for me".

function formatRelativeTime(timestamp: string): string {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatRelativeTime renders "Invalid Date" when commit.timestamp is invalid. Consider adding a guard (e.g., check isNaN(date.getTime())) and return a safe fallback string.

Suggested change
const diffMs = now.getTime() - date.getTime();
const time = date.getTime();
if (Number.isNaN(time)) return '';
const diffMs = now.getTime() - time;

🚀 Want me to fix this? Reply ex: "fix it for me".

@github-actions
Copy link
Contributor

Review: PR #283 - Git Visualization Components

Overall Assessment

This is a well-structured implementation that follows the project's conventions and integrates cleanly with the existing state management system. The code quality is high with good TypeScript typing, proper use of memoization, and follows the Nova design system. Test coverage is comprehensive with 74 new tests.

✅ Strengths

Code Quality:

  • Clean, well-documented code with comprehensive JSDoc comments
  • Proper use of for component optimization
  • Consistent following of Nova design system (using , , Hugeicons)
  • Good separation of concerns between types, API, components, and reducers
  • Proper TypeScript interfaces matching backend API contracts

Architecture:

  • Integrates cleanly with existing Context + Reducer pattern
  • Uses SWR for efficient data fetching with appropriate refresh intervals
  • WebSocket handlers for real-time and events
  • Type-safe reducer actions with proper immutability

Testing:

  • 74 new tests covering types, API client, reducer actions, and all 4 components
  • Good test fixtures and mock data
  • Tests cover loading, error, and empty states

⚠️ Issues Found

1. Missing Null Checks in WebSocket Message Mapper (Medium)

Location: src/lib/websocketMessageMapper.ts:250-288

The commit_created and branch_created handlers don't validate required fields before creating actions. If WebSocket messages are incomplete, this could cause runtime errors.

commit_created handler:

case 'commit_created': {
  const msg = message as WebSocketMessage;
  return {
    type: 'COMMIT_CREATED',
    payload: {
      commit: {
        hash: msg.commit_hash || '',  // ⚠️ Empty string if missing
        short_hash: (msg.commit_hash || '').slice(0, 7),  // ⚠️ 'slice' on empty string
        message: msg.commit_message || 'Commit created',
        author: msg.agent || 'Agent',
        timestamp: message.timestamp.toString(),
        files_changed: Array.isArray(msg.files_changed)
          ? msg.files_changed.length
          : undefined,
      },
      taskId: msg.task_id,
      timestamp,
    },
  };
}

Issue: If commit_hash is missing/undefined, the action creates a commit with an empty hash. This could cause issues downstream where commits are keyed by hash.

Recommended Fix:

case 'commit_created': {
  const msg = message as WebSocketMessage;
  if (!msg.commit_hash || !msg.commit_message) {
    if (process.env.NODE_ENV === 'development') {
      console.warn('[WebSocketMapper] commit_created message missing required fields', msg);
    }
    return null;  // Skip invalid messages
  }
  
  return {
    type: 'COMMIT_CREATED',
    payload: {
      commit: {
        hash: msg.commit_hash,
        short_hash: msg.commit_hash.slice(0, 7),
        message: msg.commit_message,
        author: msg.agent || 'Agent',
        timestamp: message.timestamp.toString(),
        files_changed: Array.isArray(msg.files_changed)
          ? msg.files_changed.length
          : undefined,
      },
      taskId: msg.task_id,
      timestamp,
    },
  };
}

branch_created handler:

case 'branch_created': {
  const msg = message as WebSocketMessage;
  return {
    type: 'BRANCH_CREATED',
    payload: {
      branch: {
        id: msg.data?.id || 0,  // ⚠️ 0 is not a valid ID
        branch_name: msg.data?.branch_name || '',  // ⚠️ Empty string if missing
        issue_id: msg.data?.issue_id || 0,  // ⚠️ 0 is not a valid issue ID
        status: 'active' as const,
        created_at: message.timestamp.toString(),
      },
      timestamp,
    },
  };
}

Issue: Branches with id: 0 and empty branch_name will break the UI when mapped (especially since components use branch.id as React keys).

Recommended Fix:

case 'branch_created': {
  const msg = message as WebSocketMessage;
  if (!msg.data?.branch_name || !msg.data?.id) {
    if (process.env.NODE_ENV === 'development') {
      console.warn('[WebSocketMapper] branch_created message missing required fields', msg);
    }
    return null;
  }
  
  return {
    type: 'BRANCH_CREATED',
    payload: {
      branch: {
        id: msg.data.id,
        branch_name: msg.data.branch_name,
        issue_id: msg.data.issue_id ?? 0,
        status: 'active' as const,
        created_at: message.timestamp.toString(),
      },
      timestamp,
    },
  };
}

2. Reducer Assumptions About gitState Structure (Low)

Location: src/reducers/agentReducer.ts:431-460

The COMMIT_CREATED and BRANCH_CREATED actions assume gitState has recentCommits and branches arrays initialized, but the state could be partially initialized:

case 'COMMIT_CREATED': {
  const { commit } = action.payload;
  const currentGitState = state.gitState ?? { ...INITIAL_GIT_STATE };
  
  // ⚠️ recentCommits might be undefined if gitState was partially loaded
  const updatedCommits = [commit, ...currentGitState.recentCommits.slice(0, 9)];
  
  newState = {
    ...state,
    gitState: {
      ...currentGitState,
      recentCommits: updatedCommits,
    },
  };
  break;
}

Issue: While using INITIAL_GIT_STATE as fallback is good, if state transitions happen out of order, recentCommits could be undefined in some edge cases.

Analysis: The code is actually correct because INITIAL_GIT_STATE provides defaults for all fields. However, adding explicit checks would make it more defensive:

case 'COMMIT_CREATED': {
  const { commit } = action.payload;
  const currentGitState = state.gitState ?? { ...INITIAL_GIT_STATE };
  
  // Explicit check for safety
  const currentCommits = currentGitState.recentCommits || [];
  const updatedCommits = [commit, ...currentCommits.slice(0, 9)];
  
  newState = {
    ...state,
    gitState: {
      ...currentGitState,
      recentCommits: updatedCommits,
    },
  };
  break;
}

This is low priority since the current implementation is technically correct.

3. getBranch URL Encoding Logic (Low)

Location: src/api/git.ts:126-134

export async function getBranch(
  projectId: number,
  branchName: string
): Promise<GitBranch> {
  const encodedName = encodeURIComponent(branchName);
  return authFetch<GitBranch>(
    `${API_BASE_URL}/api/projects/${projectId}/git/branches/${encodedName}`
  );
}

Issue: If branchName is already URL-encoded by the caller, this will double-encode it. For example, feature/new-branch encoded twice becomes feature%252Fnew-branch.

Analysis: In practice this is unlikely to be an issue since branch names typically don't have special characters, and callers shouldn't pre-encode. However, documenting this assumption would be helpful.

Recommended Addition:

/**
 * Fetch a specific branch by name
 *
 * @param projectId - Project ID
 * @param branchName - Branch name (should NOT be URL-encoded)
 * @returns Branch details
 * @throws Error if request fails, not found, or not authenticated
 */

Performance Considerations

✅ No performance issues identified:

  • SWR caching with 30-second refresh interval is appropriate for Git data
  • useMemo correctly used for filtering commits/branches
  • All components wrapped in memo() to prevent unnecessary re-renders
  • FIFO trimming in reducer (commits limited to 10, branches unlimited but typically small)
  • No expensive computations or rendering issues

Security Considerations

✅ No security issues identified:

  • Uses authFetch which includes JWT tokens via Authorization header
  • No direct HTML insertion (uses React's built-in escaping)
  • No client-side data sanitization needed (backend handles this)
  • No sensitive data exposed in error messages
  • WebSocket messages are properly typed and validated

Test Coverage

✅ Excellent test coverage:

  • Types: git.test.ts tests for type guards and INITIAL_GIT_STATE
  • API: git.test.ts tests for all 4 functions with auth, error, and happy paths
  • Reducer: gitActions.test.ts tests for all 6 Git action types with edge cases
  • Components: 4 test files covering loading, error, empty, and populated states

Total: 74 new tests across 7 test files (1313 lines of test code)

The test fixtures in agentState.fixture.ts were properly updated to include gitState: null in all mock state creators.

Minor Style Suggestions

  1. Import consistency: The imports in GitSection.tsx and other components follow the project style correctly. No issues.

  2. Error messages: Some error messages could be more user-friendly:

    • GitBranchIndicator: Shows "Git error" for any error - could differentiate between network errors vs Git repository errors
    • This is a minor UX enhancement, not a bug

Summary

High Priority: None

Medium Priority:

  • Add validation to WebSocket message handlers for commit_created and branch_created

Low Priority:

  • Add explicit safety checks in reducer (though technically not needed)
  • Document URL encoding expectation in getBranch docs

Recommendation

Approve with minor changes requested.

The implementation is solid, well-tested, and follows project conventions. The WebSocket message mapper validation should be fixed before merging to prevent runtime errors from incomplete backend messages. All other issues are low-priority improvements that could be addressed in follow-up PRs.

Great work on the comprehensive test coverage and clean architecture!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @web-ui/__tests__/components/git/GitBranchIndicator.test.tsx:
- Around line 114-129: Update the GitBranchIndicator test to assert the branch
element is visible before checking styling: after rendering with the long-name
GitStatus and finding branchName via screen.getByText(/feature\/very-long/), add
an assertion that branchName is visible (e.g., expect(branchName).toBeVisible())
and then assert its closest('[data-testid="branch-indicator"]') has the
'truncate' class; this ensures the element is rendered and then verifies the
styling via the existing class check.

In @web-ui/src/components/git/CommitHistory.tsx:
- Around line 77-83: The <time> element in the CommitHistory component is using
an invalid ARIA role ("time"); remove the role attribute from the <time> tag
(the element rendering commit.timestamp in CommitHistory.tsx) so the markup
relies on the native <time> semantics (no change to formatRelativeTime is
required).

In @web-ui/src/components/git/GitSection.tsx:
- Around line 53-62: The SWR key used in useSWR inside GitSection.tsx omits the
maxCommits parameter causing stale cached results when maxCommits changes;
update the cache key string (the first arg to useSWR, currently
`git-commits-${projectId}`) to include maxCommits (e.g., append `-${maxCommits}`
or use a tuple) so the fetcher getCommits(projectId, { limit: maxCommits }) is
re-run whenever maxCommits changes and the cache is invalidated.
🧹 Nitpick comments (14)
web-ui/src/lib/websocketMessageMapper.ts (1)

250-270: Consider validating commit_hash for commit_created messages.

Unlike the agent_created handler (lines 92-97) which validates agent_id and returns null if missing, commit_created silently accepts an empty commit_hash. This could result in commits with empty hashes appearing in the UI, making them unidentifiable.

Suggested validation pattern
     case 'commit_created': {
       const msg = message as WebSocketMessage;
+      
+      // Warn if commit_hash is missing - commits without hashes may be unidentifiable
+      if (!msg.commit_hash) {
+        if (process.env.NODE_ENV === 'development') {
+          console.warn('[WebSocketMapper] commit_created message missing commit_hash');
+        }
+      }
+      
       // Create a GitCommit-compatible structure from the WebSocket message
       return {
         type: 'COMMIT_CREATED',
web-ui/src/types/git.ts (1)

121-133: Minor: Function comments say "type guard" but these are regular boolean helpers.

True TypeScript type guards use type predicates (branch is X). These functions are simple boolean helpers, which is perfectly fine for their use case. Consider updating the comments to avoid confusion.

 /**
- * Type guard to check if branch is active
+ * Check if branch is active
  */
 export function isBranchActive(branch: GitBranch): boolean {
   return branch.status === 'active';
 }

 /**
- * Type guard to check if branch is merged
+ * Check if branch is merged
  */
 export function isBranchMerged(branch: GitBranch): boolean {
   return branch.status === 'merged';
 }
web-ui/src/api/git.ts (1)

46-85: Consider adding client-side validation for the limit option.

The comment states limit should be 1-100, but there's no client-side validation. While the backend likely validates this, adding client-side bounds checking would provide better developer experience with immediate feedback.

♻️ Optional: Add limit validation
 export async function getCommits(
   projectId: number,
   options: GetCommitsOptions = {}
 ): Promise<GitCommit[]> {
   const params = new URLSearchParams();

   if (options.branch) {
     params.append('branch', options.branch);
   }

   if (options.limit !== undefined) {
+    if (options.limit < 1 || options.limit > 100) {
+      throw new Error('Limit must be between 1 and 100');
+    }
     params.append('limit', options.limit.toString());
   }
web-ui/src/components/Dashboard.tsx (1)

556-563: Consider using normalizePhase() for consistency with other phase checks.

The Git section uses projectData.phase directly, while other phase-dependent sections (lines 463, 471, 722, 738) use normalizePhase(projectData.phase). Since normalizePhase maps 'active' to 'development', the current condition should work, but using the normalized form would be more consistent and safer if phase naming conventions change.

♻️ Suggested refactor for consistency
             {/* Git Visualization Section (Ticket #272) */}
-            {(projectData.phase === 'active' ||
-              projectData.phase === 'review' ||
-              projectData.phase === 'complete') && (
+            {(['development', 'review', 'complete'].includes(
+              normalizePhase(projectData.phase)
+            )) && (
               <div className="mb-6" data-testid="git-section-container">
                 <GitSection projectId={projectId} maxCommits={5} />
               </div>
             )}
web-ui/__tests__/api/git.test.ts (1)

1-213: Good test coverage for core API functions.

The tests effectively cover:

  • Authentication header inclusion
  • Error handling (missing auth, API failures, 404)
  • URL construction with query parameters
  • Branch name encoding (feature/testfeature%2Ftest)

Consider adding tests for the convenience functions getCurrentBranch and getRecentCommits to ensure complete coverage of the public API surface.

web-ui/__tests__/components/git/CommitHistory.test.tsx (1)

9-9: Remove unused fireEvent import.

fireEvent is imported but not used in any test case.

🔧 Suggested fix
-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
web-ui/__tests__/components/git/GitSection.test.tsx (1)

90-153: Consider extracting the repeated SWR mock setup.

The same mockUseSWR.mockImplementation logic is duplicated in three test cases (lines 92-103, 112-123, 135-146). Extract it to a helper function to improve maintainability.

♻️ Suggested refactor
+function mockLoadedData() {
+  mockUseSWR.mockImplementation((key: string) => {
+    if (key.includes('status')) {
+      return { data: mockStatus, error: undefined, isLoading: false };
+    }
+    if (key.includes('commits')) {
+      return { data: mockCommits, error: undefined, isLoading: false };
+    }
+    if (key.includes('branches')) {
+      return { data: mockBranches, error: undefined, isLoading: false };
+    }
+    return { data: undefined, error: undefined, isLoading: false };
+  });
+}

 describe('data display', () => {
   it('should render all Git components when data is loaded', () => {
-    mockUseSWR.mockImplementation((key: string) => {
-      // ... repeated logic
-    });
+    mockLoadedData();
     render(<GitSection projectId={1} />);
     expect(screen.getByText(/code & git/i)).toBeInTheDocument();
   });
web-ui/src/components/git/index.ts (1)

8-17: Consider using named exports in the source components.

The barrel re-exports default exports as named exports, which works but adds indirection. Based on learnings, named exports are preferred. Consider changing the source components to use named exports directly.

Current pattern:

// Component file
export default GitBranchIndicator;
// Barrel
export { default as GitBranchIndicator } from './GitBranchIndicator';

Preferred pattern:

// Component file
export const GitBranchIndicator = ...;
// or: export function GitBranchIndicator() {...}
// Barrel
export { GitBranchIndicator } from './GitBranchIndicator';
web-ui/src/reducers/agentReducer.ts (1)

383-487: Consider extracting the repeated gitState initialization pattern.

The pattern state.gitState ?? { ...INITIAL_GIT_STATE } is repeated in all 7 Git action handlers. Consider extracting a helper function to reduce duplication.

♻️ Suggested helper function
+function getGitState(state: AgentState): GitState {
+  return state.gitState ?? { ...INITIAL_GIT_STATE };
+}

 case 'GIT_STATUS_LOADED': {
   const { status } = action.payload;
-  const currentGitState = state.gitState ?? { ...INITIAL_GIT_STATE };
+  const currentGitState = getGitState(state);
   // ...
 }
web-ui/src/components/git/CommitHistory.tsx (2)

32-45: Consider edge cases for invalid timestamps.

The formatRelativeTime function doesn't handle invalid date inputs. If timestamp is an invalid date string, new Date(timestamp) returns Invalid Date, and subsequent calculations will produce NaN, resulting in "NaNm ago" being rendered.

🛡️ Suggested defensive handling
 function formatRelativeTime(timestamp: string): string {
   const date = new Date(timestamp);
+  if (isNaN(date.getTime())) {
+    return 'unknown';
+  }
   const now = new Date();
   const diffMs = now.getTime() - date.getTime();

55-87: Improve accessibility for clickable commit items.

The CommitItem is rendered as a clickable <div> but lacks keyboard accessibility. Users navigating with a keyboard cannot focus or activate it. Consider using a <button> or adding tabIndex, role, and onKeyDown handlers.

♿ Suggested accessibility improvement
     <div
       data-testid="commit-item"
-      className="flex items-start gap-3 py-2 px-3 rounded-md hover:bg-muted/50 transition-colors cursor-pointer"
+      role="button"
+      tabIndex={onClick ? 0 : undefined}
+      className="flex items-start gap-3 py-2 px-3 rounded-md hover:bg-muted/50 transition-colors cursor-pointer focus:outline-none focus:ring-2 focus:ring-ring"
       onClick={onClick}
+      onKeyDown={(e) => {
+        if (onClick && (e.key === 'Enter' || e.key === ' ')) {
+          e.preventDefault();
+          onClick();
+        }
+      }}
     >
web-ui/src/components/git/BranchList.tsx (3)

32-41: Missing exhaustiveness check for getStatusStyles.

The switch statement covers all current BranchStatus values, but TypeScript won't error if a new status is added to the union. Adding a default case with exhaustiveness check ensures future-proofing.

🛡️ Suggested exhaustiveness check
 function getStatusStyles(status: BranchStatus): { bgClass: string; textClass: string } {
   switch (status) {
     case 'active':
       return { bgClass: 'bg-primary/10', textClass: 'text-primary' };
     case 'merged':
       return { bgClass: 'bg-secondary/10', textClass: 'text-secondary-foreground' };
     case 'abandoned':
       return { bgClass: 'bg-muted', textClass: 'text-muted-foreground' };
+    default: {
+      const _exhaustive: never = status;
+      return { bgClass: 'bg-muted', textClass: 'text-muted-foreground' };
+    }
   }
 }

51-80: Same accessibility concern as CommitHistory.

The BranchItem has the same keyboard accessibility issue as CommitItem. Consider adding role="button", tabIndex, and keyboard event handlers for clickable items.

♿ Suggested accessibility improvement
     <div
       data-testid="branch-item"
-      className="flex items-center justify-between py-2 px-3 rounded-md hover:bg-muted/50 transition-colors cursor-pointer"
+      role="button"
+      tabIndex={onClick ? 0 : undefined}
+      className="flex items-center justify-between py-2 px-3 rounded-md hover:bg-muted/50 transition-colors cursor-pointer focus:outline-none focus:ring-2 focus:ring-ring"
       onClick={onClick}
+      onKeyDown={(e) => {
+        if (onClick && (e.key === 'Enter' || e.key === ' ')) {
+          e.preventDefault();
+          onClick();
+        }
+      }}
     >

13-13: Use GitBranchIcon instead of GitCommitIcon for semantic clarity.

The component displays git branches at line 61, but uses GitCommitIcon which is semantically incorrect. Hugeicons provides GitBranchIcon which would better represent the branch concept. Update the import to use the appropriate icon.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1383cb8 and f3db9c7.

📒 Files selected for processing (20)
  • web-ui/__tests__/api/git.test.ts
  • web-ui/__tests__/components/git/BranchList.test.tsx
  • web-ui/__tests__/components/git/CommitHistory.test.tsx
  • web-ui/__tests__/components/git/GitBranchIndicator.test.tsx
  • web-ui/__tests__/components/git/GitSection.test.tsx
  • web-ui/__tests__/reducers/gitActions.test.ts
  • web-ui/__tests__/types/git.test.ts
  • web-ui/src/api/git.ts
  • web-ui/src/components/Dashboard.tsx
  • web-ui/src/components/git/BranchList.tsx
  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/git/GitBranchIndicator.tsx
  • web-ui/src/components/git/GitSection.tsx
  • web-ui/src/components/git/index.ts
  • web-ui/src/lib/websocketMessageMapper.ts
  • web-ui/src/reducers/agentReducer.ts
  • web-ui/src/types/agentState.ts
  • web-ui/src/types/git.ts
  • web-ui/src/types/index.ts
  • web-ui/test-utils/agentState.fixture.ts
🧰 Additional context used
📓 Path-based instructions (4)
web-ui/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web-ui/src/**/*.{ts,tsx}: Use TypeScript 5.3+ for frontend development
Use React 18 and Next.js 14 for frontend development
Use Tailwind CSS for styling with Nova design system template
Use shadcn/ui components from @/components/ui/ for UI elements
Use Hugeicons (@hugeicons/react) for all icons, never mix with lucide-react
Use Nova color palette variables (bg-card, text-foreground, etc.) instead of hardcoded color values
Use cn() utility for conditional CSS classes in React components
Use process.env.NEXT_PUBLIC_API_URL with fallback to http://localhost:8080 for API endpoint configuration
Include auth token as query parameter in WebSocket connections (?token=TOKEN)
Store auth tokens in localStorage with key 'auth_token' and include token in API requests via Authorization header

Files:

  • web-ui/src/components/git/GitSection.tsx
  • web-ui/src/components/git/BranchList.tsx
  • web-ui/src/types/agentState.ts
  • web-ui/src/components/git/index.ts
  • web-ui/src/types/index.ts
  • web-ui/src/api/git.ts
  • web-ui/src/lib/websocketMessageMapper.ts
  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/git/GitBranchIndicator.tsx
  • web-ui/src/types/git.ts
  • web-ui/src/components/Dashboard.tsx
  • web-ui/src/reducers/agentReducer.ts
web-ui/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web-ui/src/components/**/*.{ts,tsx}: Use React Context with useReducer for centralized state management in Dashboard
Wrap AgentStateProvider with ErrorBoundary component for graceful error handling
Use React.memo on Dashboard sub-components and useMemo for derived state to optimize performance

Files:

  • web-ui/src/components/git/GitSection.tsx
  • web-ui/src/components/git/BranchList.tsx
  • web-ui/src/components/git/index.ts
  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/git/GitBranchIndicator.tsx
  • web-ui/src/components/Dashboard.tsx
web-ui/src/lib/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Implement WebSocket automatic reconnection with exponential backoff (1s → 30s) and full state resync

Files:

  • web-ui/src/lib/websocketMessageMapper.ts
web-ui/src/reducers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use useReducer with 13+ action types for agent state management; implement timestamp conflict resolution with last-write-wins strategy

Files:

  • web-ui/src/reducers/agentReducer.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Use feature branches from main with Conventional Commits format (feat/fix/docs scope): description
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Applies to docs/web-ui/**/__tests__/**/*.test.{ts,tsx} : Create JavaScript test files colocated or in __tests__/ as *.test.ts

Applied to files:

  • web-ui/__tests__/components/git/CommitHistory.test.tsx
  • web-ui/__tests__/components/git/GitSection.test.tsx
  • web-ui/__tests__/components/git/BranchList.test.tsx
  • web-ui/__tests__/types/git.test.ts
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Applies to docs/web-ui/src/components/**/*.{ts,tsx} : Use functional React components with TypeScript interfaces

Applied to files:

  • web-ui/src/components/git/BranchList.tsx
  • web-ui/src/components/git/CommitHistory.tsx
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/**/*.{ts,tsx} : Use React 18 and Next.js 14 for frontend development

Applied to files:

  • web-ui/src/components/git/BranchList.tsx
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/reducers/**/*.{ts,tsx} : Use useReducer with 13+ action types for agent state management; implement timestamp conflict resolution with last-write-wins strategy

Applied to files:

  • web-ui/test-utils/agentState.fixture.ts
  • web-ui/__tests__/reducers/gitActions.test.ts
  • web-ui/src/types/agentState.ts
  • web-ui/src/types/index.ts
  • web-ui/src/lib/websocketMessageMapper.ts
  • web-ui/src/reducers/agentReducer.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/components/**/*.{ts,tsx} : Wrap AgentStateProvider with ErrorBoundary component for graceful error handling

Applied to files:

  • web-ui/src/types/agentState.ts
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Applies to docs/**/*.{ts,tsx,js,jsx} : Use named exports instead of default exports in TypeScript/JavaScript

Applied to files:

  • web-ui/src/components/git/index.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/**/*.{ts,tsx} : Use shadcn/ui components from @/components/ui/ for UI elements

Applied to files:

  • web-ui/src/components/git/index.ts
  • web-ui/src/components/git/CommitHistory.tsx
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/lib/**/*.{ts,tsx} : Implement WebSocket automatic reconnection with exponential backoff (1s → 30s) and full state resync

Applied to files:

  • web-ui/src/types/index.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/**/*.{ts,tsx} : Include auth token as query parameter in WebSocket connections (?token=TOKEN)

Applied to files:

  • web-ui/src/types/index.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/components/**/*.{ts,tsx} : Use React.memo on Dashboard sub-components and useMemo for derived state to optimize performance

Applied to files:

  • web-ui/src/components/git/CommitHistory.tsx
  • web-ui/src/components/Dashboard.tsx
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to web-ui/src/components/**/*.{ts,tsx} : Use React Context with useReducer for centralized state management in Dashboard

Applied to files:

  • web-ui/src/reducers/agentReducer.ts
📚 Learning: 2026-01-11T23:33:31.895Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-11T23:33:31.895Z
Learning: Applies to tests/**/*.{ts,tsx,test.ts} : Never use test.skip() inside test logic; skip at describe level or use separate test projects for different states

Applied to files:

  • web-ui/__tests__/types/git.test.ts
🧬 Code graph analysis (11)
web-ui/__tests__/components/git/CommitHistory.test.tsx (2)
web-ui/src/types/git.ts (1)
  • GitCommit (39-46)
web-ui/src/types/index.ts (1)
  • GitCommit (251-251)
web-ui/src/components/git/GitSection.tsx (3)
web-ui/src/components/git/index.ts (1)
  • GitSectionProps (17-17)
web-ui/src/types/git.ts (3)
  • GitStatus (55-61)
  • GitCommit (39-46)
  • GitBranch (22-30)
web-ui/src/api/git.ts (3)
  • getGitStatus (36-40)
  • getCommits (64-85)
  • getBranches (99-116)
web-ui/src/components/git/BranchList.tsx (3)
web-ui/src/components/git/index.ts (1)
  • BranchListProps (16-16)
web-ui/src/types/git.ts (2)
  • GitBranch (22-30)
  • BranchStatus (17-17)
web-ui/src/types/index.ts (2)
  • GitBranch (250-250)
  • BranchStatus (254-254)
web-ui/__tests__/components/git/GitBranchIndicator.test.tsx (2)
web-ui/src/types/git.ts (1)
  • GitStatus (55-61)
web-ui/src/types/index.ts (1)
  • GitStatus (252-252)
web-ui/__tests__/api/git.test.ts (2)
web-ui/src/types/git.ts (3)
  • GitStatus (55-61)
  • GitCommit (39-46)
  • GitBranch (22-30)
web-ui/src/api/git.ts (4)
  • getGitStatus (36-40)
  • getCommits (64-85)
  • getBranches (99-116)
  • getBranch (126-134)
web-ui/src/types/agentState.ts (2)
web-ui/src/types/git.ts (4)
  • GitState (71-86)
  • GitStatus (55-61)
  • GitCommit (39-46)
  • GitBranch (22-30)
specs/005-project-schema-refactoring/contracts/agent-state-api.ts (1)
  • FullResyncAction (262-270)
web-ui/src/lib/websocketMessageMapper.ts (2)
web-ui/src/types/index.ts (1)
  • WebSocketMessage (126-204)
specs/005-project-schema-refactoring/contracts/agent-state-api.ts (1)
  • WebSocketMessage (316-321)
web-ui/src/components/git/CommitHistory.tsx (2)
web-ui/src/components/git/index.ts (1)
  • CommitHistoryProps (15-15)
web-ui/src/types/git.ts (1)
  • GitCommit (39-46)
web-ui/src/components/git/GitBranchIndicator.tsx (1)
web-ui/src/types/git.ts (1)
  • GitStatus (55-61)
web-ui/src/reducers/agentReducer.ts (2)
web-ui/src/types/git.ts (1)
  • INITIAL_GIT_STATE (113-119)
web-ui/src/types/index.ts (1)
  • INITIAL_GIT_STATE (258-258)
web-ui/__tests__/types/git.test.ts (1)
web-ui/src/types/git.ts (5)
  • GitBranch (22-30)
  • GitCommit (39-46)
  • GitStatus (55-61)
  • GitState (71-86)
  • BranchStatus (17-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Backend Unit Tests
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: opencode-review
  • GitHub Check: E2E Smoke Tests (Chromium)
  • GitHub Check: Macroscope - Correctness Check
🔇 Additional comments (30)
web-ui/src/components/git/GitBranchIndicator.tsx (1)

1-104: LGTM! Well-structured component following best practices.

The component correctly uses:

  • memo() for performance optimization as per coding guidelines
  • Hugeicons (@hugeicons/react) for all icons
  • Nova color palette variables (bg-muted, text-foreground, bg-destructive/10, etc.)
  • Proper loading/error/null state handling with appropriate test IDs
  • Clear JSDoc documentation

The pluralization logic and tooltip implementation are correct.

web-ui/src/types/git.ts (1)

1-119: LGTM! Comprehensive and well-organized type definitions.

The types are:

  • Well-documented with JSDoc and section headers
  • Properly aligned with backend API response models (as referenced in comments)
  • Include sensible defaults in INITIAL_GIT_STATE
  • Use appropriate optional fields (merged_at, merge_commit, files_changed)
web-ui/src/api/git.ts (1)

1-45: LGTM! Clean API client implementation.

The API client correctly:

  • Uses process.env.NEXT_PUBLIC_API_URL with localhost fallback per coding guidelines
  • Leverages authFetch for authenticated requests
  • Properly encodes branch names in URL paths with encodeURIComponent
  • Uses URLSearchParams for query string construction
  • Provides convenient wrapper functions (getCurrentBranch, getRecentCommits)

Also applies to: 86-161

web-ui/src/components/Dashboard.tsx (2)

40-40: LGTM! Clean import via barrel file.

The import follows the established pattern of using barrel exports for component groups.


560-562: No action required. GitSection is already wrapped with React.memo (line 37 of GitSection.tsx), meeting the coding guideline requirement for Dashboard sub-components.

web-ui/test-utils/agentState.fixture.ts (1)

88-88: LGTM! Consider adding a helper for populated gitState in future.

The gitState: null additions are consistent across both factory functions. For more comprehensive test coverage of Git-related components, you might later want to add a createMockGitState() helper or a createPopulatedAgentStateWithGit() variant that returns a fully populated gitState.

Also applies to: 161-161

web-ui/__tests__/components/git/CommitHistory.test.tsx (1)

32-125: Well-structured test suite with comprehensive coverage.

The test organization with nested describe blocks for different aspects (rendering, loading, error, styling, limits, header) is clear and maintainable. Test coverage addresses the key functionality of the CommitHistory component.

web-ui/__tests__/types/git.test.ts (1)

16-179: Type validation tests provide good contract verification.

These tests serve as compile-time contracts ensuring the Git types remain stable. The coverage across all type variations (required fields, optional fields, nullable states) is thorough.

web-ui/src/components/git/GitSection.tsx (1)

37-151: Well-structured component with good SWR patterns.

The component correctly:

  • Uses memo for performance optimization per coding guidelines
  • Implements stale-while-revalidate pattern (shows cached data during refresh)
  • Uses Nova color palette variables (bg-card, border-border, text-foreground, etc.)
  • Uses Hugeicons as required by coding guidelines
  • Passes loading/error states to child components for granular feedback
web-ui/__tests__/components/git/BranchList.test.tsx (1)

39-134: Comprehensive test coverage for BranchList component.

The test suite thoroughly covers:

  • Empty and populated states
  • All three branch status types with styling verification
  • Loading and error states
  • Filtering functionality

Test organization with nested describe blocks is clean and maintainable.

web-ui/__tests__/components/git/GitSection.test.tsx (1)

1-60: Test setup and fixtures look good.

The test file is well-structured with clear mock fixtures that align with the GitStatus, GitCommit, and GitBranch types. The SWR and API mocking approach is correct.

web-ui/__tests__/components/git/GitBranchIndicator.test.tsx (1)

1-49: Well-structured tests for rendering and branch indicator.

The test coverage for null status handling, branch name display, and icon presence is thorough and aligns with the GitStatus type definition.

web-ui/__tests__/reducers/gitActions.test.ts (3)

1-69: Excellent test fixtures and organization.

The factory functions (createMockGitStatus, createMockGitCommit, createMockGitBranch, createStateWithGitState) follow DRY principles and make the tests maintainable. Good use of partial overrides pattern.


199-256: Good FIFO limit test coverage for COMMIT_CREATED.

The test at lines 219-239 correctly validates the 10-item limit with FIFO behavior, ensuring the oldest commit is dropped when a new one is added to a full list. This aligns with the reducer implementation.


348-374: GIT_ERROR tests are thorough.

Tests cover both setting and clearing error state, and verify that isLoading is set to false when an error occurs. This matches the expected reducer behavior.

web-ui/src/reducers/agentReducer.ts (3)

379-397: Git action handlers are well-implemented.

The immutability pattern is correct, and the null coalescing with INITIAL_GIT_STATE ensures safe initialization. The handler correctly clears loading and error state when status is loaded.


383-397: Timestamp in payload is unused for conflict resolution.

Per coding guidelines, the reducer should implement timestamp conflict resolution with last-write-wins strategy. The GIT_STATUS_LOADED payload includes a timestamp but it's not used for LWW. If stale Git status updates can arrive out of order (e.g., from WebSocket events), consider adding conflict resolution similar to AGENT_UPDATED.

If Git updates always come from SWR polling and are sequential, this may be acceptable. Otherwise, consider:

case 'GIT_STATUS_LOADED': {
  const { status, timestamp } = action.payload;
  const currentGitState = state.gitState ?? { ...INITIAL_GIT_STATE };
  
  // Reject stale updates
  if (currentGitState.lastUpdated && currentGitState.lastUpdated > timestamp) {
    newState = state;
    break;
  }
  // ... rest of handler
}

431-446: COMMIT_CREATED FIFO logic is correct.

The slice(0, 9) correctly keeps the 9 most recent existing commits, and prepending the new commit results in a maximum of 10 items. This matches the test expectations.

web-ui/src/components/git/CommitHistory.tsx (2)

94-105: Good use of memoization for performance.

The component correctly uses React.memo and useMemo for the displayedCommits derivation, aligning with the coding guidelines for Dashboard sub-components. The slicing logic is clean and efficient.


161-185: Well-structured component with proper state handling.

The component handles loading, error, and empty states appropriately with distinct visuals and test IDs. The use of Nova design system classes (bg-card, text-foreground, text-muted-foreground, etc.) follows coding guidelines.

web-ui/src/components/git/BranchList.tsx (2)

87-98: Good use of memoization and filtering logic.

The component correctly applies React.memo and uses useMemo for filtered branches. The filtering logic is clean and handles the optional filterStatus prop appropriately.


154-173: Consistent structure with CommitHistory.

The component follows the same pattern as CommitHistory with proper state handling, Nova design system classes, and test IDs. Good consistency across Git visualization components.

web-ui/src/types/index.ts (2)

123-124: WebSocket message type addition looks correct.

The branch_created message type is properly added to the union, aligning with the WebSocket handlers mentioned in the PR summary for real-time Git events.


248-258: Clean type re-exports for Git visualization.

The Git types and utilities are properly re-exported from ./git, maintaining a clean public API surface. All exports (GitBranch, GitCommit, GitStatus, GitState, BranchStatus, BranchListResponse, CommitListResponse, INITIAL_GIT_STATE, isBranchActive, isBranchMerged) exist in the source module and are correctly re-exported. This follows good modular organization practices and enables consumers to import from the central types index.

web-ui/src/types/agentState.ts (6)

12-12: Proper type imports for Git integration.

The imports correctly pull in the Git types needed for the state and action definitions.


252-252: Good nullable typing for initial state.

Using GitState | null allows the state to represent "not yet loaded" vs "loaded but empty", which is appropriate for the initialization flow before the first API fetch.


401-429: Git load actions follow established patterns.

The GitStatusLoadedAction, GitCommitsLoadedAction, and GitBranchesLoadedAction interfaces correctly include timestamps for conflict resolution, consistent with the existing action patterns (e.g., TaskStatusChangedAction, AgentUpdatedAction). Based on learnings, this aligns with the "last-write-wins" timestamp conflict resolution strategy.


431-452: WebSocket event actions are well-structured.

CommitCreatedAction and BranchCreatedAction properly model real-time events with timestamps. The optional taskId in CommitCreatedAction is a good design choice for linking commits to tasks when applicable.


454-468: Simple loading/error actions are appropriate.

GitLoadingAction and GitErrorAction are straightforward boolean/string payloads without timestamps, which is correct since these represent transient UI states rather than domain events that need conflict resolution.


486-494: AgentAction union properly extended.

All seven Git actions are correctly added to the discriminated union. The comment annotation helps future maintainers understand the grouping.

- Add null checks in WebSocket commit_created/branch_created handlers
- Add defensive checks in reducer for gitState arrays
- Remove invalid ARIA role from CommitHistory time element
- Fix SWR cache key to include maxCommits for proper invalidation
- Update JSDoc for getBranch to clarify encoding behavior
- Add visibility assertion in GitBranchIndicator test
- Add 10 new tests for message validation
);
}

// Error state
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommitHistory.tsx renders CommitItem, but it’s not imported or defined. Consider importing CommitItem (or defining it here) to avoid a runtime ReferenceError.

🚀 Want me to fix this? Reply ex: "fix it for me".

@github-actions
Copy link
Contributor

Review Summary

This is a well-structured implementation with comprehensive test coverage (74 new tests) and clean architecture. The code follows project conventions for React.memo, SWR, and the Nova design system.

Critical Issue: WebSocket Handler

Location:

The handler reads fields from the wrong location in the WebSocket message. Backend sends with top-level fields, but frontend tries to access :

Backend format (from ):

Frontend handler (line 284-297):

Impact: All messages are skipped (validation fails), so branch tracking doesn't work via WebSocket.

Fix:

TypeScript Type Definition Missing

Location:

The interface is missing fields for messages. Add:

✅ Strengths

  1. Clean architecture: Proper separation between API, types, components, and reducers
  2. Good performance: SWR caching with 30s interval, React.memo, useMemo
  3. Comprehensive testing: 74 tests covering types, API, reducer, and components
  4. Follows conventions: Uses Hugeicons, Nova design system, cn() utility
  5. Fixed issues from reviews: SWR cache key now includes maxCommits, invalid ARIA role removed

Minor Suggestions

  1. Phase consistency: Dashboard line 557 uses while other sections use . Consider using for consistency.

  2. Accessibility: CommitItem and BranchItem are clickable divs without keyboard support. Consider adding , , and keyboard event handlers for full a11y.

  3. Type guard comments: Functions / in are labeled as type guards but don't use type predicates. Minor documentation fix.

Recommendation

Request changes required for the critical handler bug. This will completely break branch WebSocket updates.

Overall: Excellent implementation with a critical bug in WebSocket message parsing. Fix the field access and type definition, then approve.

@github-actions
Copy link
Contributor

Review Complete

I've reviewed PR #283 and posted a comprehensive comment at: #283 (comment)

Key Findings

Critical Bug: The branch_created WebSocket handler (web-ui/src/lib/websocketMessageMapper.ts:280) reads fields from msg.data.* but the backend sends them at the top level (msg.branch_name, msg.issue_id). This causes all branch WebSocket updates to fail silently.

Missing Type Definition: WebSocketMessage interface in web-ui/src/types/index.ts is missing branch_name and issue_number fields needed for branch_created messages.

Strengths:

  • Clean architecture with proper separation of concerns
  • Comprehensive test coverage (74 tests)
  • Good performance optimizations (SWR, React.memo, useMemo)
  • Follows project conventions (Hugeicons, Nova design system)
  • Issues from previous reviews were already addressed (SWR cache key, invalid ARIA role)

Minor Suggestions:

  • Use normalizePhase() for consistency in Dashboard phase check
  • Add keyboard accessibility to clickable divs
  • Clarify type guard comments

The implementation is excellent overall but requires the critical WebSocket handler fix to merge.

New%20session%20-%202026-01-14T00%3A42%3A31.072Z
opencode session  |  github run

@frankbria frankbria merged commit bc32826 into main Jan 14, 2026
16 checks passed
@frankbria frankbria deleted the feature/272-git-visualization branch January 14, 2026 01:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P0] Frontend: Git commit and branch visualization UI

2 participants