Summary
Refactor the desktop frontend architecture boundaries so shared contracts live in a stable inner layer, implementation services stop importing view modules, and high-fan-out entry components delegate orchestration to local controllers without changing user-visible behavior.
Motivation
The current TypeScript import graph contains a large strongly connected component spanning views, services, database, stores, utils, and popup runtime code. The most important direct cycles include PopupService <-> PopupView, AuthService <-> AgentService, BuiltInToolService <-> AgentService, NativeService <-> database, and service/utils cycles around session status display data.
This makes small changes propagate across unrelated modules, hides architectural regressions from tests, and makes popup/session/provider/database protocol changes harder to review safely.
Affected boundaries
Proposed design
Introduce apps/desktop/src/contracts as a pure contract layer. Contract files must not import services, database, utils, views, components, composables, stores, Vue, Tauri runtime APIs, or .vue files.
Add an import-boundary Vitest ratchet that starts with known baseline violations allowlisted, rejects new violations immediately, detects strongly connected components, and requires all allowlists to be empty by the end of the PR.
Move neutral shared types out of implementation modules:
- built-in tool call/error contracts out of
AgentService
- attachment/widget/session display contracts out of services and utils
- provider config data contract into
contracts/providerConfig.ts
- provider config parsing, schema, endpoint constants, and managed-mode policy into
application/providerConfigPolicy.ts
- database IPC protocol types into
contracts/database.ts
Split popup registration into two responsibilities:
contracts/popupManifest.ts is the single source for built-in popup IDs, dimensions, and declarative position strategy
PopupService registers layout/position calculators from the manifest and never imports Vue views
PopupView/popupComponents.ts maps manifest-derived popup IDs to Vue components using satisfies Record<PopupType, Component>
Move native backup operations out of the database layer into DataManagementService, leaving temporary compatibility wrappers only while callers are migrated.
Reduce orchestration fan-out in entry components by extracting local controllers for SearchView and SettingsView. The measured goal is entry-component direct dependency reduction, not changing feature behavior.
Ship as one PR tied to this RFC, split into reviewable Conventional Commit commits:
test(architecture): add import boundary ratchet
refactor(agent): move tooling contracts out of service
refactor(session): move display contracts out of services
refactor(auth): move provider config policy out of agent
refactor(popup): split manifest from component registry
refactor(database): isolate native backup operations
test(architecture): enforce service boundaries
refactor(search): route page orchestration through controllers
refactor(settings): move section orchestration to composables
test(database): cover ipc protocol shape
Alternatives and trade-offs
One alternative is to keep compatibility wrappers permanently and only add local fixes around the obvious cycles. That has lower short-term review cost, but leaves old import paths as architectural escape hatches and does not prevent regression.
Another alternative is to move shared contracts directly into packages/shared. That is premature because the immediate dependency cycle is inside apps/desktop; promotion should wait until another package actually consumes these contracts.
A third alternative is to put popup metadata and component registration into one shared registry. That keeps call sites small, but makes application services depend on Vue components again. Splitting manifest metadata from the view-owned component registry keeps dependency direction clear while still avoiding duplicate popup IDs.
Upstream references
- Local architecture plan:
docs/superpowers/plans/2026-06-07-architecture-boundary-refactor.md
- Existing contribution policy:
CONTRIBUTING.md
- Existing architecture notes:
CLAUDE.md
Testing and rollout
The PR should be opened as a draft until the boundary ratchet and behavior tests are green.
Required checks:
pnpm --filter @touchai/desktop test:typecheck
pnpm --filter @touchai/desktop test:unit
pnpm --filter @touchai/desktop lint:check
pnpm test:pr before marking the PR ready
pnpm --filter @touchai/desktop test:rust if the database IPC protocol parity task is included locally
Focused coverage should include architecture import boundaries, PopupView tests, SearchView composable tests, SettingsView provider/data-management tests, and database protocol serialization tests.
Main risks:
- accidentally turning
contracts into a new dependency sink
- breaking popup behavior across separate popup window contexts
- changing provider config parsing behavior while moving policy out of AgentService
- mixing controller extraction with boundary fixes in a way that is hard to review
Summary
Refactor the desktop frontend architecture boundaries so shared contracts live in a stable inner layer, implementation services stop importing view modules, and high-fan-out entry components delegate orchestration to local controllers without changing user-visible behavior.
Motivation
The current TypeScript import graph contains a large strongly connected component spanning views, services, database, stores, utils, and popup runtime code. The most important direct cycles include
PopupService <-> PopupView,AuthService <-> AgentService,BuiltInToolService <-> AgentService,NativeService <-> database, and service/utils cycles around session status display data.This makes small changes propagate across unrelated modules, hides architectural regressions from tests, and makes popup/session/provider/database protocol changes harder to review safely.
Affected boundaries
Proposed design
Introduce
apps/desktop/src/contractsas a pure contract layer. Contract files must not importservices,database,utils,views,components,composables,stores, Vue, Tauri runtime APIs, or.vuefiles.Add an import-boundary Vitest ratchet that starts with known baseline violations allowlisted, rejects new violations immediately, detects strongly connected components, and requires all allowlists to be empty by the end of the PR.
Move neutral shared types out of implementation modules:
AgentServicecontracts/providerConfig.tsapplication/providerConfigPolicy.tscontracts/database.tsSplit popup registration into two responsibilities:
contracts/popupManifest.tsis the single source for built-in popup IDs, dimensions, and declarative position strategyPopupServiceregisters layout/position calculators from the manifest and never imports Vue viewsPopupView/popupComponents.tsmaps manifest-derived popup IDs to Vue components usingsatisfies Record<PopupType, Component>Move native backup operations out of the database layer into
DataManagementService, leaving temporary compatibility wrappers only while callers are migrated.Reduce orchestration fan-out in entry components by extracting local controllers for
SearchViewandSettingsView. The measured goal is entry-component direct dependency reduction, not changing feature behavior.Ship as one PR tied to this RFC, split into reviewable Conventional Commit commits:
test(architecture): add import boundary ratchetrefactor(agent): move tooling contracts out of servicerefactor(session): move display contracts out of servicesrefactor(auth): move provider config policy out of agentrefactor(popup): split manifest from component registryrefactor(database): isolate native backup operationstest(architecture): enforce service boundariesrefactor(search): route page orchestration through controllersrefactor(settings): move section orchestration to composablestest(database): cover ipc protocol shapeAlternatives and trade-offs
One alternative is to keep compatibility wrappers permanently and only add local fixes around the obvious cycles. That has lower short-term review cost, but leaves old import paths as architectural escape hatches and does not prevent regression.
Another alternative is to move shared contracts directly into
packages/shared. That is premature because the immediate dependency cycle is insideapps/desktop; promotion should wait until another package actually consumes these contracts.A third alternative is to put popup metadata and component registration into one shared registry. That keeps call sites small, but makes application services depend on Vue components again. Splitting manifest metadata from the view-owned component registry keeps dependency direction clear while still avoiding duplicate popup IDs.
Upstream references
docs/superpowers/plans/2026-06-07-architecture-boundary-refactor.mdCONTRIBUTING.mdCLAUDE.mdTesting and rollout
The PR should be opened as a draft until the boundary ratchet and behavior tests are green.
Required checks:
pnpm --filter @touchai/desktop test:typecheckpnpm --filter @touchai/desktop test:unitpnpm --filter @touchai/desktop lint:checkpnpm test:prbefore marking the PR readypnpm --filter @touchai/desktop test:rustif the database IPC protocol parity task is included locallyFocused coverage should include architecture import boundaries, PopupView tests, SearchView composable tests, SettingsView provider/data-management tests, and database protocol serialization tests.
Main risks:
contractsinto a new dependency sink