Skip to content

feat: improved git graph data loading#110

Open
ruru-m07 wants to merge 10 commits into
devfrom
ruru-git-graph-brush
Open

feat: improved git graph data loading#110
ruru-m07 wants to merge 10 commits into
devfrom
ruru-git-graph-brush

Conversation

@ruru-m07

@ruru-m07 ruru-m07 commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added interactive commit history visualization with zoom and pan controls
    • Introduced activity chart showing commit insertions and deletions across time
    • Enhanced commit metadata display including statistics, timestamps, authors, and branch information
    • Added detailed diff statistics visualization in history view
  • Bug Fixes

    • Re-enabled text selection throughout the application
  • Chores

    • Updated chart rendering library for improved performance

ruru-m07 added 7 commits May 31, 2026 12:34
- Added Recharts as a dependency in package.json and ui/package.json.
- Introduced new CSS variables for chart colors in base.css.
- Created a ChartContainer component to encapsulate chart logic and styling.
- Implemented ChartTooltip and ChartLegend components for enhanced data representation.
- Added utility functions to extract configuration from chart payloads.
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds end-to-end commit activity tracking: Rust git log parsing gains shortstat support and display_name normalization for refs; branch-ref metadata is propagated through the graph swim-lane state machine; a new commit_activity Tauri command, TS/Zod bindings, and useCommitActivity React Query hook are wired up. The history graph UI is rebuilt into discrete column components (Branch, Lanes, Commiters, Summary, Timestamp, Stats, CommitHash), a virtual-scroll GraphBody, and an interactive PanoramaChart minimap using recharts.

Changes

Commit Activity + History Graph UI

Layer / File(s) Summary
Rust data model contracts
crates/git/models/commit.rs, crates/git/models/graph.rs, crates/git/models/history.rs
CommitStats gains Copy+Default. GraphRef gains display_name and serde derives. GraphLogEntry gains stats. CommitActivityQuery, CommitActivityItem, and CommitActivityResponse introduced. GraphRow gains branch_refs.
Git log parser: shortstat and display_name
crates/git/parsers/graph.rs
LOG_FORMAT record separator moved to entry start. parse_log_entries uses splitn(11). split_body_and_stats extracts shortstat into CommitStats. parse_stat_line made public. parse_ref sets display_name via display_name_for_ref and classifies GraphRefKind by ref-path prefix. Unit tests added.
Graph service: branch_refs propagation + commit_activity
crates/git/service/graph.rs, crates/git/service/history.rs, crates/git/tests/history_service.rs, apps/desktop/src-tauri/src/commands/history.rs, apps/desktop/src-tauri/src/lib.rs
Swimlane/ProcessResult gain branch_refs. merge_branch_refs deduplicates refs per row. process_commit propagates refs across lanes. history_graph fetches with include_stats=true. New commit_activity function performs a two-pass git log (hash/timestamp + shortstat). HistoryService.commit_activity method and Tauri command handler added and registered. Integration tests added.
TS/Zod bindings and React Query wiring
packages/commands/src/types.ts, packages/commands/src/commands.ts, packages/commands/src/index.ts, packages/commands/src/.typecache, apps/desktop/src/state/domains/RepositoryState.ts, apps/desktop/src/hooks/useRepository.ts, package.json, apps/desktop/package.json, packages/ui/package.json
GraphRefSchema gains display_name, GraphRowSchema gains branch_refs. Commit activity Zod schemas and inferred types added. commitActivity command wrapper generated. RepositoryState.Commit.commitActivity method and query key added. useCommitActivity hook introduced. recharts added to workspace catalog.
Shared UI primitives
packages/ui/src/components/chart.tsx, packages/ui/src/styles/base.css, packages/ui/src/components/avatar.tsx, packages/ui/src/components/button.tsx, apps/desktop/src/types/app.ts, apps/desktop/src/components/diffBoxes.tsx, apps/desktop/src/components/historyGraph/helper.tsx, apps/desktop/src/components/historyGraph/virtualizer.ts, apps/desktop/src/app.css
chart.tsx adds ChartContainer, ChartStyle, tooltip/legend primitives. Static chart color tokens removed from CSS. CSSVars type added. DiffStat box-ratio component added. helper.tsx exports GraphLayout/ProcessedRow/GraphColumnsType types and ref-filter/color helpers. virtualizer.ts adds ROW_H, computeRange. user-select CSS rules commented out.
Lane graph rendering
apps/desktop/src/components/historyGraph/columns/lanes/*
PALETTE color array added. LaneGraph (renamed from GraphLane) rewrites multi-lane bezier curve and secondary-parent connector paths, exports geometry constants, uses getRowColor for dot color. LaneBackground renders positioned background. LanesCell composes them. Lanes column adds WKWebView vertical-scroll workaround via onWheel.
History graph column components
apps/desktop/src/components/historyGraph/columns/branch/*, apps/desktop/src/components/historyGraph/columns/commiters/*, apps/desktop/src/components/historyGraph/columns/commit-hash/*, apps/desktop/src/components/historyGraph/columns/summary/*, apps/desktop/src/components/historyGraph/columns/timestamp/*, apps/desktop/src/components/historyGraph/columns/stats/*
BranchBadge/BranchCell render ref badges with gradient overlays and push button. CommitersCell shows author/co-author avatars with tooltips. CommitHashCell truncates OID. SummaryCell shows commit message with color gradient bar. TimestampCell formats time-ago. StatsCell shows file/insertion/deletion counts with DiffStat. All wrapped in memoized column index components.
GraphBody, PanoramaChart, and HistoryGraph root
apps/desktop/src/components/historyGraph/body.tsx, apps/desktop/src/components/historyGraph/PanoramaChart.tsx, apps/desktop/src/components/historyGraph/index.tsx, apps/desktop/src/routes/app/inbox/index.tsx, apps/desktop/src/routes/app/issues/index.tsx
GraphBody lays out columns with an infinite-scroll sentinel and pointer-based row hover. PanoramaChart renders a recharts stacked-bar/area chart with a DOM-mutated viewport overlay supporting wheel zoom, drag pan, scroll sync, and click-to-center. HistoryGraph root refactored to use GraphBody+PanoramaChart with useCommitActivity. Inbox route replaced with timing-instrumentation demo. Issues route replaced with multi-column grid scroll prototype.

Sequence Diagram

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    Note over HistoryGraph,GraphService: Commit Activity Flow
  end
  participant HistoryGraph
  participant useCommitActivity
  participant RepositoryState
  participant TauriIPC as Tauri IPC
  participant HistoryService
  participant GraphService as graph_service

  HistoryGraph->>useCommitActivity: mount with activityQuery
  useCommitActivity->>RepositoryState: commit.commitActivity(query)
  RepositoryState->>TauriIPC: invoke("commit_activity", {contextId, query})
  TauriIPC->>HistoryService: commit_activity(query)
  HistoryService->>GraphService: commit_activity(repo_path, query)
  GraphService->>GraphService: pass1 — git log (hash+timestamp)
  GraphService->>GraphService: pass2 — git log --shortstat
  GraphService-->>HistoryService: CommitActivityResponse
  HistoryService-->>TauriIPC: Ok(CommitActivityResponse)
  TauriIPC-->>RepositoryState: CommitActivityResponse
  RepositoryState-->>useCommitActivity: {items, headIndex, total}
  useCommitActivity-->>HistoryGraph: data
  HistoryGraph->>PanoramaChart: items, headIndex, totalCommits, scrollRef
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 Hop along the commit trail so bright,
Stacked bars and bezier curves in flight!
Branch refs now carry their name with pride,
The minimap shows where the commits reside.
With shortstat parsed at every turn,
This bunny's history graph is ready to churn! 📊

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author. The template requires sections for 'What?', 'Why?', and 'How?' to explain the change, but none were filled out. Add a description explaining what was optimized (What), the motivation for the optimization (Why), and the implementation approach (How). Link any related issues using 'Fixes #'.
Docstring Coverage ⚠️ Warning Docstring coverage is 29.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title 'feat: improved git graph data loading' directly reflects the main objective of the changeset, which encompasses adding a new commit activity query/response system, creating PanoramaChart and GraphBody components for enhanced graph visualization, and refactoring the history graph rendering pipeline.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 19, 2026

Copy link
Copy Markdown

Deploying gitru with  Cloudflare Pages  Cloudflare Pages

Latest commit: adc89c7
Status: ✅  Deploy successful!
Preview URL: https://dfa42349.gitru.pages.dev
Branch Preview URL: https://ruru-git-graph-brush.gitru.pages.dev

View logs

@ruru-m07 ruru-m07 changed the base branch from ruru/git/git-graph/core to dev June 22, 2026 06:34
@ruru-m07 ruru-m07 marked this pull request as ready for review June 22, 2026 06:34
@ruru-m07

Copy link
Copy Markdown
Owner Author

@coderabbitai help

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai rate limit to show your current review rate limit status.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
  • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
  • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai emit path instructions to emit generated path-specific review instructions for this repository.
  • @coderabbitai resolve merge conflict to automatically resolve merge conflicts.
  • @coderabbitai autofix to automatically fix issues identified in unresolved review comments.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@ruru-m07

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🧹 Nitpick comments (15)
crates/git/models/graph.rs (1)

79-91: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding Clone derive for convenience.

The CommitActivityQuery struct could benefit from deriving Clone to allow easy reuse of query objects without requiring ownership transfer.

♻️ Optional enhancement
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct CommitActivityQuery {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/git/models/graph.rs` around lines 79 - 91, Add the Clone trait to the
derive macro for the CommitActivityQuery struct. Include Clone in the derive
attribute alongside Debug, Serialize, and Deserialize to allow for easy cloning
of query objects without requiring ownership transfer. Since the struct contains
only primitive types (usize and bool), Clone can be derived automatically.
crates/git/service/graph.rs (1)

801-820: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Mutation inside .map() closure is unconventional but functional.

The head_index assignment inside the .map() closure works correctly but could be clearer. Consider using .enumerate().fold() or a separate loop for the HEAD matching to avoid side effects in a mapping operation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/git/service/graph.rs` around lines 801 - 820, The head_index
assignment inside the .map() closure is a side effect that should be avoided in
a mapping operation. Refactor this code to separate the concerns: either use
.enumerate().fold() to accumulate both the CommitActivityItem collection and the
head_index value in a single pass, or extract the HEAD matching logic into a
separate operation outside the map operation (such as finding the index where
the oid matches head_oid.as_deref() after collecting the items). This will make
the intent clearer and avoid mutating external state within the transformation
closure.
crates/git/service/history.rs (1)

57-63: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Blocking call in async context.

graph_service::commit_activity is a synchronous function that performs multiple blocking git subprocess calls. Calling it directly from an async method could block the Tokio runtime thread. However, since history_graph uses the same pattern and appears to work in production, this is likely acceptable for the current use case. Consider using tokio::task::spawn_blocking if you observe async task starvation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/git/service/history.rs` around lines 57 - 63, The synchronous
`graph_service::commit_activity` function performs blocking git subprocess calls
within the async `commit_activity` method, which could block the Tokio runtime
thread. If async task starvation becomes an issue in production, wrap the
`graph_service::commit_activity` call using `tokio::task::spawn_blocking` to
offload the blocking work to a separate thread pool and prevent runtime
starvation. This ensures the async context is not blocked by long-running
synchronous operations.
apps/desktop/src/components/historyGraph/columns/stats/stats-cell.tsx (1)

10-38: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add display name to memoized component.

React DevTools will show Memo(Component) instead of StatsCell, making debugging harder.

♻️ Proposed fix
 const StatsCell = React.memo(({ row }: StatsCellProps) => {
   // ... component body
 });
+StatsCell.displayName = "StatsCell";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/stats/stats-cell.tsx` around
lines 10 - 38, The StatsCell component is wrapped with React.memo() but does not
have a display name assigned, which causes React DevTools to show it as
Memo(Component) instead of StatsCell. Add a displayName property to the
StatsCell constant after the React.memo() declaration by assigning
StatsCell.displayName equal to the string 'StatsCell' to improve debugging
visibility in React DevTools.
apps/desktop/src/components/historyGraph/columns/branch/index.tsx (1)

6-27: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add display name to memoized component.

React DevTools will show Memo(Component) instead of Branch, making debugging harder.

♻️ Proposed fix
 const Branch = React.memo(({ rows, ...restProps }: GraphColumnsType) => {
   // ... component body
 });
+Branch.displayName = "Branch";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/branch/index.tsx` around
lines 6 - 27, The Branch component created with React.memo lacks a displayName
property, causing React DevTools to display it as "Memo(Component)" instead of
the actual component name. After the React.memo assignment for the Branch
component, add a displayName property assignment to the Branch constant to
explicitly set its display name to 'Branch'.
apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx (6)

17-65: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add display name to memoized component.

React DevTools will show Memo(Component) instead of BranchCell, making debugging harder.

♻️ Proposed fix
 const BranchCell = React.memo(
   ({ row, currentBranch, enablePushButton }: BranchCellProps) => {
     // ... component body
   },
 );
+BranchCell.displayName = "BranchCell";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx`
around lines 17 - 65, The BranchCell component wrapped with React.memo does not
have a display name assigned, causing React DevTools to display it as
Memo(Component) instead of the actual component name. After the BranchCell
assignment is complete with React.memo, add a displayName property assignment to
the BranchCell variable with the value "BranchCell" to make the component
identifiable in React DevTools for easier debugging.

114-126: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unnecessary fragment wrapper.

The fragment around the local branch refs map serves no purpose.

♻️ Proposed fix
       {row.localBranchRefs.length > 0 && (
-        <>
-          {row.localBranchRefs.map((ref) => (
-            <BranchBadge
-              key={ref.name}
-              ref={ref}
-              row={row}
-              currentBranch={currentBranch}
-              type="local"
-            />
-          ))}
-        </>
+        row.localBranchRefs.map((ref) => (
+          <BranchBadge
+            key={ref.name}
+            ref={ref}
+            row={row}
+            currentBranch={currentBranch}
+            type="local"
+          />
+        ))
       )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx`
around lines 114 - 126, The fragment wrapper around the map function in the
local branch refs rendering is unnecessary. Remove the empty fragment tags (the
<> </>) that wrap the map call, and move the map function directly into the
conditional expression where it renders the BranchBadge components for each ref
in row.localBranchRefs.

158-159: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove commented-out code.

Commented backgroundColor properties should be removed if not needed.

♻️ Proposed fix
           <div
             className="h-full w-0.5"
             style={{
-              // backgroundColor: `color-mix(in oklab, ${row.color} 70%, var(--color-background))`,
               background: isCurrentBranch
                 ? `repeating-linear-gradient(

And:

             style={{
               height: 2,
               top: ROW_H / 2 - 2,
               width: "100%",
-              // backgroundColor: `color-mix(in oklab, ${row.color} 30%, var(--color-background))`,
               background: `repeating-linear-gradient(

Also applies to: 179-179

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx`
around lines 158 - 159, Remove the commented-out backgroundColor property that
uses color-mix in the branch-cell styling section. The line containing the
commented code with backgroundColor and color-mix should be deleted entirely.
Additionally, remove any other commented-out code at the other location
mentioned in the review (line 179) that has similar styling comments. Clean up
all unused commented code to keep the codebase maintainable.

127-139: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unnecessary fragment wrapper.

The fragment around the remote refs map serves no purpose.

♻️ Proposed fix
       {row.remoteRefs.length > 0 && (
-        <>
-          {row.remoteRefs.map((ref) => (
-            <BranchBadge
-              key={ref.name}
-              ref={ref}
-              row={row}
-              currentBranch={currentBranch}
-              type="remote"
-            />
-          ))}
-        </>
+        row.remoteRefs.map((ref) => (
+          <BranchBadge
+            key={ref.name}
+            ref={ref}
+            row={row}
+            currentBranch={currentBranch}
+            type="remote"
+          />
+        ))
       )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx`
around lines 127 - 139, The fragment wrapper around the row.remoteRefs.map call
is unnecessary since it only contains a single mapped element producing
BranchBadge components. Remove the outer fragment delimiters (the empty angle
brackets) while keeping the conditional check and the map function intact. This
simplifies the JSX structure without changing the functionality or how the
BranchBadge components are rendered with their key, ref, row, currentBranch, and
type props.

43-59: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unnecessary fragment wrapper.

The outer fragment <>...</> serves no purpose here.

♻️ Proposed fix
           <div
             data-hidden-branch-refs
             className="hidden min-w-0 gap-1 items-center opacity-70"
           >
             {row.branchRefs.map((ref) => (
-              <>
-                <BranchBadge
-                  key={ref.name}
-                  ref={ref}
-                  row={row}
-                  currentBranch={currentBranch}
-                  type={
-                    ref.kind === "Local"
-                      ? "local"
-                      : ref.kind === "Remote"
-                        ? "remote"
-                        : "tag"
-                  }
-                />
-              </>
+              <BranchBadge
+                key={ref.name}
+                ref={ref}
+                row={row}
+                currentBranch={currentBranch}
+                type={
+                  ref.kind === "Local"
+                    ? "local"
+                    : ref.kind === "Remote"
+                      ? "remote"
+                      : "tag"
+                }
+              />
             ))}
           </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx`
around lines 43 - 59, The fragment wrapper around the BranchBadge component in
the branchRefs.map callback is unnecessary since there is only a single child
element being rendered. Remove the outer empty fragment operators `<>` and `</>`
that wrap the BranchBadge component, returning the BranchBadge component
directly from the map function instead.

140-152: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unnecessary fragment wrapper.

The fragment around the tags map serves no purpose.

♻️ Proposed fix
       {row.tags.length > 0 && (
-        <>
-          {row.tags.map((tag) => (
-            <BranchBadge
-              key={tag.name}
-              ref={tag}
-              row={row}
-              currentBranch={currentBranch}
-              type="tag"
-            />
-          ))}
-        </>
+        row.tags.map((tag) => (
+          <BranchBadge
+            key={tag.name}
+            ref={tag}
+            row={row}
+            currentBranch={currentBranch}
+            type="tag"
+          />
+        ))
       )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx`
around lines 140 - 152, The unnecessary fragment wrapper around the
row.tags.map() call should be removed. Since the conditional check
{row.tags.length > 0 && ...} already ensures the block only renders when tags
exist, and the fragment is only wrapping a single map operation that generates
BranchBadge components, you can safely remove the <> and </> fragment delimiters
and render the map call directly within the conditional expression.
apps/desktop/src/components/historyGraph/columns/commiters/index.tsx (1)

5-13: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add display name to memoized component.

React DevTools will show Memo(Component) instead of Commiters, making debugging harder.

♻️ Proposed fix
 const Commiters = React.memo(({ rows, ...restProps }: GraphColumnsType) => {
   // ... component body
 });
+Commiters.displayName = "Commiters";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/commiters/index.tsx` around
lines 5 - 13, The Commiters component is wrapped with React.memo but lacks a
displayName property, causing React DevTools to display it as "Memo(Commiters)"
instead of just "Commiters", which hampers debugging. Add a displayName property
to the Commiters component after it is defined by assigning
Commiters.displayName equal to the string "Commiters" to explicitly set how it
appears in React DevTools.
apps/desktop/src/components/historyGraph/columns/commiters/commiters-cell.tsx (2)

18-117: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add display name to memoized component.

React DevTools will show Memo(Component) instead of CommitersCell, making debugging harder.

♻️ Proposed fix
 const CommitersCell = React.memo(({ row }: CommitersCellProps) => {
   // ... component body
 });
+CommitersCell.displayName = "CommitersCell";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/components/historyGraph/columns/commiters/commiters-cell.tsx`
around lines 18 - 117, The CommitersCell component is wrapped in React.memo but
lacks a displayName property, causing React DevTools to display it as
Memo(Component) instead of the actual component name. Add a displayName property
assignment after the component export to set it to "CommitersCell", which will
help with debugging and make the component easier to identify in React DevTools.

40-44: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Extract duplicate initials logic into helper function.

The initials fallback logic is duplicated three times. Extract it to reduce repetition.

♻️ Proposed refactor

At the top of the file or in a helper module:

const getInitials = (name: string) =>
  name
    .split(" ")
    .map((n) => n[0])
    .join("")
    .toUpperCase();

Then replace all three occurrences:

               <AvatarFallback>
-                {commitRow.commit?.authors.author.name
-                  .split(" ")
-                  .map((n) => n[0])
-                  .join("")
-                  .toUpperCase()}
+                {getInitials(commitRow.commit?.authors.author.name)}
               </AvatarFallback>

Also applies to: 68-72, 100-104

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/components/historyGraph/columns/commiters/commiters-cell.tsx`
around lines 40 - 44, The initials extraction logic (splitting a name by spaces,
taking the first character of each word, joining them, and converting to
uppercase) is duplicated three times in the commiters-cell.tsx file at lines
40-44, 68-72, and 100-104. Create a reusable helper function called getInitials
that takes a name string parameter and returns the formatted initials, then
replace all three duplicate code blocks with calls to this new helper function
to eliminate the repetition.
apps/desktop/src/components/historyGraph/columns/stats/index.tsx (1)

5-13: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add display name to memoized component.

React DevTools will show Memo(Component) instead of Stats, making debugging harder.

♻️ Proposed fix
 const Stats = React.memo(({ rows, ...restProps }: GraphColumnsType) => {
   // ... component body
 });
+Stats.displayName = "Stats";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/columns/stats/index.tsx` around
lines 5 - 13, The Stats component is wrapped in React.memo but lacks a
displayName property, which causes React DevTools to display it as
Memo(Component) instead of Stats, making debugging difficult. After the Stats
component definition (following the closing `});`), add a displayName property
to the Stats component by assigning it the string value "Stats" to ensure proper
identification in React DevTools.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/components/historyGraph/body.tsx`:
- Around line 142-150: The columns.map() function is using the array index idx
as the React key, which can cause component reuse issues if the columns array
order changes. Replace the key={idx} with a stable identifier from the column
object itself, such as a unique id property or column name that remains constant
regardless of array position. If the column objects don't have a unique
identifier, consider adding one or using a property that uniquely identifies
each column component.
- Around line 65-120: The useEffect hook is missing hoverClasses from its
dependency array, causing the event handlers in onMove to use stale references
if hoverClasses changes. Since hoverClasses is redeclared on every render (lines
43-46), the best fix is to move the hoverClasses constant outside the component
body so it's not recreated on each render, then reference it in the apply
function within the useEffect. Alternatively, if hoverClasses must remain in the
component, add it to the useEffect dependency array at the end, but this will
cause the effect to recreate on every render due to the constant being
redeclared.
- Around line 71-82: The `map` variable caches DOM query results indefinitely
without clearing stale entries when `rows` change, causing unbounded memory
growth. Add a useEffect hook that depends on `rows` and clears the `map` cache
by calling `map.clear()` whenever `rows` changes, ensuring the cache doesn't
accumulate stale row IDs and old DOM references.

In `@apps/desktop/src/components/historyGraph/columns/branch/branch-badge.tsx`:
- Around line 28-29: The conditional check at the ternary operator in
branch-badge.tsx is comparing currentBranch?.name with the result of finding a
Local ref's display_name. When both currentBranch is null and no Local ref
exists, both sides evaluate to undefined, causing undefined === undefined to be
true and incorrectly rendering the CircleDotDashed icon. Fix this by explicitly
ensuring that currentBranch is not null and a Local ref is found before
considering the branch as active, rather than relying on the loose comparison
that treats undefined === undefined as true. You should guard both conditions:
check that currentBranch is defined and check that the find() actually returns a
valid Local ref before doing the display_name comparison.

In
`@apps/desktop/src/components/historyGraph/columns/commiters/commiters-cell.tsx`:
- Line 37: The avatar URL in the commiters-cell component is exposing email
addresses as query parameters, which creates a PII leak risk that can be logged
by proxies, CDNs, or browser history. Replace the email query parameter in the
src attribute with either a hashed version of the email or a GitHub user ID if
available from the commitRow data. Apply this same change to all occurrences
mentioned (lines 37, 65, and 97) where the avatar URL is constructed with the
email parameter in the query string.
- Around line 93-108: The mapped `<div>` elements rendering coAuthor information
are missing required key props, which will cause React rendering warnings. Add a
unique key prop to the outer `<div>` element that wraps the Avatar and name
span. Use a unique identifier from the coAuthor object (such as coAuthor.email)
as the key value to ensure React can properly track and reconcile list items
during re-renders.

In `@apps/desktop/src/components/historyGraph/columns/lanes/lane-graph.tsx`:
- Around line 430-441: The AvatarImage component in the lane-graph.tsx file is
directly transmitting the commit author's email address as a query parameter to
the GitHub avatars endpoint, which exposes PII. Instead of sending the raw email
in the src URL of AvatarImage, hash the email address using MD5 (which is the
expected format for GitHub's avatar API) before passing it to the endpoint. This
way the email remains private while still allowing the avatar endpoint to
generate the appropriate avatar image.

In `@apps/desktop/src/components/historyGraph/columns/stats/stats-cell.tsx`:
- Around line 22-34: The code in the stats-cell component assumes
innerRow.commit.stats always exists and will crash if stats is null or undefined
when accessing properties like files_changed, insertions, and deletions. Add
null guard checks before rendering the statistics section and the DiffStat
component to safely handle cases where commit or stats are missing. Return early
or render a fallback UI (such as a placeholder or empty state) when
innerRow.commit.stats is not available.

In `@apps/desktop/src/components/historyGraph/columns/summary/index.tsx`:
- Around line 8-10: The `rows.map()` function rendering `SummaryCell` is missing
a `key` prop, which can cause incorrect reconciliation when the row set changes.
Add `key={row.row.oid}` to the `SummaryCell` component to provide a stable
unique identifier, matching the consistent pattern used in other column
components like Branch, CommitHash, and Timestamp in the same directory.

In `@apps/desktop/src/components/historyGraph/helper.tsx`:
- Around line 102-116: The getRemoteRefs function hardcodes filtering to only
include refs/remotes/origin/* branches, which excludes remote-tracking branches
from other remotes like upstream. Additionally, both getBranchRefs and
getRemoteRefs only filter out origin/HEAD symbolic refs, allowing other remote
symbolic HEAD refs through. Update getRemoteRefs to match any refs/remotes/*
pattern instead of just origin, and update both getBranchRefs and getRemoteRefs
to filter out all remote symbolic HEAD refs (any refs/remotes/*/HEAD pattern)
rather than just the origin variant.

