Single-source the history wire types shared by server and web - #981
Merged
Conversation
apps/web/src/hooks/use-agent-history.ts hand-mirrored five response shapes that apps/server/src/routes/activity/history-routes.ts restated inline as pool.query<> generics. Both sides now derive from a new dependency-free leaf, apps/server/src/routes/activity/history-wire.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Sub-item (d) of the server↔web wire-type duplication cluster found by the 2026-08-14 audit's duplicate-block scan (5 mirrored 7-line windows — the largest remainder after #965, #971 and #976).
What was duplicated
apps/web/src/hooks/use-agent-history.tshand-wrote five response shapes thatapps/server/src/routes/activity/history-routes.tsrestated inline asdeps.pool.query<{...}>generics:type ChildAgentHistoryChildAgentHistoryEventHistoryTokenUsageHistoryMediaHistoryFeedbackItemWhat changed
New dependency-free leaf
apps/server/src/routes/activity/history-wire.ts(73 lines, zero imports, no classes) declares them once.history-routes.tsuses them as itspool.query<>generics;use-agent-history.tstype-imports them and re-exports every name it exported before, so no consumer of either module changed.HistoryLatestEventis named because the same{type, message, updatedAt, metadata}object was restated three times (serverChildAgent, webHistoryChildAgent, webHistoryAgent).3 files, +32/−119.
Why Shape B (leaf extraction) and not a direct import
history-routes.tsimports./shared.js, whoseActivityRouteDepspulls inAgentManager— i.e. most of the server graph would land in web's TS program. The class probe (grep -n '^export class\|^class') came back empty, but the service-layer graph settles it on its own. The leaf was verified against web's TS program withapps/server/src/generated/absent (pnpm run check:webbeforepnpm run check, which creates that directory as a side effect).Drift
None. All five shapes were field-for-field identical; only declaration order differed (web listed
totalTokensbeforelatestEventon the child agent). No web field turned out to be an un-mirrored wire field this time.Deliberately excluded
HistoryAgent/HistoryAgentsResponse/HistoryAgentDetailstay web-local. The server's agents-list and agent-detail queries carry no type generic at all, so there is nothing to share — typing those rows would be adding new server-side typing, not removing a duplicate.messages: AgentMessage[]keeps pointing at@/hooks/use-agent-messages. The message row is sub-item (g) of the same cluster (messages/store.tsvsuse-agent-messages.ts) and belongs to its own run.TokenStatsinapps/web/src/hooks/use-activity.ts:168looks likeHistoryTokenTotalsbut carries an extratotal_sessions, and is produced by a different route (token-routes.ts). Not merged — it is a separate duplication pair (backlogged), and merging would widen the history payload's contract.TokenByModel(use-activity.ts:209) vs the newHistoryTokenByModel: same lookalike situation — five fields vs three, different route, different SQL. Left alone.size_bytes:inapps/server/src/media/store.ts:18andserver/mcp-handlers.ts:1058— same field name, different domain row (the media store's own shape, not the history payload's media entry).handleHistoryProjects'sprojectOptionsreturn type stays anonymous; web'suseHistoryProjectsonly readsprojects: string[], so there is no second declaration to remove.Completeness check
Content-grep on middle fields, counting declaration sites:
total_cache_creationandmediaRefandsize_bytes:each resolve to exactly one declaration for the history payload (history-wire.ts) outside test fixtures. The remaining hits are the token/activity pair and the media-store row listed above.Checks
pnpm run check:web(withgenerated/absent) ·pnpm run check·pnpm run finalize:web· web vitest 1000/1000 · server vitest 2797 passed / 9 skipped ·pnpm run test:e2e181 passed / 12 skipped.Queued next
Sub-item (e) of the cluster:
apps/server/src/release-info.tsvsapps/web/src/hooks/use-cached-release-info.ts(3 windows).🤖 Generated with Claude Code