Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
119 lines
8.5 KiB
Markdown
119 lines
8.5 KiB
Markdown
# 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 ↔ <domain>` 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`).
|