In `@apps/desktop/src/components/historyGraph/PanoramaChart.tsx`:
- Around line 85-100: The applyViewport callback performs division by ws (window
size) on lines 99-100, but when totalCommits is zero, winOf returns zero,
causing division by zero and invalid CSS values. Add a guard clause at the
beginning of the applyViewport useCallback function that returns early if
totalCommits equals zero, preventing the downstream ratio calculations from
executing. Apply the same guard check to the other affected location at lines
157-159.

In `@apps/desktop/src/routes/app/inbox/index.tsx`:
- Around line 16-23: The useGitHistoryGraph hook is being initialized with an
excessively large limit value of 10,000 for the initial page, causing
performance degradation in large repositories. Reduce the limit parameter in the
useGitHistoryGraph call to a smaller, more reasonable initial page size that
balances user experience with performance, such as a value in the range of
50-100, allowing subsequent pages to be fetched as needed through pagination.

In `@apps/desktop/src/routes/app/issues/index.tsx`:
- Around line 18-25: The div container with gridTemplateColumns and scrollRef is
not configured to allow vertical scrolling despite wheel event handlers in lines
92-96 attempting to modify scrollTop. Add overflowY property set to "auto" or
"scroll" in the style object of this div element (alongside the existing
overflowX: "hidden") to enable vertical scrolling on this container, ensuring
the wheel event consumption in the handler code can actually scroll the content
vertically.

