refactor: split board.ts into projection / cache / sse / actions modules #1146
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/agent-hooks!1146
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1139"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #1139.
Summary
Decomposes the 1817-line
apps/server/src/domain/views/board.tsinto four focused files and eliminates every module-levelletdeclaration by folding the six pieces of mutable state onto per-concern singleton classes (mirrors the #1140 pattern used forrender-queue.ts).board-projection.tsprojectBoard(input)+ helpers (parseClosesIssues,foldCiState,foldReviewState,currentBoardStage, card-face derivers). No I/O, no cache, no module-level mutable state.board-cache.tsBoardCachesingleton ownsdeps,result,agentInstances,prState. Orchestrates the Forgejo fetches, computes thetypeInfomap, and hands a pure input toprojectBoard. HostsprobeBoardWorker.board-sse.tsBoardSseListenersingleton with theregisteredflag + broadcast-type filter that busts the response + agent-instances caches. Idempotent.board-actions.tsBoardActionsStatesingleton ownsrestageDeps,kickDeps,rerouteCount,kickCount. Hosts the four mutation handlers (assign / reroute / restage / kick) and the agent-type label rules they enforce.board.tshandleBoard,handleBoardAgentInstances,parseReposParam,filterBoardByRepos, plus re-exports for every previously public symbol.board-projection.test.tsprojectBoard+ helpers; no DB / SSE /loadWebhookConfigboot.Callers (
server.ts,bootstrap.ts,board.test.ts) keep importing from./boardunchanged — the facade re-exports the entire previous public surface.Module dependency graph
No cycles.
nonDispatchableTypes/getWebhookConfig/ DB resolvers are now only touched byboard-cache.ts(build orchestration) andboard-actions.ts(mutation handlers); the projection takes already-resolvedtypeInfo+userToTypemaps.Out of scope
parseClosesIssues→ fold CI + review → cache).Test plan
just qa— 3371 / 3371 tests passing (3360 existing + 11 new pure-projection tests), typecheck + biome + sql / paraglide / i18n / flow-schema checks green.grep -n "^let " apps/server/src/domain/views/board*.tsreturns no matches — all mutable state lives on the four singleton classes.board-projection.test.tsruns in 55 ms with no DB / config-loading setup, demonstrating the projection is unit-testable in isolation.🤖 Generated with Claude Code
Review — PR #1146
CI: ✅ green (3371/3371) · Mergeable: ✅ · Verdict: APPROVE
AC verification (issue #1139)
board-projection.ts— pure, no I/O, no cacheprojectBoard(input)takes pre-fetched data onlyboard-cache.ts— cache, TTL, deps mapBoardCachesingleton classboard-sse.ts— SSE listener + registered flagBoardSseListenersingleton classboard-actions.ts— restage + kick mutationsBoardActionsStatesingleton classlets eliminatedgrep "^let"returns no matchesboard-projectionunit-testable with pure inputsboard-projection.test.ts, 55 ms, no DB / SSE / config bootserver.ts,bootstrap.ts,board.test.ts) unchangedWhat's well done
projectBoardaccepts a fully resolvedBoardProjectionInput(no DB calls, nogetWebhookConfig). The test proves it: 11 tests run in isolation with zero infrastructure setup. This is the real win of the refactor.projection ← cache ← sse ← actions ← facade. No surprises.letand module-levelconst Mapis now a class field with a clear owner. The #1140 pattern (singleton class per concern) is applied consistently.checkRateLimit(map, key, limit, windowMs)replaces the two separate duplicate helpers (checkRerouteRateLimit/checkKickRateLimit). Cleaner.buildAgentInstancesoptionalprobeWorkerarg is backward-compatible; existing callers (facadehandleBoardAgentInstances) pass nothing and get the production default.getBoardDeps()sharing between cache and actions is intentional and well-designed: in tests, onesetBoardDeps()call covers all board I/O paths simultaneously, which is what you want.Minor notes (non-blocking)
void BOARD_STAGES;inboard-actions.tsBOARD_STAGESis imported from@claude-hooks/sharedsolely for thisvoidexpression — the actual stage set used at runtime isSTAGE_SET(imported fromboard-projection). The intent appears to be documentation ("restage shares its stage definition with the projection"), butvoid expris an opaque way to express that. SinceSTAGE_SETis already defined asnew Set(BOARD_STAGES)inboard-projection.ts, the coupling is already implicit through that import chain. Consider either:void BOARD_STAGESline and the direct@claude-hooks/sharedimport inboard-actions.ts(the transitive chain is sufficient), orNot a blocker — Biome passes and the logic is correct; it's a readability nit.
Not in scope / confirmed out of scope
board-cache.tsorboard-actions.tsdirectly — these continue to be covered by the existing integration-styleboard.test.tsthrough the facade. Acceptable for a pure refactor PR._agentInstancesCachewas listed as one of the mutable state items in the issue (counted among the "6") even though the issue body named only 6. All 9 distinct mutable pieces are handled.Ready to merge.