Skip to content

Import the agent-diff wire types from the server - #989

Merged
selfcontained merged 1 commit into
mainfrom
debt/agent-diff-wire-types
Aug 21, 2026
Merged

Import the agent-diff wire types from the server#989
selfcontained merged 1 commit into
mainfrom
debt/agent-diff-wire-types

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

apps/web/src/hooks/use-agent-diff.ts hand-restated four types that apps/server/src/shared/git/agent-diff.ts already declares. It now type-imports them. One file, +18/-22.

This is sub-item (f) of the server↔web wire-type duplication cluster — (a)–(e) landed in #965, #971, #976, #981 and #985. Only (g) (messages/store.ts vs use-agent-messages.ts) remains.

Headline: this one had real drift

Five of the six prior consolidations found zero drift. This one did not.

The web copy of DiffResponse was:

{ baseRef: string | null; files: DiffFile[] }

The server's is:

{ baseRef: string; files: DiffFile[]; truncatedFileCount?: number }

Both halves of that gap are explained by the route, not by a mistake in either type:

  • apps/server/src/routes/agents/lifecycle-routes.ts:394 substitutes { baseRef: null, files: [] } when getAgentDiff returns null. So baseRef really is nullable on the wire, even though the server's own DiffResponse (a function return type) always carries a merge-base SHA. The web type was right; the server type simply isn't the wire type here.
  • truncatedFileCount is set at agent-diff.ts:308 when a diff exceeds MAX_FILES (1000) and is sent over the wire. Web's copy dropped it.

So instead of a flat alias, the web type is derived:

export type DiffResponse = Omit<AgentDiffResult, "baseRef"> & { baseRef: string | null };

The nullability stays documented at the one place it's true, and truncatedFileCount — plus any future field — comes across for free.

DiffFileStatus, DiffFile and FileDiffResponse were byte-identical and are plain type-only imports re-exported under their existing names, so every consumer import (changes-file-tree.tsx, changes-diff-section.tsx, changes-tab.test.tsx) is unchanged.

Not fixed here (noted, not in scope)

Nothing in the web app reads truncatedFileCount today, so the "diff truncated at 1000 files" signal is silently dropped by the UI. That's a feature gap, not debt — surfacing it is a UI change and belongs in its own PR. Backlogged.

Deliberate exclusions (near-miss lookalikes left alone)

  • agent-diff.ts:55 NumstatEntry and :82's inline { status; oldPath? } map value both share fields with DiffFile, but they are file-private parser intermediates on one side of the boundary — not wire shapes. Left alone.
  • The /diff-stats family (use-agent-diff-stats.ts, shared/git/diff-stats.ts) is a separate route and a separate type tree. Untouched.

Why it's tech debt

Two declarations of one wire contract, already measurably drifted apart, with no compiler link between them.

Verification

  • pnpm run check:web run before anything created apps/server/src/generated (that directory is absent on a clean tree) — the web program type-checks against the server source without the generated-file side effect. This is the Alias web release/assisted-update wire types to the server definitions #863 trap and it does not apply here.
  • pnpm run check, pnpm run finalize:web, pnpm run test:e2e (181 passed), apps/web vitest (1064 passed / 71 files) — all green.
  • Completeness grep: oldPath?: string now has 4 declarations, all inside agent-diff.ts (2 wire + 2 parser internals, above); truncated: boolean and truncatedFileCount are 1 declaration each. Zero web copies remain.

Next run

Sub-item (g): apps/server/src/messages/store.ts vs apps/web/src/hooks/use-agent-messages.ts — the last of the cluster.

apps/web/src/hooks/use-agent-diff.ts hand-restated four types that
apps/server/src/shared/git/agent-diff.ts already declares. This is
sub-item (f) of the server<->web wire-type duplication cluster;
(a)-(e) landed in #965, #971, #976, #981 and #985.

DiffFileStatus, DiffFile and FileDiffResponse were byte-identical and
are now type-only imports re-exported under their existing names, so
every consumer import is unchanged.

DiffResponse had genuinely drifted — the first real drift this cluster
has turned up in six consolidations. The web copy declared
`baseRef: string | null` and omitted the server's
`truncatedFileCount?: number`. Both halves of that are explained by the
route rather than by a mistake in either type: the GET
/api/v1/agents/:id/diff handler substitutes `{ baseRef: null, files: [] }`
when getAgentDiff returns null, so nullability is real on the wire even
though the server's own DiffResponse always carries a merge-base SHA.
The web type is now derived — Omit<DiffResponse, "baseRef"> plus the
nullable baseRef — so the nullability stays documented while
truncatedFileCount and any future field come across for free.

Type-only change; esbuild erases the imports, so nothing from the
server reaches the web bundle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PHD7HNfqtZHTXTLWwxxWo
@selfcontained
selfcontained merged commit e7ceafa into main Aug 21, 2026
1 check passed
@selfcontained
selfcontained deleted the debt/agent-diff-wire-types branch August 21, 2026 09:09
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