In `@packages/commands/src/types.ts`:
- Around line 206-216: The search field in the HistoryQuerySchema export is
unused throughout the service logic and does not appear in any query processing
functions such as build_cache_key, build_log_args, or build_revision_args.
Remove the search property line (z.string().optional()) from the
HistoryQuerySchema object definition. Additionally, remove the corresponding
search field from the Rust HistoryQuery struct defined in
crates/git/models/graph.rs to maintain consistency between the TypeScript and
Rust contracts.

In `@packages/ui/src/components/chart.tsx`:
- Around line 243-246: The conditional check `{item.value &&` on line 243 uses a
truthy check which treats the numeric value 0 as falsy and prevents it from
being rendered. Replace this truthy check with an explicit nullish check such as
`item.value != null` to properly allow zero values to display while still
preventing null and undefined values from rendering in the tooltip span element.
- Around line 80-99: The ChartStyle component uses dangerouslySetInnerHTML with
dynamic values including the id parameter, colorConfig keys, and color values
that are interpolated directly into CSS without escaping or validation, creating
an XSS vulnerability. Escape the id variable before using it in the
[data-chart=${id}] selector to prevent selector injection, validate and escape
each key from colorConfig before using it in the --color-${key} property name,
and validate the color value before inserting it into the CSS rule to ensure it
cannot break out of the style context. Consider using CSS variables or a
CSS-in-JS approach as a safer alternative to dangerouslySetInnerHTML.
- Line 1: Remove the `@ts-nocheck` directive at the top of the chart component
file to re-enable TypeScript type checking. Then address the type safety issues:
replace the 9 unsafe `as keyof typeof` assertions with proper type guards or
explicit type narrowing instead of type assertions, add proper type guards
before accessing unknown type properties to ensure they exist before use, and
specifically fix the potential undefined property access where
`item.value.toLocaleString()` is called by adding a null/undefined check to
ensure item.value exists and is of the correct type before invoking the
toLocaleString method.

---

Nitpick comments:
In `@apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx`:
- Around line 17-65: The BranchCell component wrapped with React.memo does not
have a display name assigned, causing React DevTools to display it as
Memo(Component) instead of the actual component name. After the BranchCell
assignment is complete with React.memo, add a displayName property assignment to
the BranchCell variable with the value "BranchCell" to make the component
identifiable in React DevTools for easier debugging.
- Around line 114-126: The fragment wrapper around the map function in the local
branch refs rendering is unnecessary. Remove the empty fragment tags (the <>
</>) that wrap the map call, and move the map function directly into the
conditional expression where it renders the BranchBadge components for each ref
in row.localBranchRefs.
- Around line 158-159: Remove the commented-out backgroundColor property that
uses color-mix in the branch-cell styling section. The line containing the
commented code with backgroundColor and color-mix should be deleted entirely.
Additionally, remove any other commented-out code at the other location
mentioned in the review (line 179) that has similar styling comments. Clean up
all unused commented code to keep the codebase maintainable.
- Around line 127-139: The fragment wrapper around the row.remoteRefs.map call
is unnecessary since it only contains a single mapped element producing
BranchBadge components. Remove the outer fragment delimiters (the empty angle
brackets) while keeping the conditional check and the map function intact. This
simplifies the JSX structure without changing the functionality or how the
BranchBadge components are rendered with their key, ref, row, currentBranch, and
type props.
- Around line 43-59: The fragment wrapper around the BranchBadge component in
the branchRefs.map callback is unnecessary since there is only a single child
element being rendered. Remove the outer empty fragment operators `<>` and `</>`
that wrap the BranchBadge component, returning the BranchBadge component
directly from the map function instead.
- Around line 140-152: The unnecessary fragment wrapper around the
row.tags.map() call should be removed. Since the conditional check
{row.tags.length > 0 && ...} already ensures the block only renders when tags
exist, and the fragment is only wrapping a single map operation that generates
BranchBadge components, you can safely remove the <> and </> fragment delimiters
and render the map call directly within the conditional expression.

