Skip to content

Decoupling & Logic Improvements #96

@kryptodrex

Description

@kryptodrex

1. Centralize Tab Identifiers

Parent status: [x] Done

Problem: Tab IDs ('metrics', 'breakdown', 'bills', 'loans', 'taxes', 'savings', 'other-income') are scattered as raw string literals across ~15 files. Renaming or adding a tab requires hunting through search modules, PlanDashboard routing, PlanTabs, and test fixtures.

Files affected:

Tasks:

  • Create a TAB_IDS constant object in src/constants/tabIds.ts (e.g. TAB_IDS.bills, TAB_IDS.metrics) and derive the TabId type from it.
  • Replace all raw tab ID string literals across PlanDashboard, search modules, and tests with TAB_IDS.* references.
  • Verify the TabId type is derived from the constant (type TabId = typeof TAB_IDS[keyof typeof TAB_IDS]) so adding a new tab is a single-location change.
  • Add/update tests where necessary.

Done definition:

  • No raw tab ID string literal exists outside src/constants/tabIds.ts and test assertions.
  • Adding a new tab ID requires only one new entry in the constants file.

2. Centralize Frequency Constants

Parent status: [x] Done

Problem: Pay frequency, bill frequency, savings frequency, and view mode strings are used as discriminators in 30+ switch/if-else statements across 12+ files. The types exist in src/types/frequencies.ts and src/types/viewMode.ts, but every function that branches on them uses raw string literals like 'bi-weekly' or 'monthly'.

Files affected:

  • src/utils/payPeriod.ts — 5+ switch statements on frequency/view mode strings (getPaychecksPerYear, getDisplayModeLabel, getDisplayModeOccurrencesPerYear, getPayFrequencyViewMode, formatPayFrequencyLabel)
  • src/utils/payCalendar.ts — 5 separate case statements on pay frequency
  • src/utils/frequency.tsPAY_FREQUENCY_OCCURRENCES, BILL_FREQUENCY_OCCURRENCES, SAVINGS_FREQUENCY_OCCURRENCES maps with hardcoded keys
  • src/utils/billFrequency.tsconvertBillToMonthly, formatBillFrequency switch on frequency strings
  • Component files — UI option arrays for frequency dropdowns duplicate the same strings

Tasks:

  • Create src/constants/frequencies.ts exporting constant objects for each frequency family (e.g. PAY_FREQUENCIES.biWeekly, BILL_FREQUENCIES.semiAnnual) and derive the union types from them.
  • Refactor switch statements in payPeriod.ts, payCalendar.ts, frequency.ts, and billFrequency.ts to use the constants instead of raw strings.
  • Consolidate frequency dropdown option arrays (currently defined inline in OtherIncomeManager, LoansManager, etc.) into shared constants that components import.
  • Do the same for ViewMode strings — create a VIEW_MODES constant object and derive the type.
  • Add/update tests where necessary.

Done definition:

  • No raw frequency or view mode string literal used as a discriminator outside the constants file.
  • Dropdown option arrays for frequencies are imported from a shared constant, not defined inline per component.

3. Extract Field Error Types to Shared Location

Parent status: [x] Done

Problem: Every tab-view component defines its own *FieldErrors type locally. There are 7 independent definitions that all follow the same pattern ({ fieldName?: string }) but share no common base and can't be imported by tests or other modules.

Current definitions (all local to their component):

Tasks:

  • Create src/types/fieldErrors.ts and move all *FieldErrors types there.
  • Update each component to import from the shared file instead of defining locally.
  • Add/update tests where necessary.

Done definition:

  • All field error types live in src/types/fieldErrors.ts.
  • No component file contains a local *FieldErrors type definition.

4. Centralize Reallocation Source Type Metadata

Parent status: [x] Done

Problem: Reallocation source types ('bill', 'deduction', 'custom-allocation', 'savings', 'investment', 'retirement') and their metadata (display labels, pause-only vs adjustable classification, section ordering) are defined independently in multiple files. Adding a new source type requires coordinated changes across 4+ files with no compiler guidance.

Files affected:

Tasks:

  • Create src/constants/reallocationSourceTypes.ts with a single metadata array/object that defines each source type's ID, label, isPauseOnly flag, and display order.
  • Derive ReallocationProposalSourceType from the metadata so adding a type is a one-line change.
  • Refactor PAUSE_ONLY_TYPES, ADJUSTABLE_TYPES, and SECTION_ORDER in ReallocationReviewModal to be computed from the shared metadata.
  • Refactor reallocationPlanner.ts filtering logic to iterate the metadata instead of hardcoding each type.
  • Add/update tests where necessary.

Done definition:

  • Adding a new reallocation source type requires only one entry in the metadata file.
  • No file outside the constants file contains the raw source-type string literals.

5. Centralize Loan and Retirement Type Definitions

Parent status: [x] Done

Problem: Loan types ('mortgage', 'auto', 'student', etc.) and retirement plan types are defined as UI dropdown option arrays inside component files rather than shared constants. The type union in src/types/obligations.ts separately lists the same strings.

Files affected:

Tasks:

  • Create src/constants/loanTypes.ts — single-source loan type metadata (value + label); derive the Loan.type union from it.
  • Move LOAN_TYPES and LOAN_PAYMENT_FREQUENCIES from LoansManager to shared constants.
  • Remove the duplicate LOAN_TYPE_LABELS in HistorySnapshotCard — import from the shared constants instead.
  • Audit retirement plan type definitions in retirement.ts for the same pattern and consolidate if needed.
  • Add/update tests where necessary.

Done definition:

  • Loan type labels have a single source of truth imported by both LoansManager and HistorySnapshotCard.
  • No separate LOAN_TYPE_LABELS map exists outside the shared constants file.

6. Remove Component Import from BudgetContext

Parent status: [x] Done

Problem: BudgetContext.tsx imports ErrorDialog from src/components/_shared. This is a dependency-inversion violation — the data/state layer should not depend on presentation components. It makes BudgetContext harder to test in isolation and creates a potential circular dependency risk.

Tasks:

  • Refactor BudgetContext to accept an error display callback (or emit an error event) instead of directly rendering ErrorDialog.
  • Move ErrorDialog rendering to a higher-level component (e.g. a provider wrapper or PlanDashboard) that subscribes to BudgetContext error state.
  • Add/update tests where necessary.

Done definition:

  • src/contexts/BudgetContext.tsx has zero imports from src/components/.
  • Error display behavior is unchanged from the user's perspective.

7. Consolidate Inline Types in PayBreakdown

Parent status: [x] Done

Problem: PayBreakdown.tsx defines 5+ large local types (AllocationCategory, AllocationAccount, AccountFunding, ValidationMessage, ReallocationUndoSnapshot, ReallocationSummaryMeta). AllocationCategory alone has 12 optional boolean/count flags suggesting it conflates multiple concerns.

Tasks:

  • Move types that represent domain concepts (especially AllocationCategory, AllocationAccount, AccountFunding) to src/types/.
  • Evaluate whether AllocationCategory should be decomposed or use a discriminated union instead of 12 optional flags.
  • Keep truly component-local types (internal render state) in the component.
  • Add/update tests where necessary.

Done definition:

  • Domain-level types from PayBreakdown are in src/types/ and importable by services or tests.
  • AllocationCategory is refactored to avoid the flag-per-concern pattern.

8. Improve Logic Gate Robustness

Parent status: [x] Done

Problem: Many switch statements across the codebase use string literal cases with no default exhaustiveness check. If a new value is added to a union type, the compiler won't flag the missing case — it silently falls through.

Key locations:

Tasks:

  • Add exhaustive default: never checks (or use a helper like assertNever()) to all switch statements that branch on union types.
    • This causes a compile error if a new union member is added but not handled.
  • Where feasible, replace switch statements with constant lookup maps (e.g. Record<PayFrequency, number>) so missing keys are caught by the type system at definition time rather than at each call site.
  • Add/update tests where necessary.

Done definition:

  • All switch statements on discriminated union types have exhaustive handling or assertNever() default.
  • Adding a new member to a frequency, view mode, or tab type causes a compile error at every unhandled site.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions