Skip to content

dfsgdhn#52

Open
alfred-murray wants to merge 15 commits intojakemor:mainfrom
superparle:main
Open

dfsgdhn#52
alfred-murray wants to merge 15 commits intojakemor:mainfrom
superparle:main

Conversation

@alfred-murray
Copy link
Copy Markdown

No description provided.

superparle and others added 5 commits April 16, 2026 13:10
Replaces the default Changes view with a side-by-side collapsible
three-column layout showing traditional diffs, a prototype
LLM-reordered diff story, and natural language summaries.
Sidebar expands dynamically based on visible panel count.

Made-with: Cursor
…eaders, auto-fit grid

- Moved Raw/AI/Summary toggle buttons into the parent Changes/History header
  to avoid z-index overlap issues with file cards
- Made panel headers sticky so they stay visible while scrolling
- Switched panels to auto-fit CSS grid that respects available sidebar width
- Removed DiffFileCard sticky header to prevent overlap with control bar

Made-with: Cursor
…ion for Raw, Reordered, Natural Language, and Summary views. Implement automatic analysis on entering the diff view and streamline stale analysis handling. Adjust commit form placement for improved layout and usability.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

This PR introduces a Codex-powered diff analysis feature: a DiffAnalysisStore on the server that runs git diffs through a Codex session and streams structured change notes and a summary back to clients, surfaced in a new three-panel sidebar view (Raw Diff, AI Order, Summary) with per-panel toggle controls.

  • P1 — DiffAnalysisStore.startAnalysis race condition: the duplicate-analysis guard (active.has(projectId)) is checked before await this.diffStore.refreshSnapshot(...), while the sentinel (active.set(projectId, ...)) is only set after that await. A second startAnalysis call that arrives while the first is suspended at that await will pass the guard and launch a second runAnalysis loop — both sharing the same Codex chatId — before either has registered in active.

Confidence Score: 4/5

Safe to merge after fixing the startAnalysis guard race; all other findings are cosmetic.

One P1 logic bug (non-atomic concurrency guard in startAnalysis) warrants attention before merge; everything else — parser, stats, hunk splitter, WebSocket wiring, and client UI — looks correct.

src/server/diff-analysis-store.ts — move active.set(...) before the first await in startAnalysis.

Important Files Changed

Filename Overview
src/server/diff-analysis-store.ts New store orchestrating Codex-based diff analysis; has a P1 race condition where the active guard is set after an await, allowing duplicate concurrent analyses for the same project.
src/client/components/chat-ui/RightSidebar.tsx Adds three-panel diff viewer (Raw, AI Order, Summary) with toggle controls; DiffLine renders the prefix character twice — once in the gutter and again in the full line content.
src/server/ws-router.ts Adds chat.analyzeDiff and chat.cancelDiffAnalysis command handlers; wires DiffAnalysisStore with a fallback stub and registers project-diff-analysis subscription topic correctly.
src/shared/protocol.ts Adds chat.analyzeDiff, chat.cancelDiffAnalysis client commands and project-diff-analysis subscription topic and snapshot type.
src/shared/diff-analysis-parser.ts Parses Codex agent response into structured ParsedDiffAnalysis; supports both legacy HUNK NOTE format and new CHANGE NOTE format with deduplication.
src/shared/diff-analysis-hunks.ts Splits unified diff into fine-grained DiffAnalysisSourceBlocks with configurable model/view context lines; logic is correct and well-tested.
src/shared/diff-analysis-stats.ts Computes diff statistics (files, hunks, additions, deletions, lines) from a unified diff string; simple and correct.
src/shared/diff-analysis.ts New shared types and factory helpers for DiffAnalysisSnapshot, ParsedDiffAnalysis, and DiffAnalysisSourceBlock; straightforward and well-structured.
src/server/server.ts Instantiates DiffAnalysisStore with an onChange callback that broadcasts project-diff-analysis snapshots; wiring is correct.
src/client/app/ChatPage/useChatPageSidebarActions.ts Adds handleAnalyzeDiff and handleCancelDiffAnalysis callbacks that dispatch WebSocket commands and pass diffAnalysis snapshot to the sidebar.
src/server/diff-analysis-store.test.ts Tests cancel-before-Codex-starts path; good coverage of the critical concurrency edge case.
ui-test/server.js Standalone HTTP server for UI testing the diff analysis flow; contains duplicated copies of shared utilities that could diverge from the TypeScript originals.

Sequence Diagram

sequenceDiagram
    participant Client
    participant WsRouter
    participant DiffAnalysisStore
    participant DiffStore
    participant CodexAppServer

    Client->>WsRouter: chat.analyzeDiff { paths }
    WsRouter->>DiffAnalysisStore: startAnalysis(projectId, paths)
    DiffAnalysisStore->>DiffStore: refreshSnapshot()
    DiffStore-->>DiffAnalysisStore: ChatDiffSnapshot
    DiffAnalysisStore->>DiffStore: readPatchesForAnalysis()
    DiffStore-->>DiffAnalysisStore: raw diff patch
    DiffAnalysisStore->>DiffAnalysisStore: parseUnifiedDiffHunks() → sourceBlocks
    DiffAnalysisStore->>DiffAnalysisStore: computeDiffStats()
    DiffAnalysisStore-->>WsRouter: (resolves, status=running)
    WsRouter-->>Client: ack
    DiffAnalysisStore->>CodexAppServer: startSession + startTurn(prompt)
    loop streaming
        CodexAppServer-->>DiffAnalysisStore: onAgentMessageDelta
        DiffAnalysisStore->>DiffAnalysisStore: parseAgentResponse() → partial hunks
        DiffAnalysisStore->>WsRouter: onChange(projectId)
        WsRouter-->>Client: project-diff-analysis snapshot
    end
    CodexAppServer-->>DiffAnalysisStore: result (completed/cancelled/error)
    DiffAnalysisStore->>WsRouter: onChange(projectId) status=completed
    WsRouter-->>Client: project-diff-analysis snapshot (final)
