Skip to content

Single-source the brain wire types shared by server and web - #976

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

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

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

Tech-debt run: sub-item (c) of the server↔web wire-type duplication cluster (the pattern shipped in #856, #965, #971).

What was duplicated

apps/web/src/hooks/use-brain.ts hand-restated seven record shapes that apps/server/src/brain/store.ts already declares — BrainProject, BrainCollectionSummary, BrainObject, BrainList, BrainListItem, BrainEntryType, BrainEvent. Six duplicated 7-line windows, found by the 2026-08-14 audit's block scan.

What changed (3 files, +51 / −146)

  • New apps/server/src/brain/types.ts (102 lines, zero imports) holds the brain record shapes lifted verbatim out of store.ts.
  • store.ts now export type { … } from "./types.js" for all 14 moved names, plus a paired import type because a bare re-export does not bind the names in local scope (the trap that bit Derive the remaining within-server agent-type tables from shared #950 and Consolidate the icon-color palette onto one shared module #961). Every server-side importer — routes/brain.ts, shared/mcp/brain-tools.ts, shared/mcp/server.ts, routes/mcp.ts, server.ts, and four test files — is untouched.
  • use-brain.ts imports the leaf directly and keeps every previously-exported name as a local alias, so its importers are untouched too.
  • The BrainList & { itemCount: number } composition the list endpoints return is now named BrainListWithItemCount in types.ts, replacing four separate restatements of it (BrainAgentActivity, the listLists signature, and web's copy).

Why the new file (deviation from the last three runs)

The plan was to import brain/store.ts directly, as #965 and #971 did with their leaves. That does not compile.apps/web's tsconfig sets useDefineForClassFields: true with target: ES2020, and store.ts declares error classes with initialized properties — pulling it into web's TS program fails with TS2401: A 'super' call must be a root-level statement… at store.ts:130. The leaf-extraction fix from #863 applies instead, which is why this diff touches the server. The probe was run before committing to a shape, not after.

Drift found: none

Every field matched the server declaration exactly. The one apparent difference — web's BrainList carried an extra itemCount — is not drift: BrainStore.listLists() returns Promise<(BrainList & { itemCount: number })[]>, so web was mirroring the wire shape correctly. That composition is now the named type both sides use.

Deliberate exclusions (near-misses left alone)

  • BRAIN_ENTRY_TYPES exists as a runtime const in both store.ts (derived from ENTRY_TYPE_TABLES) and use-brain.ts (derived from ENTRY_TYPE_COUNT_KEYS). Each derives from a different local lookup table that is meaningful only on its own side, and sharing it would mean importing runtime code from server into the web bundle — a different and much larger change than a type-only import. Left as-is.
  • useBrainListItems's inline { items, totalCount, revision } matches getListItems' return type, but that type is anonymous on the server side too. Naming it would be adding a type, not removing a duplicate.
  • ScopeTotals in use-brain.ts is already derived (Pick<BrainCollectionSummary, …>), so it now derives from the server type transitively. No change needed.
  • agents/reviews.ts:297's itemCount is a review item count, unrelated to brain lists despite the shared field name.

Validation

  • pnpm run check:web alone passes with apps/server/src/generatedabsent — proof the web program does not depend on generated server assets. (pnpm run check creates that directory as a side effect, so the clean probe was run first.)
  • pnpm run check ✅ · pnpm run finalize:web ✅ (production build succeeds — the import type is fully erased by esbuild, zero runtime change)
  • Server Vitest: 2797 passed / 9 skipped ✅ · Web Vitest: 969 passed ✅ · Playwright E2E: 181 passed / 12 skipped ✅
  • Completeness grep (createdByAgentId across apps e2e bin scripts) returns exactly one declaration site outside test fixtures: brain/types.ts.

Next run

Sub-item (d) of the same cluster: apps/server/src/routes/activity/history-routes.ts vs apps/web/src/hooks/use-agent-history.ts (5 windows). A route module is even more likely than store.ts to need the leaf-extraction shape — probe with check:web first.

🤖 Generated with Claude Code

apps/web/src/hooks/use-brain.ts hand-mirrored seven record shapes from
apps/server/src/brain/store.ts. Extract the store's type block into a new
dependency-free apps/server/src/brain/types.ts and have web import it, so
the shapes have one definition.
store.ts re-exports every moved name, so all server-side importers are
untouched. The BrainList & { itemCount } composition the list endpoints
return is now named BrainListWithItemCount instead of being restated at
each of its four use sites.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 0036880 into mainAug 18, 2026
1 check passed
@selfcontained
selfcontained deleted the tech-debt/brain-wire-types branch August 18, 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