In `@apps/desktop/src/components/historyGraph/columns/branch/index.tsx`:
- Around line 6-27: The Branch component created with React.memo lacks a
displayName property, causing React DevTools to display it as "Memo(Component)"
instead of the actual component name. After the React.memo assignment for the
Branch component, add a displayName property assignment to the Branch constant
to explicitly set its display name to 'Branch'.

In
`@apps/desktop/src/components/historyGraph/columns/commiters/commiters-cell.tsx`:
- Around line 18-117: The CommitersCell component is wrapped in React.memo but
lacks a displayName property, causing React DevTools to display it as
Memo(Component) instead of the actual component name. Add a displayName property
assignment after the component export to set it to "CommitersCell", which will
help with debugging and make the component easier to identify in React DevTools.
- Around line 40-44: The initials extraction logic (splitting a name by spaces,
taking the first character of each word, joining them, and converting to
uppercase) is duplicated three times in the commiters-cell.tsx file at lines
40-44, 68-72, and 100-104. Create a reusable helper function called getInitials
that takes a name string parameter and returns the formatted initials, then
replace all three duplicate code blocks with calls to this new helper function
to eliminate the repetition.

In `@apps/desktop/src/components/historyGraph/columns/commiters/index.tsx`:
- Around line 5-13: The Commiters component is wrapped with React.memo but lacks
a displayName property, causing React DevTools to display it as
"Memo(Commiters)" instead of just "Commiters", which hampers debugging. Add a
displayName property to the Commiters component after it is defined by assigning
Commiters.displayName equal to the string "Commiters" to explicitly set how it
appears in React DevTools.

In `@apps/desktop/src/components/historyGraph/columns/stats/index.tsx`:
- Around line 5-13: The Stats component is wrapped in React.memo but lacks a
displayName property, which causes React DevTools to display it as
Memo(Component) instead of Stats, making debugging difficult. After the Stats
component definition (following the closing `});`), add a displayName property
to the Stats component by assigning it the string value "Stats" to ensure proper
identification in React DevTools.

In `@apps/desktop/src/components/historyGraph/columns/stats/stats-cell.tsx`:
- Around line 10-38: The StatsCell component is wrapped with React.memo() but
does not have a display name assigned, which causes React DevTools to show it as
Memo(Component) instead of StatsCell. Add a displayName property to the
StatsCell constant after the React.memo() declaration by assigning
StatsCell.displayName equal to the string 'StatsCell' to improve debugging
visibility in React DevTools.

In `@crates/git/models/graph.rs`:
- Around line 79-91: Add the Clone trait to the derive macro for the
CommitActivityQuery struct. Include Clone in the derive attribute alongside
Debug, Serialize, and Deserialize to allow for easy cloning of query objects
without requiring ownership transfer. Since the struct contains only primitive
types (usize and bool), Clone can be derived automatically.

In `@crates/git/service/graph.rs`:
- Around line 801-820: The head_index assignment inside the .map() closure is a
side effect that should be avoided in a mapping operation. Refactor this code to
separate the concerns: either use .enumerate().fold() to accumulate both the
CommitActivityItem collection and the head_index value in a single pass, or
extract the HEAD matching logic into a separate operation outside the map
operation (such as finding the index where the oid matches head_oid.as_deref()
after collecting the items). This will make the intent clearer and avoid
mutating external state within the transformation closure.

In `@crates/git/service/history.rs`:
- Around line 57-63: The synchronous `graph_service::commit_activity` function
performs blocking git subprocess calls within the async `commit_activity`
method, which could block the Tokio runtime thread. If async task starvation
becomes an issue in production, wrap the `graph_service::commit_activity` call
using `tokio::task::spawn_blocking` to offload the blocking work to a separate
thread pool and prevent runtime starvation. This ensures the async context is
not blocked by long-running synchronous operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bc9cf612-4995-4342-ade0-4efdf50acef4

📥 Commits