Loading

Fix All in Codex

Reviews (1): Last reviewed commit: "Revise diff panel UX: replace independen..." | Re-trigger Greptile

Comment thread src/server/diff-analysis-store.ts Outdated
Comment on lines +57 to +98
if (this.active.has(args.projectId)) {
throw new Error("A diff analysis is already running.")
}

const selectedPaths = [...new Set(args.paths)].sort((left, right) => left.localeCompare(right))
if (selectedPaths.length === 0) {
throw new Error("Select at least one file to analyze")
}

await this.diffStore.refreshSnapshot(args.projectId, args.projectPath)
const diffSnapshot = this.diffStore.getProjectSnapshot(args.projectId)
if (diffSnapshot.status !== "ready") {
throw new Error(diffSnapshot.status === "no_repo"
? "Project is not in a git repository"
: "Diffs are not ready yet")
}

const fileByPath = new Map(diffSnapshot.files.map((file) => [file.path, file]))
const missingPath = selectedPaths.find((path) => !fileByPath.has(path))
if (missingPath) {
throw new Error(`File is no longer changed: ${missingPath}`)
}

const runId = randomUUID()
const chatId = `diff-analysis-${args.projectId}`
const requestKey = createDiffAnalysisRequestKey(diffSnapshot.files, selectedPaths)
this.active.set(args.projectId, { runId, chatId })
this.itemBuffersByRun.set(runId, new Map())
this.patchState(args.projectId, {
status: "starting",
statusText: "Preparing diff analysis",
startedAt: new Date().toISOString(),
completedAt: null,
error: null,
selectedPaths,
requestKey,
diffStats: null,
sourceBlocks: [],
parsed: createEmptyParsedDiffAnalysis(),
plan: [],
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Race condition in startAnalysis guard check

The concurrency guard at line 57 and the active.set(...) at line 83 are separated by await this.diffStore.refreshSnapshot(...) at line 66. A second startAnalysis call arriving while the first is suspended at that await will see active.has(projectId) === false and also pass the guard, launching two concurrent runAnalysis loops for the same project. Because both share the same chatId (diff-analysis-${projectId}), they will contend on the same Codex session.

Fix: move the sentinel into active (or a separate starting set) before the first await, and clean it up if the pre-flight checks fail:

// Fail fast before any async work
if (this.active.has(args.projectId)) {
  throw new Error("A diff analysis is already running.")
}

// Reserve the slot synchronously so a concurrent call fails immediately
const runId = randomUUID()
const chatId = `diff-analysis-${args.projectId}`
this.active.set(args.projectId, { runId, chatId })

try {
  await this.diffStore.refreshSnapshot(args.projectId, args.projectPath)
  // ... remaining validation ...
  this.patchState(args.projectId, { status: "starting", ... })
  void this.runAnalysis({ ... })
} catch (error) {
  // Remove the slot so a future call can succeed
  if (this.active.get(args.projectId)?.runId === runId) {
    this.active.delete(args.projectId)
    this.itemBuffersByRun.delete(runId)
  }
  throw error
}

Fix in Codex

Comment on lines +252 to +263

function classifyDiffLine(line: string) {
if (line.startsWith("diff --git ") || line.startsWith("---") || line.startsWith("+++")) {
return "bg-muted/60 text-muted-foreground font-medium"
}
if (line.startsWith("@@")) {
return "bg-accent text-muted-foreground"
}
if (line.startsWith("+")) {
return "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"
}
if (line.startsWith("-")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Diff prefix character rendered twice

The left gutter <span> shows line.slice(0, 1) (the +/-/space prefix), and the <code> tag shows the full line — so the prefix character appears in both columns. For a line like +const x = 1, viewers see + in the gutter and +const x = 1 in the content area. The content should strip the leading prefix character:

Suggested change
function classifyDiffLine(line: string) {
if (line.startsWith("diff --git ") || line.startsWith("---") || line.startsWith("+++")) {
return "bg-muted/60 text-muted-foreground font-medium"
}
if (line.startsWith("@@")) {
return "bg-accent text-muted-foreground"
}
if (line.startsWith("+")) {
return "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"
}
if (line.startsWith("-")) {
<code className="whitespace-pre py-1 pr-3">{line.slice(1)}</code>

Fix in Codex

aasishraj and others added 10 commits April 16, 2026 16:03
…son mode, allowing users to analyze differences between the current branch and the default branch. Update UI components to support new comparison mode, including adjustments to diff loading and analysis commands. Refactor related functions and tests to ensure compatibility with the new feature.
…ine the default comparison mode based on available diffs and enhance the RightSidebar component to utilize these functions. Update tests to cover new comparison mode scenarios and ensure proper behavior when switching between modes.
Standalone React+Vite site showcasing better-diff concept.
Chalkboard theme with SVG doodles (diff blocks, magnifying glass,
braces, plus/minus signs) scattered as background illustrations.
Lenis smooth scrolling, sketch arrows, and typographic effects
(zoom-in/out, marker highlights, SHOUT caps).

Made-with: Cursor
…diff blocks

- Introduced a new utility function to filter and return only the hunk body lines from diff text.
- Updated RightSidebar component to utilize this function for rendering diff lines.
- Added a test case to verify the correct output of the new function for reordered diff blocks.
Preserve original typos, remove paraphrased descriptions,
add missing paragraphs (jump in and out, cant this be just
a skill, dev flow section).

Made-with: Cursor
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.

4 participants