Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions app/projects/[id]/editor/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ContinuousEditingToggle } from "@/components/editor/ContinuousEditingTo
import { DeveloperEditorLayout } from "@/components/editor/developer/DeveloperEditorLayout";
import { StandardEditorLayout } from "@/components/editor/standard/StandardEditorLayout";
import { BranchSelector, BranchBadge, RevisionHistoryPanel, HistoryButton } from "@/components/revision";
import { BranchProvider } from "@/lib/context/BranchContext";
import { BranchProvider, type BranchProviderHandle } from "@/lib/context/BranchContext";
import { useOntologyTree } from "@/lib/hooks/useOntologyTree";
import { useCollaborationStatus } from "@/lib/hooks/useCollaborationStatus";
import { ConnectionStatus } from "@/components/ui/ConnectionStatus";
Expand Down Expand Up @@ -75,6 +75,7 @@ export default function EditorPage() {
const [normalizationStatus, setNormalizationStatus] = useState<NormalizationStatusResponse | null>(null);

// Branch state
const branchRef = useRef<BranchProviderHandle>(null);
const [activeBranch, setActiveBranch] = useState<string | undefined>(undefined);

// Source state (shared across modes)
Expand Down Expand Up @@ -335,6 +336,7 @@ export default function EditorPage() {
setSourceIriIndex(new Map());
loadRootClasses();
iriPatternDetectedRef.current = false;
branchRef.current?.refreshBranches();

pendingSaveResolveRef.current?.();
pendingSaveResolveRef.current = null;
Expand Down Expand Up @@ -588,6 +590,7 @@ export default function EditorPage() {
setSourceContent("");
// Reload tree to ensure consistency
loadRootClasses();
branchRef.current?.refreshBranches();
} catch (err) {
toast.error(
"Failed to delete class",
Expand Down Expand Up @@ -646,6 +649,7 @@ export default function EditorPage() {
// Re-index source IRIs
setSourceIriIndex(new Map());
iriPatternDetectedRef.current = false;
branchRef.current?.refreshBranches();
}, [session?.accessToken, projectId, activeBranch, project?.git_ontology_path, sourceContent, toast, updateNodeLabel]);

// Handle update property (form-based editing)
Expand Down Expand Up @@ -685,6 +689,7 @@ export default function EditorPage() {
setDetailRefreshKey((k) => k + 1);
setSourceIriIndex(new Map());
iriPatternDetectedRef.current = false;
branchRef.current?.refreshBranches();
}, [session?.accessToken, projectId, activeBranch, project?.git_ontology_path, sourceContent, toast]);

// Handle update individual (form-based editing)
Expand Down Expand Up @@ -724,6 +729,7 @@ export default function EditorPage() {
setDetailRefreshKey((k) => k + 1);
setSourceIriIndex(new Map());
iriPatternDetectedRef.current = false;
branchRef.current?.refreshBranches();
}, [session?.accessToken, projectId, activeBranch, project?.git_ontology_path, sourceContent, toast]);

// Handle suggestion-mode class update
Expand Down Expand Up @@ -1032,7 +1038,7 @@ export default function EditorPage() {
}

return (
<BranchProvider projectId={projectId} accessToken={session?.accessToken} initialBranch={initialBranch}>
<BranchProvider projectId={projectId} accessToken={session?.accessToken} initialBranch={initialBranch} refreshRef={branchRef}>
<Header />
<main id="main-content" className="min-h-[calc(100vh-4rem)] bg-slate-100 dark:bg-slate-900">
{/* Editor Header */}
Expand Down
9 changes: 8 additions & 1 deletion components/revision/GitGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface GitGraphProps {
onSelectCommit?: (hash: string) => void;
config?: Partial<GraphConfig>;
className?: string;
refs?: Record<string, string[]>;
defaultBranch?: string;
}

export function GitGraph({
Expand All @@ -24,13 +26,18 @@ export function GitGraph({
onSelectCommit,
config: configOverrides,
className,
refs,
defaultBranch,
}: GitGraphProps) {
const config = useMemo(
() => ({ ...DEFAULT_GRAPH_CONFIG, ...configOverrides }),
[configOverrides]
);

const layout = useMemo(() => buildGraphLayout(commits), [commits]);
const layout = useMemo(
() => buildGraphLayout(commits, refs, defaultBranch),
[commits, refs, defaultBranch]
);

if (commits.length === 0) {
return null;
Expand Down
26 changes: 25 additions & 1 deletion components/revision/RevisionHistoryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { cn } from "@/lib/utils";
import {
History,
GitMerge,
GitBranch,
User,
Calendar,
X,
} from "lucide-react";
import { useBranch } from "@/lib/context/BranchContext";
import { Button } from "@/components/ui/button";
import { GitGraph } from "./GitGraph";
import { CommitDetailView } from "./CommitDetailView";
Expand All @@ -35,10 +37,12 @@ export function RevisionHistoryPanel({
className,
}: RevisionHistoryPanelProps) {
const [commits, setCommits] = useState<RevisionCommit[]>([]);
const [refs, setRefs] = useState<Record<string, string[]> | undefined>();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selectedHash, setSelectedHash] = useState<string | null>(null);
const [selectedCommit, setSelectedCommit] = useState<RevisionCommit | null>(null);
const { defaultBranch, loadBranches } = useBranch();

const loadHistory = useCallback(async () => {
if (!projectId) return;
Expand All @@ -49,6 +53,7 @@ export function RevisionHistoryPanel({
try {
const response = await revisionsApi.getHistory(projectId, accessToken);
setCommits(response.commits);
setRefs(response.refs);
} catch (err) {
const message =
err instanceof Error ? err.message : "Failed to load history";
Expand All @@ -61,8 +66,9 @@ export function RevisionHistoryPanel({
useEffect(() => {
if (isOpen) {
loadHistory();
loadBranches();
}
}, [isOpen, loadHistory]);
}, [isOpen, loadHistory, loadBranches]);

const handleSelectCommit = (commit: RevisionCommit) => {
setSelectedHash(commit.hash);
Expand Down Expand Up @@ -161,6 +167,8 @@ export function RevisionHistoryPanel({
commits={commits}
selectedHash={selectedHash}
onSelectCommit={handleGraphSelect}
refs={refs}
defaultBranch={defaultBranch}
className="pt-0"
/>
</div>
Expand All @@ -185,6 +193,22 @@ export function RevisionHistoryPanel({
{commit.message}
</p>

{/* Branch ref badges */}
{(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>
)}

{/* Merge badge */}
{commit.is_merge && commit.merged_branch && (
<span className="inline-flex items-center gap-1 rounded-full bg-purple-100 px-1.5 py-0.5 text-xs font-medium text-purple-700 dark:bg-purple-900/30 dark:text-purple-300">
Expand Down
1 change: 1 addition & 0 deletions lib/api/revisions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface RevisionHistoryResponse {
project_id: string;
commits: RevisionCommit[];
total: number;
refs?: Record<string, string[]>;
}

export interface RevisionDiffChange {
Expand Down
12 changes: 12 additions & 0 deletions lib/context/BranchContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
useCallback,
useContext,
useEffect,
useImperativeHandle,
useState,
type ReactNode,
type Ref,
} from "react";
import {
branchesApi,
Expand Down Expand Up @@ -56,17 +58,23 @@ interface BranchContextValue {

const BranchContext = createContext<BranchContextValue | null>(null);

export interface BranchProviderHandle {
refreshBranches: () => Promise<void>;
}

interface BranchProviderProps {
projectId: string;
accessToken?: string;
initialBranch?: string;
refreshRef?: Ref<BranchProviderHandle>;
children: ReactNode;
Comment on lines +61 to 70
Copy link

Choose a reason for hiding this comment

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

🛠️ 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 lib

Repository: 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 -250

Repository: 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 2

Repository: 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 2

Repository: 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().

}

export function BranchProvider({
projectId,
accessToken,
initialBranch,
refreshRef,
children,
}: BranchProviderProps) {
const [branches, setBranches] = useState<BranchInfo[]>([]);
Expand Down Expand Up @@ -218,6 +226,10 @@ export function BranchProvider({
}
}, [initialBranch, initialBranchHandled, isLoading, branches, currentBranch]);

useImperativeHandle(refreshRef, () => ({
refreshBranches: loadBranches,
}), [loadBranches]);

const value: BranchContextValue = {
branches,
currentBranch,
Expand Down
Loading
Loading