Reviewing files that changed from the base of the PR and between 17c1f84 and adc89c7.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (50)
  • apps/desktop/package.json
  • apps/desktop/src-tauri/src/commands/history.rs
  • apps/desktop/src-tauri/src/lib.rs
  • apps/desktop/src/app.css
  • apps/desktop/src/components/diffBoxes.tsx
  • apps/desktop/src/components/historyGraph/PanoramaChart.tsx
  • apps/desktop/src/components/historyGraph/body.tsx
  • apps/desktop/src/components/historyGraph/columns/branch/branch-badge.tsx
  • apps/desktop/src/components/historyGraph/columns/branch/branch-cell.tsx
  • apps/desktop/src/components/historyGraph/columns/branch/index.tsx
  • apps/desktop/src/components/historyGraph/columns/commit-hash/commit-hash-cell.tsx
  • apps/desktop/src/components/historyGraph/columns/commit-hash/index.tsx
  • apps/desktop/src/components/historyGraph/columns/commiters/commiters-cell.tsx
  • apps/desktop/src/components/historyGraph/columns/commiters/index.tsx
  • apps/desktop/src/components/historyGraph/columns/lanes/color-palettes.ts
  • apps/desktop/src/components/historyGraph/columns/lanes/index.tsx
  • apps/desktop/src/components/historyGraph/columns/lanes/lane-background.tsx
  • apps/desktop/src/components/historyGraph/columns/lanes/lane-cell.tsx
  • apps/desktop/src/components/historyGraph/columns/lanes/lane-graph.tsx
  • apps/desktop/src/components/historyGraph/columns/stats/index.tsx
  • apps/desktop/src/components/historyGraph/columns/stats/stats-cell.tsx
  • apps/desktop/src/components/historyGraph/columns/summary/index.tsx
  • apps/desktop/src/components/historyGraph/columns/summary/summary-cell.tsx
  • apps/desktop/src/components/historyGraph/columns/timestamp/index.tsx
  • apps/desktop/src/components/historyGraph/columns/timestamp/timestamp-cell.tsx
  • apps/desktop/src/components/historyGraph/helper.tsx
  • apps/desktop/src/components/historyGraph/index.tsx
  • apps/desktop/src/components/historyGraph/virtualizer.ts
  • apps/desktop/src/hooks/useRepository.ts
  • apps/desktop/src/routes/app/inbox/index.tsx
  • apps/desktop/src/routes/app/issues/index.tsx
  • apps/desktop/src/state/domains/RepositoryState.ts
  • apps/desktop/src/types/app.ts
  • crates/git/models/commit.rs
  • crates/git/models/graph.rs
  • crates/git/models/history.rs
  • crates/git/parsers/graph.rs
  • crates/git/service/graph.rs
  • crates/git/service/history.rs
  • crates/git/tests/history_service.rs
  • package.json
  • packages/commands/src/.typecache
  • packages/commands/src/commands.ts
  • packages/commands/src/index.ts
  • packages/commands/src/types.ts
  • packages/ui/package.json
  • packages/ui/src/components/avatar.tsx
  • packages/ui/src/components/button.tsx
  • packages/ui/src/components/chart.tsx
  • packages/ui/src/styles/base.css
💤 Files with no reviewable changes (1)
  • packages/ui/src/styles/base.css

Comment on lines +65 to +120
useEffect(() => {
const root = scrollRef.current;
if (!root) return;

let activeRowId: string | undefined | null = null;

const map = new Map<string, HTMLElement[]>();

const getRowEls = (rowId: string) => {
let els = map.get(rowId);
if (!els) {
els = Array.from(
root.querySelectorAll(`[data-cell-id="${rowId}"]`),
) as HTMLElement[];
map.set(rowId, els);
}
return els;
};

const apply = (rowId: string | undefined | null) => {
if (activeRowId === rowId) return;

if (activeRowId) {
getRowEls(activeRowId).forEach((el) =>
hoverClasses.forEach((c) => el.classList.remove(c)),
);
}

if (rowId) {
getRowEls(rowId).forEach((el) =>
hoverClasses.forEach((c) => el.classList.add(c)),
);
}

activeRowId = rowId;
};

const onMove = (e: PointerEvent) => {
const el = (e.target as Element)?.closest?.("[data-cell]");
if (!el) {
apply(null);
return;
}
apply((el as HTMLElement).dataset.cellId);
};

const onLeave = () => apply(null);

root.addEventListener("pointerover", onMove);
root.addEventListener("pointerleave", onLeave);

return () => {
root.removeEventListener("pointerover", onMove);
root.removeEventListener("pointerleave", onLeave);
};
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Effect missing hoverClasses dependency causes stale closure.

The effect closure captures hoverClasses but doesn't list it in the dependency array, so if hoverClasses changes, the event handlers will continue using the stale array.

🐛 Proposed fix
     return () => {
       root.removeEventListener("pointerover", onMove);
       root.removeEventListener("pointerleave", onLeave);
     };
-  }, []);
+  }, [hoverClasses]);

Note: Since hoverClasses is redeclared on every render (lines 43-46), this will recreate the effect on every render. Consider moving hoverClasses outside the component or memoizing it:

