Import ReleaseInfoSnapshot from the server instead of restating it - #985
Merged
Merged
Conversation
apps/web/src/hooks/use-cached-release-info.ts hand-mirrored the server's ReleaseInfoSnapshot (11 fields) rather than importing it. Web already type-imports ReleaseChannel from the same module in use-release-stream.ts, so the direct import needed no new leaf module. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 21, 2026
selfcontained
added a commit
that referenced
this pull request
Aug 21, 2026
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. Claude-Session: https://claude.ai/code/session_014PHD7HNfqtZHTXTLWwxxWo Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
apps/web/src/hooks/use-cached-release-info.tshand-restated the server'sReleaseInfoSnapshot— an 11-field copy ofapps/server/src/release-info.ts:31-43. It nowimport types the server declaration and re-exports it under the same name. +5/-18, one file.This is sub-item (e) of the server↔web wire-type duplication cluster (the 2026-08-14 audit's duplicate-block scan). (a)
use-service-resources#965, (b) reviews three-way #971, (c) brain #976, (d) agent-history #981 are already done.Why it's tech debt
Two hand-maintained copies of one HTTP response shape. Nothing enforced that they stayed in sync — a field added or renamed server-side would type-check fine on both sides while the web copy silently described a shape the server no longer sends.
Shape A, no new leaf module needed
The two-grep probe from prior runs:
grep -n '^export class\|^class' apps/server/src/release-info.ts→ no class, souseDefineForClassFields(the Single-source the brain wire types shared by server and web #976 TS2401 trap) does not apply.grep -n '^import' apps/server/src/release-info.ts→ no service-layer type, and nothing reachingapps/server/src/generated/*.Better than a probe:
apps/web/src/hooks/use-release-stream.ts:26already type-importsReleaseChannelfrom this exact module, sorelease-info.ts's whole graph is in web's TS program today.pnpm run check:webwas run first, withapps/server/src/generated/still absent (confirmed byls), so the #863 generated-assets trap is ruled out rather than masked bypnpm run check's side effect.Web now imports directly from
../../../server/src/release-inforather than routing throughuse-release-stream.ts. That drops the three type-only imports this hook took from that hub (AssistedUpdateMetadata,PendingMigration,ReleaseChannel), which were only there to build the copy.Drift: none
Field-for-field identical. The one name difference is cosmetic and pre-resolved: web's
PendingMigrationis alreadyexport type PendingMigration = PendingMigrationSummary(use-release-stream.ts:45), the exact server type the snapshot uses. This is the fifth of six consolidations to find zero drift.Completeness
grep -rn "absoluteLatestTag:" apps e2e bin scripts(a middle field, counting declaration sites) returns exactly one non-test hit after the change:apps/server/src/release-info.ts:35. Before: two.Deliberately excluded — near-misses, do not re-litigate
ReleaseInfoinuse-release-stream.ts:47-72— the admin-enriched/api/v1/release/infoshape. It looks like the same duplication, but the server builds that response as an anonymous object literal atroutes/release.ts:345-360; there is no named server type to alias. Naming one would be adding typing to a route return, not removing a duplicate. Backlogged separately.UiEventrestated inuse-sse.ts:52-97vsapps/server/src/server/ui-events.ts:8-73** — a much larger duplication in a different domain, and blocked by Shape A:ui-events.tsdeclaresclass UiEventBroker`, so it needs a leaf extraction. Backlogged; out of scope for a one-item run.CachedInfoResponse({ snapshot: ReleaseInfoSnapshot | null }) — kept web-local. Its server counterpart ishandleCachedInfo's inferred{ snapshot }return atroutes/release.ts:362, anonymous server-side too; now that the element type is shared, the one-field envelope carries no drift risk.{ tag; publishedAt; url }inline shape inside the snapshot — moved along with the type, not extracted. It is anonymous on the server as well.Validation
pnpm run check:web(run first,apps/server/src/generated/absent) ✅pnpm run check✅pnpm run finalize:web✅ (built, PWA precache generated)apps/webvitest: 70 files / 1044 tests passed ✅pnpm run test:e2e: 181 passed, 12 skipped ✅Next run
Sub-item (f):
apps/server/src/shared/git/agent-diff.tsvsapps/web/src/hooks/use-agent-diff.ts(3 mirrored windows), then (g)messages/store.tsvsuse-agent-messages.ts.🤖 Generated with Claude Code