# PRD — Modular Dependency Slices (Eliminate Billing ↔ Clients Cycles) - Slug: `modularity-dependency-slices` - Date: `2026-01-20` - Status: Implemented ## Summary We have “domain slice” packages (e.g. `@alga-psa/billing`, `@alga-psa/clients`) intended to be vertically-owned modules. Today, **billing ↔ clients** have a bidirectional dependency that creates circular build graph issues and has prompted dynamic-import “escape hatches”. This plan introduces a **new lower slice** that *owns the cross-domain surface area* (data access + server actions + (optionally) shared UI primitives) so dependencies go **down** (billing/clients → shared slice), and dynamic imports are removed. **Scope expansion (2026-01-20):** additional NX build cycles were discovered involving `@alga-psa/ui` importing vertical slice actions/components (e.g. tenancy/users/clients/client-portal). This plan now also covers refactoring UI so it remains a true horizontal “lower slice” (no vertical imports), eliminating `ui ↔ ` cycles that block `nx run-many -t build`. ## Problem Billing and Clients currently need each other: - Billing needs client-facing data/actions/components (e.g. client selection, lookups, contracts per client, default tax region). - Clients need billing domain logic/models (e.g. “reactivate expired contract” when a client contract assignment changes; tax settings UI embeds billing components). This creates a **bidirectional dependency** which: - Breaks static dependency analysis (NX graph cycles) and produces brittle build behavior. - Encourages dynamic import patterns that can mask missing rebuilds and cause runtime-only failures. - Violates the modularization goal: dependencies should be layered and flow **down**, not sideways. Separately, `@alga-psa/ui` contains multiple “app feature” components that import vertical slice actions (users/tenancy/clients/client-portal). Because many vertical slices depend on `@alga-psa/ui`, this turns `@alga-psa/ui` into a pseudo-vertical and creates cycles like: - `... -> auth -> ui -> tenancy -> client-portal -> clients -> ui` ## Goals - Remove the **billing ↔ clients** bidirectional dependency entirely. - Replace dynamic-import wrappers with **static imports** from a lower slice so builds and typechecking remain correct. - Define a clear ownership boundary for “cross-domain” concerns (contracts assignments, client lookup for billing, client tax default region). - Keep runtime behavior and UX unchanged (refactor only). - Keep `@alga-psa/ui` “horizontal”: no imports from vertical slices (`@alga-psa/*` domains) so dependency flow is strictly down from features/apps into UI primitives. ## Non-goals - A full re-architecture of all domain slices across the repo. - Redesigning billing/contracts or client onboarding UX. - Consolidating all “tax” concerns across the whole product (scope is only what’s needed to remove the current cycles). - Performance/observability work beyond what’s needed for safe refactoring. - Comprehensive new automated test coverage (this effort focuses on restoring deterministic typechecks/builds and removing dependency cycles; build/typecheck checks are the primary validation). ## Users and Primary Flows Primary persona: internal engineers working on modularization, billing, and clients. Primary flow: 1. Developer changes code in billing or clients. 2. The correct dependency chain triggers rebuild/typecheck deterministically. 3. No runtime-only failures caused by hidden dynamic imports or missing package builds. ## Proposed Architecture ### “Cross-dependencies” live in `@alga-psa/shared` Use the existing `@alga-psa/shared` lower slice as the home for the shared surface area between billing and clients. This shared module should: - Depend only on lower layers (e.g. `@alga-psa/db`, `@alga-psa/types`, `@alga-psa/core`, `@alga-psa/validation`, `@alga-psa/ui` if UI primitives are moved there). - Export **server actions** and/or **models** used by both `@alga-psa/billing` and `@alga-psa/clients`. - Contain the “cross-domain” operations currently duplicated/entangled across slices: - Client contract assignment operations backed by `client_contracts` and `contracts` tables (create/update/get/list). - Client lookup for billing admin UIs (paginated lists, filtering by billing cycle ranges). - Default client tax region code retrieval used by invoice generation. ### Shared UI primitives Where feasible, move UI components that are “pure UI” (no domain-side imports) into `@alga-psa/ui` so all slices can use them without pulling in a domain package. In-scope: move `ClientPicker` into `@alga-psa/ui` (it renders a picker but does not fetch clients; it receives `clients` as props). ### UI “feature” code must move up Any UI code that calls server actions or imports vertical slices must live in the owning vertical slice or in the top-level app (`server` / `client-portal`), not in `@alga-psa/ui`. Examples of out-of-scope-for-UI patterns: - `@alga-psa/ui` importing `@alga-psa/tenancy/actions` for tenant settings UI. - `@alga-psa/ui` importing `@alga-psa/users/actions` for user management/profile UIs. - `@alga-psa/ui` importing `@alga-psa/clients/actions` for client pickers/lists (UI can accept data via props instead). ### Removing dynamic import escape hatches After shared modules exist and imports are redirected, remove: - `packages/billing/src/lib/clientsHelpers.ts` dynamic wrappers. - `packages/clients/src/lib/billingHelpers.ts` dynamic wrappers (as applicable). - Any remaining dynamic imports introduced only to break cycles (e.g. importing billing models from clients actions). ## Requirements ### Functional Requirements - Billing UI can fetch clients and render billing screens without importing from `@alga-psa/clients`. - Billing backend code can access client default tax region and relevant client data without importing from `@alga-psa/clients`. - Clients-side operations that require billing contract logic can call into the new shared slice without importing from `@alga-psa/billing`. - The dependency graph has **no cycles** between `@alga-psa/billing` and `@alga-psa/clients`. - `@alga-psa/ui` contains only reusable UI primitives/hooks and does not import vertical slices (tenancy/users/clients/client-portal/auth/etc). ### Non-functional Requirements - **Static dependencies only** across packages; no dynamic import indirection used to bypass the build graph. - Clear package ownership: cross-domain code lives in the shared slice; billing/clients slices remain vertically-owned. ## Data / API Notes No schema changes are expected; this is a code movement/refactor. Server actions currently located in `@alga-psa/clients/actions` that are used by billing UIs should be relocated or wrapped (statically) in the shared slice. ## Risks - Moving server actions across packages can break import paths in Next.js client/server boundaries (must keep `use server` usage correct). - Incremental build tools may require updating tsconfig path mappings / package exports. - UI component moves (e.g. `ClientPicker`) can impact downstream imports across many packages. ## Rollout / Migration Plan 1. Introduce the new shared slice package with a minimal exported surface. 2. Move/refactor the smallest “cycle-causing” APIs first (tax region + client contract assignment). 3. Redirect `@alga-psa/billing` and `@alga-psa/clients` to import from the shared slice (static imports). 4. Remove dynamic import helpers and verify the dependency graph is cycle-free. 5. Optional: move pure UI primitives (e.g. `ClientPicker`) into `@alga-psa/ui` and update imports repo-wide. ## Open Questions 1. What should the `@alga-psa/shared` export surface and module path be for these cross-domain APIs (chosen: `@alga-psa/shared/billingClients`)? ## Acceptance Criteria (Definition of Done) - No code in `@alga-psa/billing` imports from `@alga-psa/clients` (actions or components). - No code in `@alga-psa/clients` imports from `@alga-psa/billing` (actions, components, or models). - No dynamic imports exist solely to break the billing↔clients dependency cycle. - Dependency tooling (nx graph) reports no billing↔clients cycle **or**, if nx graph is too slow/unavailable in the current environment, we verify via (a) import-greps and (b) `tsc --listFilesOnly` / `--explainFiles` that billing does not compile clients and vice versa. - `npm run build` (or the relevant package builds) succeeds with correct incremental rebuild behavior (changes in a dependency trigger builds). - `nx run-many -t build` does not report cycles rooted in `@alga-psa/ui` (e.g. `ui -> tenancy -> client-portal -> clients -> ui`).