+const HOVER_CLASSES = [
+  cn("bg-secondary/70"),
+  cn("[&_[data-hidden-branch-refs]]:flex"),
+];
+
 const GraphBody = ({
   rows,
   fetchNextPage,
   hasNextPage,
   isFetchingNextPage,
   scrollRef,
 }: GraphBodyProps) => {
-  const hoverClasses = [
-    cn("bg-secondary/70"),
-    cn("[&_[data-hidden-branch-refs]]:flex"),
-  ];

Then use HOVER_CLASSES in the effect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/body.tsx` around lines 65 - 120, The
useEffect hook is missing hoverClasses from its dependency array, causing the
event handlers in onMove to use stale references if hoverClasses changes. Since
hoverClasses is redeclared on every render (lines 43-46), the best fix is to
move the hoverClasses constant outside the component body so it's not recreated
on each render, then reference it in the apply function within the useEffect.
Alternatively, if hoverClasses must remain in the component, add it to the
useEffect dependency array at the end, but this will cause the effect to
recreate on every render due to the constant being redeclared.

Comment on lines +71 to +82
const map = new Map<string, HTMLElement[]>();

const getRowEls = (rowId: string) => {
let els = map.get(rowId);
if (!els) {
els = Array.from(
root.querySelectorAll(`[data-cell-id="${rowId}"]`),
) as HTMLElement[];
map.set(rowId, els);
}
return els;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Map cache grows unbounded as rows change.

The Map caches DOM query results but is never cleared when rows change, causing memory leaks as stale row IDs accumulate.

🛡️ Proposed fix

Clear the map when rows change or when the effect re-runs:

   useEffect(() => {
     const root = scrollRef.current;
     if (!root) return;

     let activeRowId: string | undefined | null = null;

     const map = new Map<string, HTMLElement[]>();

     const getRowEls = (rowId: string) => {
       let els = map.get(rowId);
       if (!els) {
         els = Array.from(
           root.querySelectorAll(`[data-cell-id="${rowId}"]`),
         ) as HTMLElement[];
         map.set(rowId, els);
       }
       return els;
     };

     // ... rest of effect

     return () => {
+      map.clear();
       root.removeEventListener("pointerover", onMove);
       root.removeEventListener("pointerleave", onLeave);
     };
   }, [hoverClasses]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const map = new Map<string, HTMLElement[]>();
const getRowEls = (rowId: string) => {
let els = map.get(rowId);
if (!els) {
els = Array.from(
root.querySelectorAll(`[data-cell-id="${rowId}"]`),
) as HTMLElement[];
map.set(rowId, els);
}
return els;
};
useEffect(() => {
const root = scrollRef.current;
if (!root) return;
let activeRowId: string | undefined | null = null;
const map = new Map<string, HTMLElement[]>();
const getRowEls = (rowId: string) => {
let els = map.get(rowId);
if (!els) {
els = Array.from(
root.querySelectorAll(`[data-cell-id="${rowId}"]`),
) as HTMLElement[];
map.set(rowId, els);
}
return els;
};
// ... rest of effect
return () => {
map.clear();
root.removeEventListener("pointerover", onMove);
root.removeEventListener("pointerleave", onLeave);
};
}, [hoverClasses]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/body.tsx` around lines 71 - 82, The
`map` variable caches DOM query results indefinitely without clearing stale
entries when `rows` change, causing unbounded memory growth. Add a useEffect
hook that depends on `rows` and clears the `map` cache by calling `map.clear()`
whenever `rows` changes, ensuring the cache doesn't accumulate stale row IDs and
old DOM references.

Comment on lines +142 to +150
{columns.map(({ Component }, idx) => (
<Component
key={idx}
rows={processedRows}
style={getColumnStyle(idx + 1, TOTAL_ROWS)}
layout={layout}
scrollRef={scrollRef}
/>
))}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use stable keys for column components.

Array index as key can cause React to incorrectly reuse component instances if the columns array order changes.

🐛 Proposed fix
           {columns.map(({ Component }, idx) => (
             <Component
-              key={idx}
+              key={columns[idx].id}
               rows={processedRows}
               style={getColumnStyle(idx + 1, TOTAL_ROWS)}
               layout={layout}
               scrollRef={scrollRef}
             />
           ))}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/components/historyGraph/body.tsx` around lines 142 - 150,
The columns.map() function is using the array index idx as the React key, which
can cause component reuse issues if the columns array order changes. Replace the
key={idx} with a stable identifier from the column object itself, such as a
unique id property or column name that remains constant regardless of array
position. If the column objects don't have a unique identifier, consider adding
one or using a property that uniquely identifies each column component.

<Avatar className="ring-2 ring-background rounded-sm size-4.5">
<AvatarImage
alt={commitRow.commit?.authors.author.name}
src={`https://avatars.githubusercontent.com/u/e?email=${commitRow.commit?.authors.author.email}&s=64`}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Email addresses exposed in avatar URLs may leak PII.

GitHub avatar URLs contain email addresses in query parameters, which may be logged by proxies, CDNs, or browser history, potentially violating GDPR/CCPA. Consider hashing emails or using GitHub user IDs if available.

Also applies to: 65-65, 97-97

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/components/historyGraph/columns/commiters/commiters-cell.tsx`
at line 37, The avatar URL in the commiters-cell component is exposing email
addresses as query parameters, which creates a PII leak risk that can be logged
by proxies, CDNs, or browser history. Replace the email query parameter in the
src attribute with either a hashed version of the email or a GitHub user ID if
available from the commitRow data. Apply this same change to all occurrences
mentioned (lines 37, 65, and 97) where the avatar URL is constructed with the
email parameter in the query string.

Comment on lines +18 to +25
<div
style={{
display: "grid",
gridTemplateColumns: "minmax(min-content, max-content) 400px 1fr",
overflowX: "hidden",
width: "100%",
}}
ref={scrollRef}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Vertical wheel events are redirected to a non-scrollable element.

The container referenced at Line 25 is not set up for vertical scrolling, but Lines 92–96 consume wheel events and write scrollTop. With PageLayout using hidden overflow, this can block normal vertical navigation.

🛠️ Suggested fix
       <div
+        className="h-full min-h-0 overflow-y-auto"
         style={{
           display: "grid",
           gridTemplateColumns: "minmax(min-content, max-content) 400px 1fr",
           overflowX: "hidden",
           width: "100%",
         }}
         ref={scrollRef}
       >
...
           onWheel={(e: React.WheelEvent<HTMLDivElement>) => {
             const parent = scrollRef.current;
             if (!parent) return;

             const isVerticalIntent = Math.abs(e.deltaY) > Math.abs(e.deltaX);

-            if (isVerticalIntent) {
+            const canScrollDown =
+              parent.scrollTop + parent.clientHeight < parent.scrollHeight;
+            const canScrollUp = parent.scrollTop > 0;
+
+            if (
+              isVerticalIntent &&
+              ((e.deltaY > 0 && canScrollDown) || (e.deltaY < 0 && canScrollUp))
+            ) {
               parent.scrollTop += e.deltaY;
               e.preventDefault();
               e.stopPropagation(); // <-- important in WKWebView
             }
           }}

Also applies to: 86-97

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/routes/app/issues/index.tsx` around lines 18 - 25, The div
container with gridTemplateColumns and scrollRef is not configured to allow
vertical scrolling despite wheel event handlers in lines 92-96 attempting to
modify scrollTop. Add overflowY property set to "auto" or "scroll" in the style
object of this div element (alongside the existing overflowX: "hidden") to
enable vertical scrolling on this container, ensuring the wheel event
consumption in the handler code can actually scroll the content vertically.

Comment thread packages/commands/src/types.ts
Comment thread packages/ui/src/components/chart.tsx
Comment thread packages/ui/src/components/chart.tsx
Comment thread packages/ui/src/components/chart.tsx
@ruru-m07 ruru-m07 changed the title feat: optiized graph data loading feat: improved git graph data loading Jun 22, 2026
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.

1 participant