feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
// Domain types for the API responses. Money fields are branded `Dollars` — the
|
|
|
|
|
// server stores integer cents and serializes dollars over the wire (via
|
|
|
|
|
// utils/money.fromCents), so every amount a component receives is dollars. Typing
|
|
|
|
|
// them `Dollars` lets `fmt`/`formatUSD` accept them while rejecting raw cents
|
|
|
|
|
// (e.g. bank-transaction `*_cents` fields), catching the cents-as-dollars bug
|
|
|
|
|
// class at compile time.
|
|
|
|
|
//
|
|
|
|
|
// Shapes are typed incrementally (endpoint-by-endpoint). Complex nested payloads
|
|
|
|
|
// that no typed consumer reads yet (trend series, autopay suggestion/stats,
|
|
|
|
|
// sparkline) are left `unknown` and refined when a consumer needs them.
|
|
|
|
|
import type { Dollars } from '@/lib/money';
|
|
|
|
|
|
|
|
|
|
// A bill's month status. Mirrors the server's statusService.
|
|
|
|
|
export type TrackerStatus =
|
|
|
|
|
| 'paid' | 'autodraft' | 'upcoming' | 'due_soon' | 'late' | 'missed' | 'skipped';
|
|
|
|
|
|
2026-07-04 19:32:39 -05:00
|
|
|
// A price-drift finding (driftService.getDriftReport) — a bill whose recent
|
|
|
|
|
// payments trend away from its expected amount.
|
|
|
|
|
export interface DriftBill {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
category_name?: string | null;
|
|
|
|
|
expected_amount: Dollars;
|
|
|
|
|
recent_amount: Dollars;
|
|
|
|
|
direction: string;
|
|
|
|
|
drift_pct: number;
|
|
|
|
|
months_sampled: number;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-04 19:29:42 -05:00
|
|
|
// A suggested autopay payment (trackerService.applyAutopaySuggestions).
|
|
|
|
|
export interface AutopaySuggestion {
|
|
|
|
|
bill_id: number;
|
|
|
|
|
amount: Dollars;
|
|
|
|
|
paid_date: string;
|
|
|
|
|
method: string;
|
|
|
|
|
}
|
|
|
|
|
|
feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
// A payment record as serialized by the server (money in dollars). serializePayment
|
|
|
|
|
// spreads the full DB row, so extra columns pass through as `unknown`.
|
|
|
|
|
export interface Payment {
|
|
|
|
|
id: number;
|
|
|
|
|
bill_id?: number;
|
|
|
|
|
amount: Dollars;
|
|
|
|
|
paid_date: string;
|
2026-07-04 19:29:42 -05:00
|
|
|
method?: string | null;
|
feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
payment_source?: string | null;
|
|
|
|
|
notes?: string | null;
|
2026-07-04 19:29:42 -05:00
|
|
|
autopay_failure?: number | null;
|
feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
balance_delta?: Dollars | null;
|
|
|
|
|
interest_delta?: Dollars | null;
|
|
|
|
|
created_at?: string;
|
|
|
|
|
deleted_at?: string | null;
|
|
|
|
|
[key: string]: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
feat(ts): type the shared Category API response (A4)
Adds Category (+ CategoryBillSummary) to @/types and wires categories/
createCategory/updateCategory. Category has no money field of its own (budgets
live on the spending endpoints); the nested per-bill total_paid is a raw SQL sum,
so it's deliberately left unbranded. With categories now typed, useSpending
Categories drops its `any` cast and dead d.categories branch.
Category, Bill, Payment, and the Tracker envelope are the cross-cutting types
used by many components — worth defining upfront. The remaining single-consumer
page responses (summary, analytics, spending, snowball, subscriptions, bank
ledger) are typed alongside their page conversions in phase B, where the exact
fields consumed are visible (more accurate than a speculative interface).
typecheck 0, build green, 48 client tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:27:08 -05:00
|
|
|
// A category's per-bill rollup (categories list endpoint). `total_paid` here is a
|
|
|
|
|
// raw SQL sum (not run through fromCents), so it is deliberately NOT branded.
|
|
|
|
|
export interface CategoryBillSummary {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
active: boolean;
|
|
|
|
|
payment_count: number;
|
|
|
|
|
total_paid: number;
|
|
|
|
|
last_paid_date: string | null;
|
|
|
|
|
[key: string]: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A spending/bill category. Has no money field of its own (budgets live on the
|
|
|
|
|
// spending endpoints). The list endpoint adds bill rollups; create/update return
|
|
|
|
|
// the bare row — so the rollup fields are optional.
|
|
|
|
|
export interface Category {
|
|
|
|
|
id: number;
|
|
|
|
|
user_id?: number;
|
|
|
|
|
name: string;
|
|
|
|
|
sort_order?: number;
|
|
|
|
|
spending_enabled: boolean;
|
|
|
|
|
group_id: number | null;
|
|
|
|
|
created_at?: string;
|
|
|
|
|
updated_at?: string;
|
|
|
|
|
bill_count?: number;
|
|
|
|
|
active_bill_count?: number;
|
|
|
|
|
inactive_bill_count?: number;
|
|
|
|
|
payment_count?: number;
|
|
|
|
|
bill_names?: string[];
|
|
|
|
|
bills?: CategoryBillSummary[];
|
|
|
|
|
[key: string]: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 22:24:35 -05:00
|
|
|
// A bill as serialized by the server (billsService.serializeBill spreads the DB
|
|
|
|
|
// row and converts the three money columns to dollars). SQLite booleans come back
|
|
|
|
|
// as 0/1 integers. Extra joined columns (sparkline, has_merchant_rule, …) pass
|
|
|
|
|
// through as `unknown` via the index signature.
|
|
|
|
|
export interface Bill {
|
|
|
|
|
id: number;
|
|
|
|
|
user_id?: number;
|
|
|
|
|
name: string;
|
|
|
|
|
category_id: number | null;
|
|
|
|
|
category_name?: string | null;
|
|
|
|
|
due_day: number | null;
|
|
|
|
|
override_due_date: string | null;
|
|
|
|
|
bucket: string;
|
|
|
|
|
expected_amount: Dollars;
|
|
|
|
|
interest_rate: number | null;
|
|
|
|
|
billing_cycle: string | null;
|
|
|
|
|
autopay_enabled: number;
|
|
|
|
|
autodraft_status: string | null;
|
|
|
|
|
auto_mark_paid: number;
|
|
|
|
|
website: string | null;
|
|
|
|
|
username: string | null;
|
|
|
|
|
account_info: string | null;
|
|
|
|
|
has_2fa: number;
|
|
|
|
|
notes: string | null;
|
|
|
|
|
history_visibility: string | null;
|
|
|
|
|
active: number;
|
|
|
|
|
cycle_type: string | null;
|
|
|
|
|
cycle_day: string | number | null;
|
|
|
|
|
current_balance: Dollars | null;
|
|
|
|
|
minimum_payment: Dollars | null;
|
|
|
|
|
snowball_order: number | null;
|
|
|
|
|
snowball_include: number;
|
|
|
|
|
snowball_exempt: number;
|
|
|
|
|
is_subscription: number;
|
|
|
|
|
subscription_type: string | null;
|
|
|
|
|
reminder_days_before: number | null;
|
|
|
|
|
subscription_source: string | null;
|
|
|
|
|
subscription_detected_at: string | null;
|
|
|
|
|
created_at?: string;
|
|
|
|
|
updated_at?: string;
|
|
|
|
|
deleted_at?: string | null;
|
|
|
|
|
[key: string]: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
// One tracker row: a bill's state for a given month. Built by
|
|
|
|
|
// statusService.buildTrackerRow and augmented by trackerService.getTracker.
|
|
|
|
|
export interface TrackerRow {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
category_id: number | null;
|
|
|
|
|
category_name: string | null;
|
|
|
|
|
due_date: string;
|
|
|
|
|
due_day: number | null;
|
|
|
|
|
bucket: string;
|
|
|
|
|
expected_amount: Dollars;
|
|
|
|
|
notes: string | null;
|
|
|
|
|
total_paid: Dollars;
|
|
|
|
|
paid_toward_due: Dollars;
|
|
|
|
|
overpaid_amount: Dollars;
|
|
|
|
|
balance: Dollars;
|
|
|
|
|
has_payment: boolean;
|
|
|
|
|
is_settled: boolean;
|
|
|
|
|
last_paid_date: string | null;
|
|
|
|
|
last_payment_amount: Dollars | null;
|
|
|
|
|
status: TrackerStatus;
|
|
|
|
|
autopay_enabled: boolean;
|
|
|
|
|
autodraft_status: string | null;
|
|
|
|
|
auto_mark_paid: boolean;
|
|
|
|
|
billing_cycle: string | null;
|
|
|
|
|
cycle_type: string | null;
|
|
|
|
|
cycle_day: string | number | null;
|
|
|
|
|
current_balance: Dollars | null;
|
|
|
|
|
minimum_payment: Dollars | null;
|
|
|
|
|
interest_rate: number | null;
|
|
|
|
|
is_subscription: boolean;
|
|
|
|
|
has_2fa: boolean;
|
|
|
|
|
has_merchant_rule: boolean;
|
|
|
|
|
has_linked_transactions: boolean;
|
|
|
|
|
website: string | null;
|
|
|
|
|
autopay_verified_at: string | null;
|
|
|
|
|
inactive_reason: string | null;
|
|
|
|
|
inactivated_at: string | null;
|
|
|
|
|
sparkline: unknown;
|
|
|
|
|
autopay_stats: unknown;
|
|
|
|
|
payments: Payment[];
|
|
|
|
|
// Augmented by getTracker:
|
|
|
|
|
actual_amount: Dollars | null;
|
|
|
|
|
is_skipped: boolean;
|
|
|
|
|
snoozed_until: string | null;
|
refactor(ts): convert all UI primitives + ThemeContext to TSX (B2)
Foundational for phase B: the ui/* primitives were untyped .jsx, and TS infers
React-19 ref-as-prop components as *requiring* a ref, so every consumer errored.
Converted all 22 ui primitives (button, input, card, dialog, select, table,
dropdown-menu, tabs, badge, checkbox, switch, tooltip, separator, label,
Skeleton, collapsible, alert-dialog, sonner, save-status, theme-toggle,
input-dialog, confirm-dialog) using ComponentProps<'el'> / ComponentProps<typeof
Radix.X> + VariantProps for cva components — refs now optional, event params
typed. Also converted ThemeContext.tsx (createContext(null) made useContext's
post-guard return `never`, so setTheme was uncallable — fixed with a typed
context value) and three tracker cells (EditableCell/NotesCell/
LowerThisMonthButton) that consume the branded TrackerRow.
Added a shared errMessage(unknown) helper to utils (strict catch → unknown);
usePaymentActions reuses it. TrackerRow gains monthly_notes + amount_suggestion
(real server fields).
typecheck 0, lint 0 errors (42 warns), build green, 48 client tests. NOTE: the
e2e auth-setup probe is currently red on an unrelated, pre-existing
release-notes-modal dismissal race (reproduces on the last known-good commit;
the a11y snapshot shows these converted primitives rendering correctly) —
investigating separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:57:22 -05:00
|
|
|
monthly_notes?: string | null;
|
2026-07-04 19:29:42 -05:00
|
|
|
autopay_suggestion?: AutopaySuggestion | null;
|
refactor(ts): convert all UI primitives + ThemeContext to TSX (B2)
Foundational for phase B: the ui/* primitives were untyped .jsx, and TS infers
React-19 ref-as-prop components as *requiring* a ref, so every consumer errored.
Converted all 22 ui primitives (button, input, card, dialog, select, table,
dropdown-menu, tabs, badge, checkbox, switch, tooltip, separator, label,
Skeleton, collapsible, alert-dialog, sonner, save-status, theme-toggle,
input-dialog, confirm-dialog) using ComponentProps<'el'> / ComponentProps<typeof
Radix.X> + VariantProps for cva components — refs now optional, event params
typed. Also converted ThemeContext.tsx (createContext(null) made useContext's
post-guard return `never`, so setTheme was uncallable — fixed with a typed
context value) and three tracker cells (EditableCell/NotesCell/
LowerThisMonthButton) that consume the branded TrackerRow.
Added a shared errMessage(unknown) helper to utils (strict catch → unknown);
usePaymentActions reuses it. TrackerRow gains monthly_notes + amount_suggestion
(real server fields).
typecheck 0, lint 0 errors (42 warns), build green, 48 client tests. NOTE: the
e2e auth-setup probe is currently red on an unrelated, pre-existing
release-notes-modal dismissal race (reproduces on the last known-good commit;
the a11y snapshot shows these converted primitives rendering correctly) —
investigating separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:57:22 -05:00
|
|
|
amount_suggestion?: unknown;
|
feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
previous_month_paid: Dollars;
|
|
|
|
|
// Bank-mode augmentation (present only when bank_tracking.enabled):
|
|
|
|
|
pending_cleared?: boolean;
|
|
|
|
|
bank_pending_count?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TrackerSummary {
|
|
|
|
|
total_expected: Dollars;
|
|
|
|
|
total_starting: Dollars;
|
|
|
|
|
has_starting_amounts: boolean;
|
|
|
|
|
total_paid: Dollars;
|
|
|
|
|
paid_toward_due: Dollars;
|
|
|
|
|
remaining: Dollars;
|
|
|
|
|
total_remaining: Dollars;
|
|
|
|
|
remaining_period: string;
|
|
|
|
|
remaining_label: string;
|
|
|
|
|
remaining_hint: string;
|
|
|
|
|
overdue: Dollars;
|
|
|
|
|
count_paid: number;
|
|
|
|
|
count_upcoming: number;
|
|
|
|
|
count_late: number;
|
|
|
|
|
count_autodraft: number;
|
|
|
|
|
previous_month_total: Dollars;
|
|
|
|
|
trend: unknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Bank tracking is off (`{ enabled: false }`) or on with the account snapshot.
|
|
|
|
|
export type BankTracking =
|
|
|
|
|
| { enabled: false }
|
|
|
|
|
| {
|
|
|
|
|
enabled: true;
|
|
|
|
|
account_id: number;
|
|
|
|
|
account_name: string;
|
|
|
|
|
org_name: string;
|
|
|
|
|
balance: Dollars;
|
|
|
|
|
pending_payments: Dollars;
|
|
|
|
|
pending_days: number;
|
|
|
|
|
effective_balance: Dollars;
|
|
|
|
|
unpaid_this_month: Dollars;
|
|
|
|
|
remaining: Dollars;
|
|
|
|
|
last_updated: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export interface SafeToSpendUpcoming {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
due_date: string;
|
|
|
|
|
amount: Dollars;
|
|
|
|
|
status: TrackerStatus;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-04 19:29:42 -05:00
|
|
|
// A bill landing on a given cashflow-timeline day.
|
|
|
|
|
export interface TimelineBill {
|
|
|
|
|
name: string;
|
|
|
|
|
amount: Dollars;
|
|
|
|
|
}
|
|
|
|
|
|
feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
export interface CashflowTimelinePoint {
|
|
|
|
|
date: string;
|
|
|
|
|
balance: number; // running dollar balance; cashflowUtils treats it loosely
|
2026-07-04 19:29:42 -05:00
|
|
|
bills: TimelineBill[];
|
feat(ts): type the Tracker API response with branded Dollars (A1)
New client/types.ts domain-types module. Types the full /tracker response —
TrackerResponse { summary, bank_tracking, cashflow, rows } — with every money
field branded `Dollars` (the server serializes cents→dollars, verified against
statusService.buildTrackerRow + trackerService.getTracker + buildBankTracking +
buildSafeToSpend). api.tracker() now returns Promise<TrackerResponse>.
TrackerRow + TrackerStatus move to @/types (canonical) and are re-exported from
@/lib/trackerUtils for compat; trackerUtils' row money fields upgrade from plain
`number` to branded `Dollars` (rowThreshold -> Dollars, paymentSummary re-brands
its computed amounts via asDollars so callers can fmt() them directly). This is
the first endpoint where the money brand flows end-to-end: fetch layer -> row
helpers, and (in phase B) into the components that format them.
Nested payloads no typed consumer reads yet (summary.trend, autopay_suggestion/
_stats, sparkline) are left `unknown`, to refine when consumed. asDollars is an
identity at runtime, so behavior is unchanged: typecheck 0, lint 0 errors, build
green, 48 client tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:21:45 -05:00
|
|
|
payday?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TrackerCashflow {
|
|
|
|
|
next_payday: string | null;
|
|
|
|
|
days_until_payday: number;
|
|
|
|
|
available: Dollars;
|
|
|
|
|
safe_to_spend: Dollars;
|
|
|
|
|
still_due_total: Dollars;
|
|
|
|
|
still_due_count: number;
|
|
|
|
|
upcoming: SafeToSpendUpcoming[];
|
|
|
|
|
timeline: CashflowTimelinePoint[];
|
|
|
|
|
has_data: boolean;
|
|
|
|
|
uses_bank_balance: boolean;
|
|
|
|
|
period: string;
|
|
|
|
|
period_end_label: string;
|
|
|
|
|
period_starting: Dollars;
|
|
|
|
|
period_bills_total: Dollars;
|
|
|
|
|
period_paid: Dollars;
|
|
|
|
|
period_paid_count: number;
|
|
|
|
|
period_total_count: number;
|
|
|
|
|
period_projected: Dollars;
|
|
|
|
|
month_starting: Dollars;
|
|
|
|
|
month_bills_total: Dollars;
|
|
|
|
|
month_paid: Dollars;
|
|
|
|
|
month_paid_count: number;
|
|
|
|
|
month_total_count: number;
|
|
|
|
|
month_projected: Dollars;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TrackerResponse {
|
|
|
|
|
year: number;
|
|
|
|
|
month: number;
|
|
|
|
|
today: string;
|
|
|
|
|
summary: TrackerSummary;
|
|
|
|
|
bank_tracking: BankTracking;
|
|
|
|
|
cashflow: TrackerCashflow;
|
|
|
|
|
rows: TrackerRow[];
|
|
|
|
|
}
|