chatTools monolith split into typed tool registry

Archibald312 broke a 3,284-line orchestration file into ten per-tool modules behind a single registry. The orchestrator shrank by roughly 70% and now handles only what genuinely belongs there.

infrastructurechat-ui

The old chatTools.ts held four schema arrays, a monolithic dispatch chain, and all ten callable tools jammed together. The new shape: each tool gets its own file with a ToolDefinition export, a registry array collects them, and a single dispatcher routes calls. Shared helpers for document reading, docx generation, and tracked-edit persistence live alongside as separate modules.

The gating logic changed too. Previously, callers threaded tool availability in via extraTools pass-throughs. Now each tool carries an availableWhen predicate that encodes whether it needs a project ID, a tabular store, or a document index. The project-chat and tabular-review routes drop their pass-through entirely.

Behavioral surface is held stable: tool schemas reproduced verbatim, SSE event shapes unchanged, aggregated side-effect counters (docsRead, docsFound, docsCreated, docsReplicated, workflowsApplied, docsEdited) carried through.

The test picture is honest about its gaps. Unit suite was green; three of four e2e specs passed. The chat/documents/projects/tabular Playwright specs were already parked on main behind test.describe.skip() due to drifted selectors before this PR landed, so the new dispatcher hasn't been exercised end-to-end under automation. A follow-up to fix those selectors is queued. Two adjacent findings (a log-out redirect change, a missing user_profiles migration in the test Supabase project) went to TECHDEBT rather than this PR.

So what Worth a look if you're carrying a bloated tool-orchestration file and want a pattern for splitting it without changing the wire behavior. The `availableWhen` predicate approach is a clean alternative to passing extra tools per route. Skip if your concern is end-to-end test coverage - the Playwright suite doesn't yet verify the new dispatcher path.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?