Skip to content

Single-source the history wire types shared by server and web - #981

Merged
selfcontained merged 1 commit into
mainfrom
tech-debt/history-wire-types
Aug 19, 2026
Merged

Single-source the history wire types shared by server and web#981
selfcontained merged 1 commit into
mainfrom
tech-debt/history-wire-types

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

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.ts hand-wrote five response shapes that apps/server/src/routes/activity/history-routes.ts restated inline as deps.pool.query<{...}> generics:

shapeserver (before)web (before)
child agentlocal type ChildAgentHistoryChildAgent
event rowinline generic on the events queryHistoryEvent
token totals + by-modeltwo inline genericsHistoryTokenUsage
media rowinline genericHistoryMedia
feedback rowinline genericHistoryFeedbackItem

What changed

New dependency-free leaf apps/server/src/routes/activity/history-wire.ts (73 lines, zero imports, no classes) declares them once. history-routes.ts uses them as its pool.query<> generics; use-agent-history.ts type-imports them and re-exports every name it exported before, so no consumer of either module changed.

HistoryLatestEvent is named because the same {type, message, updatedAt, metadata} object was restated three times (server ChildAgent, web HistoryChildAgent, web HistoryAgent).

3 files, +32/−119.

Why Shape B (leaf extraction) and not a direct import

history-routes.ts imports ./shared.js, whose ActivityRouteDeps pulls in AgentManager — 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 with apps/server/src/generated/absent (pnpm run check:web before pnpm 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 totalTokens before latestEvent on the child agent). No web field turned out to be an un-mirrored wire field this time.

Deliberately excluded

  • HistoryAgent / HistoryAgentsResponse / HistoryAgentDetail stay 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.ts vs use-agent-messages.ts) and belongs to its own run.
  • TokenStats in apps/web/src/hooks/use-activity.ts:168 looks like HistoryTokenTotals but carries an extra total_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 new HistoryTokenByModel: same lookalike situation — five fields vs three, different route, different SQL. Left alone.
  • size_bytes: in apps/server/src/media/store.ts:18 and server/mcp-handlers.ts:1058 — same field name, different domain row (the media store's own shape, not the history payload's media entry).
  • handleHistoryProjects's projectOptions return type stays anonymous; web's useHistoryProjects only reads projects: string[], so there is no second declaration to remove.

Completeness check

Content-grep on middle fields, counting declaration sites: total_cache_creation and mediaRef and size_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 (with generated/ absent) · pnpm run check · pnpm run finalize:web · web vitest 1000/1000 · server vitest 2797 passed / 9 skipped · pnpm run test:e2e 181 passed / 12 skipped.

Queued next

Sub-item (e) of the cluster: apps/server/src/release-info.ts vs apps/web/src/hooks/use-cached-release-info.ts (3 windows).

🤖 Generated with Claude Code

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>
@selfcontained
selfcontained merged commit d889ff4 into mainAug 19, 2026
1 check passed
@selfcontained
selfcontained deleted the tech-debt/history-wire-types branch August 19, 2026 09:14
Sign up for freeto 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

@selfcontained