Show branch topology in revision history graph#10
Conversation
📝 WalkthroughWalkthroughAdds branch-ref support across the revision stack: API responses may include refs, BranchContext exposes an imperative refresh handle, the graph builder assigns lanes using branch hints and vertices carry refs, and UI components load, refresh, and render branch badges and updated graphs. Changes
Sequence Diagram(s)sequenceDiagram
participant Editor as Editor Page
participant BranchProv as BranchProvider
participant API as API Layer
participant UI as RevisionHistoryPanel
Editor->>BranchProv: mount with refreshRef
BranchProv->>BranchProv: expose refreshBranches via useImperativeHandle
Editor->>API: save / delete / update project
API-->>Editor: operation response
Editor->>BranchProv: refreshRef.current?.refreshBranches()
BranchProv->>API: loadBranches()
API-->>BranchProv: branches data
BranchProv-->>UI: context updates (branch data)
sequenceDiagram
participant UI as RevisionHistoryPanel
participant API as API Layer
participant Context as BranchContext
participant GraphBuilder as GitGraph Builder
participant Graph as GitGraph Component
UI->>API: loadHistory() (commits + optional refs)
UI->>Context: loadBranches()
Context->>API: fetch branches
API-->>Context: branches array
UI->>GraphBuilder: buildGraphLayout(commits, refs, defaultBranch)
GraphBuilder->>GraphBuilder: computeBranchHints(refs, defaultBranch)
GraphBuilder->>GraphBuilder: assign lanes/colors using hints
GraphBuilder-->>UI: GraphLayout with branch-aware positions
UI->>Graph: render commits with branch badges (refs, defaultBranch)
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/revision/RevisionHistoryPanel.tsx`:
- Around line 196-205: The badge strip is wrapping and pushing the text column
out of sync; when rendering refs?.[commit.hash]?.map((ref) => ...) around the
GitBranch badge, make the badge row single-line and truncated by adding no-wrap
and overflow truncation classes to the span (e.g. add whitespace-nowrap,
overflow-hidden, text-ellipsis/truncate and a sensible max-width or max-w-[...]
class) so long branch names don’t wrap; keep the GitBranch icon and ref text but
ensure each badge is constrained (or alternatively constrain the parent
container for all badges) so the row stays fixed-height and aligns with the
graph cell.
In `@lib/context/BranchContext.tsx`:
- Around line 61-70: The BranchProvider currently exposes an imperative
refreshRef (BranchProviderHandle.refreshBranches) and uses loadBranches; replace
this with a React Query-backed query for branches keyed by projectId and remove
refreshRef/refreshBranches from BranchProviderProps/BranchProviderHandle,
switching consumers to the query's data and refetch functions; implement any
write operations as React Query mutations that call
queryClient.invalidateQueries or mutation.onSuccess to refresh the branches
cache, and update callers (the editor and history panel) to either read the
branches from useQuery or call queryClient.refetchQueries/useQueryClient instead
of calling loadBranches or branchRef.current?.refreshBranches().
In `@lib/git-graph/graph-builder.ts`:
- Around line 183-185: The color assignment incorrectly allows the first side
branch to receive color 0 because nextColor is initialized to 0; update the
logic in graph-builder.ts (around the assignedColor/hintedLane handling and the
nextColor initialization) to reserve color 0 for lane 0 — either initialize
nextColor to 1 where it’s declared or ensure the branch that sets assignedColor
for hintedLane !== 0 uses nextColor starting at 1 (and then increments nextColor
afterwards); refer to the variables assignedColor, hintedLane, and nextColor
when making the change.
- Around line 167-171: The forEach callbacks are returning the Set via the
implicit-expression form (triggering useIterableCallbackReturn); change the
callbacks that iterate activeLanes and the other forEach at the later location
to use block bodies and call usedLanes.add(lane); (and the analogous add call)
as statements. Locate the forEach that reads activeLanes and the second forEach
around the code referencing hintedLane and replace the arrow expressions with
block-bodied arrow functions so Set.add(...) is a statement rather than an
implicit return.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 11f84d96-c734-4cba-b909-a989ba5cf677
📒 Files selected for processing (7)
app/projects/[id]/editor/page.tsxcomponents/revision/GitGraph.tsxcomponents/revision/RevisionHistoryPanel.tsxlib/api/revisions.tslib/context/BranchContext.tsxlib/git-graph/graph-builder.tslib/git-graph/types.ts
| export interface BranchProviderHandle { | ||
| refreshBranches: () => Promise<void>; | ||
| } | ||
|
|
||
| interface BranchProviderProps { | ||
| projectId: string; | ||
| accessToken?: string; | ||
| initialBranch?: string; | ||
| refreshRef?: Ref<BranchProviderHandle>; | ||
| children: ReactNode; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Expect either an existing branch-focused query/invalidation path,
# or confirmation that branch state is still fetched imperatively.
rg -n -g '*.ts' -g '*.tsx' 'branchesApi\.list|loadBranches\(' app components lib
rg -n -g '*.ts' -g '*.tsx' 'useQuery|useMutation|invalidateQueries|queryClient' app components libRepository: CatholicOS/ontokit-web
Length of output: 1345
🏁 Script executed:
#!/bin/bash
# Read the BranchContext.tsx file to verify the implementation
cat -n lib/context/BranchContext.tsx | head -250Repository: CatholicOS/ontokit-web
Length of output: 8873
🏁 Script executed:
#!/bin/bash
# Find where refreshRef/branchRef is used
rg -n 'refreshRef|branchRef' app components lib --type ts --type tsx -A 2 -B 2Repository: CatholicOS/ontokit-web
Length of output: 93
🏁 Script executed:
#!/bin/bash
# Find where refreshRef/branchRef is used
rg -n 'refreshRef|branchRef' -g '*.ts' -g '*.tsx' app components lib -A 2 -B 2Repository: CatholicOS/ontokit-web
Length of output: 3715
🏁 Script executed:
#!/bin/bash
# Check RevisionHistoryPanel.tsx for loadBranches usage
cat -n components/revision/RevisionHistoryPanel.tsx | sed -n '60,80p'Repository: CatholicOS/ontokit-web
Length of output: 676
Migrate branch state to React Query instead of using imperative refreshRef.
The editor calls branchRef.current?.refreshBranches() after writes while the history panel separately calls loadBranches() on open. This duplicates server-state management logic. Using a React Query query with mutation invalidation would handle deduping, cancellation, and cache freshness automatically, aligning with the project guideline to use React Query for server state management.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/context/BranchContext.tsx` around lines 61 - 70, The BranchProvider
currently exposes an imperative refreshRef
(BranchProviderHandle.refreshBranches) and uses loadBranches; replace this with
a React Query-backed query for branches keyed by projectId and remove
refreshRef/refreshBranches from BranchProviderProps/BranchProviderHandle,
switching consumers to the query's data and refetch functions; implement any
write operations as React Query mutations that call
queryClient.invalidateQueries or mutation.onSuccess to refresh the branches
cache, and update callers (the editor and history panel) to either read the
branches from useQuery or call queryClient.refetchQueries/useQueryClient instead
of calling loadBranches or branchRef.current?.refreshBranches().
…nts after commits Use the refs map from the backend to visually separate branches onto different lanes in the git graph. Feature branch commits now appear on a separate lane with branch name badges, while main commits stay on lane 0. Also fix stale branch data (commits ahead/behind) by refreshing branch info after every commit operation and when the history panel opens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d3fb68f to
3bdf914
Compare
Wrap branch ref badges in a non-wrapping container with overflow hidden to prevent long branch names from breaking row alignment in the git graph. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use block bodies in forEach callbacks to avoid returning Set.add() values (lint: useIterableCallbackReturn). Initialize nextColor to 1 so the main lane always gets color 0 and side branches start at color 1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
♻️ Duplicate comments (3)
lib/git-graph/graph-builder.ts (2)
155-156:⚠️ Potential issue | 🟡 MinorKeep color
0exclusive to lane0.
nextColorstill starts at0, so the first hinted side branch on Line 184 renders with the same color as the main lane. The fallback path on Line 216 has the same problem when it allocates lane0.Proposed fix
- let nextColor = 0; + let nextColor = 1; @@ - assignedColor = hintedLane === 0 ? 0 : nextColor++; + assignedColor = hintedLane === 0 ? 0 : nextColor++; @@ - assignedColor = nextColor++; + assignedColor = assignedLane === 0 ? 0 : nextColor++;Also applies to: 183-185, 212-216
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/git-graph/graph-builder.ts` around lines 155 - 156, nextColor is initialized to 0 so the first allocated side branch can get color 0 (same as main lane); change the allocation logic to keep color 0 reserved for lane 0 by initializing nextColor = 1 and update any increment/wrap logic that assigns lanes (places that do nextColor++ / nextColor = (nextColor + 1) % N or fallback lane search) to skip 0 (e.g., if nextColor becomes 0, advance to 1) so no allocation ever returns color 0 for hinted/fallback branches.
167-171:⚠️ Potential issue | 🟠 MajorFix the
forEachcallbacks before linting.Line 171 and Line 210 still use expression-bodied callbacks, so Biome treats them as returning the
SetfromusedLanes.add(...)and failsuseIterableCallbackReturn.Proposed fix
- activeLanes.forEach(({ lane }) => usedLanes.add(lane)); + activeLanes.forEach(({ lane }) => { + usedLanes.add(lane); + }); @@ - activeLanes.forEach(({ lane }) => usedLanes.add(lane)); + activeLanes.forEach(({ lane }) => { + usedLanes.add(lane); + });Also applies to: 209-210
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/git-graph/graph-builder.ts` around lines 167 - 171, The forEach callbacks that call usedLanes.add(...) are using expression-bodied arrows which return the Set value and trigger the useIterableCallbackReturn lint; change those to block-bodied callbacks so they don't return a value. Specifically, update activeLanes.forEach(({ lane }) => usedLanes.add(lane)) to activeLanes.forEach(({ lane }) => { usedLanes.add(lane); }) and make the analogous change to the other forEach near the hintedLane logic (the second occurrence that also calls usedLanes.add) so both callbacks use { ... } and no implicit return.components/revision/RevisionHistoryPanel.tsx (1)
196-205:⚠️ Potential issue | 🟡 MinorConstrain the ref badges to one line.
These rows are fixed to
ROW_HEIGHT, so long or multiple refs can still wrap here and push the text column out of sync with the graph. Keep the badge strip single-line/truncated, or raise both the row height and graph cell height together.Proposed fix
- {refs?.[commit.hash]?.map((ref) => ( - <span - key={ref} - className="inline-flex items-center gap-1 rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-300" - > - <GitBranch className="h-2.5 w-2.5" /> - {ref} - </span> - ))} + {(refs?.[commit.hash] ?? []).length > 0 && ( + <div className="mt-0.5 flex min-w-0 items-center gap-1 overflow-hidden whitespace-nowrap"> + {(refs?.[commit.hash] ?? []).map((ref) => ( + <span + key={ref} + title={ref} + className="inline-flex max-w-[160px] items-center gap-1 rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-300" + > + <GitBranch className="h-2.5 w-2.5 shrink-0" /> + <span className="truncate">{ref}</span> + </span> + ))} + </div> + )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/revision/RevisionHistoryPanel.tsx` around lines 196 - 205, The ref badges can wrap and change row height; wrap the map output in a single-line, non-wrapping container and make each badge non-shrinking so badges are clipped instead of wrapping: update the block that renders refs?.[commit.hash]?.map(...) (the span elements using GitBranch and ref) to be inside a wrapper with CSS classes enforcing overflow-hidden, whitespace-nowrap, and text-overflow/ellipses; ensure each badge gets a flex-shrink-0 (or equivalent) so they stay on one line and excess badges are truncated rather than wrapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@components/revision/RevisionHistoryPanel.tsx`:
- Around line 196-205: The ref badges can wrap and change row height; wrap the
map output in a single-line, non-wrapping container and make each badge
non-shrinking so badges are clipped instead of wrapping: update the block that
renders refs?.[commit.hash]?.map(...) (the span elements using GitBranch and
ref) to be inside a wrapper with CSS classes enforcing overflow-hidden,
whitespace-nowrap, and text-overflow/ellipses; ensure each badge gets a
flex-shrink-0 (or equivalent) so they stay on one line and excess badges are
truncated rather than wrapping.
In `@lib/git-graph/graph-builder.ts`:
- Around line 155-156: nextColor is initialized to 0 so the first allocated side
branch can get color 0 (same as main lane); change the allocation logic to keep
color 0 reserved for lane 0 by initializing nextColor = 1 and update any
increment/wrap logic that assigns lanes (places that do nextColor++ / nextColor
= (nextColor + 1) % N or fallback lane search) to skip 0 (e.g., if nextColor
becomes 0, advance to 1) so no allocation ever returns color 0 for
hinted/fallback branches.
- Around line 167-171: The forEach callbacks that call usedLanes.add(...) are
using expression-bodied arrows which return the Set value and trigger the
useIterableCallbackReturn lint; change those to block-bodied callbacks so they
don't return a value. Specifically, update activeLanes.forEach(({ lane }) =>
usedLanes.add(lane)) to activeLanes.forEach(({ lane }) => { usedLanes.add(lane);
}) and make the analogous change to the other forEach near the hintedLane logic
(the second occurrence that also calls usedLanes.add) so both callbacks use {
... } and no implicit return.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 91d32c2b-fe7a-4f33-97dd-d456384614b3
📒 Files selected for processing (7)
app/projects/[id]/editor/page.tsxcomponents/revision/GitGraph.tsxcomponents/revision/RevisionHistoryPanel.tsxlib/api/revisions.tslib/context/BranchContext.tsxlib/git-graph/graph-builder.tslib/git-graph/types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/api/revisions.ts
- lib/context/BranchContext.tsx
When the fallback lane assignment happens to pick lane 0, use color 0 instead of allocating the next color, keeping main-line commits visually consistent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/revision/RevisionHistoryPanel.tsx (1)
39-71: 🛠️ Refactor suggestion | 🟠 MajorMove the history/branch loading onto React Query.
This change adds more remote state (
refsplus a branch refresh trigger) behind localuseState/useEffect, which keeps cache and refetch behavior split across component state and context. Please model the history request as a query and drive refreshes through query invalidation/refetch instead.As per coding guidelines, "Use React Query (
@tanstack/react-query) for server state management".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/revision/RevisionHistoryPanel.tsx` around lines 39 - 71, The component is managing remote history/refs with local useState and loadHistory; replace that with a React Query useQuery that calls revisionsApi.getHistory and returns {commits, refs} keyed by ['revisions', projectId] (remove loadHistory, setCommits/setRefs, setIsLoading/setError and selected state updates tied to them), enable the query only when isOpen and projectId are present, and surface query status/error instead of local flags; keep calling loadBranches() but when branches refreshes complete (or when defaultBranch changes) trigger the history refresh by calling useQueryClient().invalidateQueries(['revisions', projectId]) or query.refetch so branch updates drive refetches rather than local effects. Ensure you reference the existing symbols revisionsApi.getHistory, loadBranches, loadHistory (remove), commits and refs state (replace), and the query key ['revisions', projectId].
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/git-graph/graph-builder.ts`:
- Around line 130-137: The loop that assigns lane hints (using nextLane, hints,
walk, GraphVertex, vertices, mainLine, and v.id) currently overwrites existing
hints and steals commits from earlier stacked branches; change the walk so it
stops when it encounters a commit that already has a hint instead of rewriting
it (i.e., break the walk if hints.has(walk) is true) — ensure you check hints
before calling hints.set and update the loop condition or add an early-break so
earlier lane assignments are preserved.
---
Outside diff comments:
In `@components/revision/RevisionHistoryPanel.tsx`:
- Around line 39-71: The component is managing remote history/refs with local
useState and loadHistory; replace that with a React Query useQuery that calls
revisionsApi.getHistory and returns {commits, refs} keyed by ['revisions',
projectId] (remove loadHistory, setCommits/setRefs, setIsLoading/setError and
selected state updates tied to them), enable the query only when isOpen and
projectId are present, and surface query status/error instead of local flags;
keep calling loadBranches() but when branches refreshes complete (or when
defaultBranch changes) trigger the history refresh by calling
useQueryClient().invalidateQueries(['revisions', projectId]) or query.refetch so
branch updates drive refetches rather than local effects. Ensure you reference
the existing symbols revisionsApi.getHistory, loadBranches, loadHistory
(remove), commits and refs state (replace), and the query key ['revisions',
projectId].
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: eddff8d5-2c95-49a5-96bb-fa643180da3c
📒 Files selected for processing (2)
components/revision/RevisionHistoryPanel.tsxlib/git-graph/graph-builder.ts
| // Walk first-parents backward until hitting a main-line commit | ||
| const lane = nextLane++; | ||
| let walk: number | null = v.id; | ||
| while (walk !== null && !mainLine.has(walk)) { | ||
| hints.set(walk, lane); | ||
| const wv: GraphVertex = vertices[walk]; | ||
| walk = | ||
| wv.parentIndices.length > 0 ? wv.parentIndices[0] : null; |
There was a problem hiding this comment.
Preserve earlier lane hints for stacked branches.
Line 134 overwrites any existing hint on the walked ancestry. If feature/b is branched from feature/a, the later walk steals feature/a's commits and collapses the older branch onto the newer lane. Stop once a commit already has a hint instead of rewriting it.
Proposed fix
- while (walk !== null && !mainLine.has(walk)) {
+ while (walk !== null && !mainLine.has(walk) && !hints.has(walk)) {
hints.set(walk, lane);
const wv: GraphVertex = vertices[walk];
walk =
wv.parentIndices.length > 0 ? wv.parentIndices[0] : null;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/git-graph/graph-builder.ts` around lines 130 - 137, The loop that assigns
lane hints (using nextLane, hints, walk, GraphVertex, vertices, mainLine, and
v.id) currently overwrites existing hints and steals commits from earlier
stacked branches; change the walk so it stops when it encounters a commit that
already has a hint instead of rewriting it (i.e., break the walk if
hints.has(walk) is true) — ensure you check hints before calling hints.set and
update the loop condition or add an early-break so earlier lane assignments are
preserved.
Summary
refsmap to assign feature branch commits to separate lanes in the git graph, with branch name badges (blue, with GitBranch icon)Test plan
npm run type-checkandnpm run lintpass cleanly🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor
Closes #8