0049: read models out of db/ — lib/read-models/ + lint-enforced component boundary #127

Merged
chad merged 2 commits from glen/0049-read-models into main 2026-10-18 10:35:21 -04:00
Collaborator

Implements approved doc 0049 — the last item from the maintainability review. Pure type relocation + one lint rule; zero runtime change.

What changed:

  • The four view-shape type groups move from db/{attention,organize,search,sandbox-view}.ts to lib/read-models/{attention,sidebar,search,sandbox}.ts — 24 declarations, no barrel. The read models import nothing from db/ (their only imports are import type from lib/agent/tools/evidence and lib/plan).
  • The db modules keep every query and re-export the moved types, so all server-side importers (17 API routes, app/layout.tsx, lib/, workflows, tests) are untouched; the diff is component-focused — 10 component files now import from @/lib/read-models/*, zero @/db imports remain under components/.
  • eslint.config.mjs gains the boundary rule: no-restricted-imports error on @/db + @/db/* under components/** — "components never import db" is now machine-checked instead of remembered. AGENTS.md's layering note updated.

Verification: pi implemented; the fresh-eyes review verified all 24 moves byte-identical (the one delta is two inline import("…") types lifted to top imports), db diffs pure-cut, no import cycles, the lint rule firing on all three import shapes with negative controls (lib/tests unaffected), and full gates: tsc clean, eslint exactly the 2 pre-existing warnings, 764 passed / 13 skipped.

Review nits (both declined, documented): the rule can't see relative ../../db imports (none exist, no such convention in the repo); Where's re-export from the db path widens what was a private type (doc-sanctioned — it needed exporting to move).

After this merges, the original review's list is complete: clip consolidation, steps/tools split, chat-session decomposition, tool-views regroup, 0048 machines purge, and this.

Implements approved doc 0049 — the last item from the maintainability review. Pure type relocation + one lint rule; zero runtime change. **What changed:** - The four view-shape type groups move from `db/{attention,organize,search,sandbox-view}.ts` to `lib/read-models/{attention,sidebar,search,sandbox}.ts` — 24 declarations, no barrel. The read models import nothing from `db/` (their only imports are `import type` from `lib/agent/tools/evidence` and `lib/plan`). - The db modules keep every query and re-export the moved types, so all server-side importers (17 API routes, `app/layout.tsx`, `lib/`, workflows, tests) are untouched; the diff is component-focused — 10 component files now import from `@/lib/read-models/*`, zero `@/db` imports remain under `components/`. - `eslint.config.mjs` gains the boundary rule: `no-restricted-imports` error on `@/db` + `@/db/*` under `components/**` — "components never import db" is now machine-checked instead of remembered. AGENTS.md's layering note updated. **Verification:** pi implemented; the fresh-eyes review verified all 24 moves byte-identical (the one delta is two inline `import("…")` types lifted to top imports), db diffs pure-cut, no import cycles, the lint rule firing on all three import shapes with negative controls (lib/tests unaffected), and full gates: tsc clean, eslint exactly the 2 pre-existing warnings, **764 passed / 13 skipped**. Review nits (both declined, documented): the rule can't see relative `../../db` imports (none exist, no such convention in the repo); `Where`'s re-export from the db path widens what was a private type (doc-sanctioned — it needed exporting to move). After this merges, the original review's list is complete: clip consolidation, steps/tools split, chat-session decomposition, tool-views regroup, 0048 machines purge, and this.
Types only, zero runtime change; queries and their invariants stay in
db/. The UI read-model shapes a page draws (InboxData, SidebarData,
NeedsYouThread, SearchHit, SandboxState …) now live where the layering
rule can point:

  db/attention.ts    -> lib/read-models/attention.ts  (RequestOption,
    PendingRequest, AttentionState, Where — previously the private
    embedded base, now exported — NeedsYouThread, NeedsYouPreview,
    UnreadThread, InboxData)
  db/organize.ts     -> lib/read-models/sidebar.ts    (SidebarItem,
    AttentionCounts, FolderNode, SidebarGroup, ShelvedItem, SidebarData,
    AttentionFlags)
  db/search.ts       -> lib/read-models/search.ts     (SearchHit,
    SearchOptions)
  db/sandbox-view.ts -> lib/read-models/sandbox.ts    (SandboxRef,
    SandboxState, ConversationSandboxView, SandboxSummary, SandboxUser,
    SandboxDetail, SandboxLink)

Approach (stated per plan): each db module imports back the types its
signatures need and RE-EXPORTS the moved group, so every server-side
importer (app routes, lib, tests) is untouched and the diff is
component-focused. Components now import from @/lib/read-models/* —
10 files: inbox/{approval-card,plan-card,preview-failure-card,
question-card,unread-card, inbox-view}, sidebar/{types,search-results,
use-search}, harness-session-view. No barrel index.ts (deep imports
keep file-to-file tracing honest).

Inheritance across the split is intact: NeedsYouThread/UnreadThread
extend Where (both moved together), ShelvedItem/SidebarTreeItem build
on SidebarItem (same file / existing lib/sidebar-tree), SandboxDetail
extends SandboxSummary (same file). lib/read-models/attention.ts
references PlanCard via lib/plan (where it actually lives) and
ApprovalEvidence via lib/agent/tools/evidence — no db dependency in
the read models at all.

Gates: tsc clean, eslint at the same 2 pre-existing warnings,
vitest 764 passed / 13 skipped (baseline unchanged).
chore(lint): enforce the component/db boundary — no @/db imports in components/ (0049)
All checks were successful
deploy / checks (pull_request) Successful in 41s
deploy / deploy (pull_request) Has been skipped
c7359f1738
eslint.config.mjs gains a flat-config object scoping
no-restricted-imports to components/**: the pattern group ["@/db",
"@/db/*"] errors with 'read models live in @/lib/read-models;
queries go through API routes'. patterns grammar, not paths — the
alias needs the glob arm to catch deep imports (@/db/organize), and
paths would only match the bare @/db entry exactly. Verified by
probing a component with all three import forms (bare @/db,
@/db/organize, @/db/search): each fires the error, exit 1, probe
removed. Full run finds zero real violations — commit 33e6ce2 already
moved every component import to @/lib/read-models/*, so nothing to
fix and no value-coupling finding to report.

AGENTS.md's UI bullet gains the rule in one line: components never
import @/db (lint-enforced); read-model types live in
@/lib/read-models (0049).

Gates: tsc clean, eslint at the same 2 pre-existing warnings, vitest
764 passed / 13 skipped.
chad merged commit a14b223b39 into main 2026-10-18 10:35:21 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
chad/sweatshop-legacy!127
No description provided.