Skip to content

[RFC]: Refactor desktop frontend architecture boundaries #434

@hiqiancheng

Description

@hiqiancheng

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

  • AgentService
  • conversation runtime
  • tool execution
  • session persistence
  • context construction
  • instruction loading
  • agent orchestration
  • MCP integration
  • database schema or migrations

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:

  1. test(architecture): add import boundary ratchet
  2. refactor(agent): move tooling contracts out of service
  3. refactor(session): move display contracts out of services
  4. refactor(auth): move provider config policy out of agent
  5. refactor(popup): split manifest from component registry
  6. refactor(database): isolate native backup operations
  7. test(architecture): enforce service boundaries
  8. refactor(search): route page orchestration through controllers
  9. refactor(settings): move section orchestration to composables
  10. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:agent-serviceAgentService and conversation runtime changesarea:databaseSchema, persistence, or migration changesarea:frontendFrontend UI or view-layer changesarea:mcpMCP integration changeskind:rfcArchitecture or cross-cutting design discussionstatus:acceptedConfirmed and ready for contribution

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions