Commit Graph

310 Commits

Author SHA1 Message Date
null c5d2aeda29 refactor(ts): PayoffPage → TypeScript (debt payoff simulator) 2026-07-04 22:36:24 -05:00
null 7a33bd1d56 refactor(ts): StatusPage → TypeScript (server-health dashboard, loose response shape) 2026-07-04 22:33:15 -05:00
null 18d0c0bdb9 refactor(ts): SettingsPage → TypeScript (auto-save settings + tracker layout toggles)
TS flagged a dead prop: onSaved was passed to NotificationPreferences (which only
takes settings) — removed it. Notification components imported from ProfilePage
(still .jsx) type as any until that page converts.
2026-07-04 22:29:16 -05:00
null b912fd74be refactor(ts): RoadmapPage → TypeScript (issues board + dev activity log) 2026-07-04 22:25:35 -05:00
null 14f1fedfd0 refactor(ts): LoginPage → TypeScript (auth-mode/TOTP/change-pw/privacy flows) 2026-07-04 22:23:31 -05:00
null 42e67dfdeb refactor(ts): DataPage → TypeScript (section routing + data-section orchestration) 2026-07-04 22:21:48 -05:00
null 2f4f76db83 refactor(ts): HealthPage → TypeScript + export makeBillDraft's SourceBill return
Annotated makeBillDraft's return as SourceBill (index-signatured) and exported it,
so its draft output stays typed; BillModal keeps initialBill: Partial<Bill> and
callers cast makeBillDraft(...) as Partial<Bill> at the boundary. typecheck 0.
2026-07-04 22:19:39 -05:00
null 57cac30029 refactor(ts): useAutoSave test → .test.ts; completes components/hooks conversion
client/hooks and all loose components/ now fully TS. Only pages/ remain.
2026-07-04 22:14:09 -05:00
null b48a34ee33 refactor(ts): SubscriptionCatalogSection → TypeScript
Typed CatalogEntry/Descriptor/MatchedBill; branded Dollars on monthly_equivalent.
Completes all loose components/ conversions. typecheck 0, lint 0 errors, build green.
2026-07-04 22:12:49 -05:00
null 2f62d6383a refactor(ts): CommandPalette → TypeScript (typed Command + Bill results) 2026-07-04 22:09:36 -05:00
null d4eb48de92 refactor(ts): BillMerchantRules → TypeScript
Typed MerchantRule/Conflict/Suggestion + generic useDebounce; billId widened to
Id (number|string) to match the api layer; asserted billId at the BillModal call
site (rendered only for existing bills). Dropped unused RuleChip billId prop.
typecheck 0.
2026-07-04 22:08:05 -05:00
null 4d380c8da0 refactor(ts): BillHistoricalImportDialog + BillsTableInner → TypeScript
Branded Dollars on historical-import candidate amounts; typed BillPrefs +
MoveControls/DragProps on the bills table. typecheck 0.
2026-07-04 22:04:55 -05:00
null 41e847a33d refactor(ts): MobileBillRow, IncomeBreakdownModal, CalendarFeedManager → TS
Also removed a stale PayoffChart.jsx duplicate (its .tsx was already committed).
Branded Dollars on income transactions/bank-tracking balances. typecheck 0.
2026-07-04 22:01:49 -05:00
null 80fb0043b6 refactor(ts): convert ImportSpreadsheetSection to TypeScript — data/ dir complete
The XLSX importer (workbook parse → per-row decision tree → bulk actions →
bill-history grouped import). Typed PreviewRow/Decision/BillGroup/ImportDetail
and the merged BillImportResult accumulator; fixed Date subtraction in the
duplicate-date sort to use getTime(). Dropped two unused imports (CountPill/fmt)
the original .jsx never used. Trimmed unused eslint-disable directives.

This completes the entire client/components/data/ directory (14 files) → .tsx.
typecheck 0, lint 0 errors, build green.
2026-07-04 21:56:04 -05:00
null 0cc5cbd957 refactor(ts): convert BankSyncSection to TypeScript
SimpleFIN connect/sync/backfill/accounts + bank-budget-tracking + auto-categorize.
Branded Cents on account/tx amounts, Dollars on financial-account balances.
Dropped unused sourceId/bills props on AccountRow. typecheck 0, build green.
2026-07-04 21:46:55 -05:00
null 5a2e37fd61 refactor(ts): convert data/ import + matching sections to TypeScript
ImportMyData (SQLite), ImportTransactionCsv, TransactionMatching sections.
Branded BankTransaction/Bill/Category flow through the matching workbench;
relaxed BillModal initialBill to Partial<Bill> (it's a pre-filled draft).

TypeScript caught a real bug: ImportTransactionCsvSection used <CountPill> 6×
but never imported it — a ReferenceError that crashed the CSV preview/results
views. Added the missing import.

typecheck 0, build green.
2026-07-04 21:42:18 -05:00
null dfedb75e6d refactor(ts): convert data/ shared + small sections to TypeScript
dataShared (SectionCard/CountPill/importErrorState), DataNav, ConnectionHero,
DownloadMyData/Erase/SeedDemo/ImportHistory/AutoMatchReview/ImportOfx sections.
Branded Cents on OFX preview txns, Dollars on auto-match amounts; SectionCardProps
exported for section cardProps passthrough. typecheck 0, build green.
2026-07-04 21:33:25 -05:00
null a8496a9c64 refactor(ts): convert admin/ cards to TypeScript (AuthMethods, BankSync, Backup, ...)
Completes the entire client/components/admin/ directory conversion to .tsx:
OidcForm/AuthConfigData/OidcTest types for the OIDC config form; Backup/
BackupSettings types for the backup manager. errMessage() on all strict
catch(unknown) sites. typecheck 0, lint 0 errors, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:26:22 -05:00
null c9b5f93569 refactor(ts): convert LoginModeCard + EmailNotifCard admin cards to TSX (B18)
LoginModeCard (AuthModeConfig; AdminUser[]) and EmailNotifCard (EmailConfig +
keyed set(k,v)). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:19:39 -05:00
null a9a58d8aa2 refactor(ts): convert CleanupPanel + UsersTable admin cards to TSX (B17)
CleanupPanel (CleanupForm/CleanupStatus/CleanupResult; keyed toggle array typed
[keyof CleanupForm,string][]). UsersTable (AdminUser type exported + reused by
AdminPage's users state; ResetForm; typed row handlers). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:17:16 -05:00
null ea531f06c7 refactor(ts): convert OnboardingWizard to TSX (B16b)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:45:53 -05:00
null 7f4ef20b7c refactor(ts): convert admin shared helpers + AddUserCard; drop dead PrivacyAdminCard (B16)
adminShared.tsx (SectionHeading/FieldRow/Toggle/formatDateTime/BackupTypeBadge —
shared by the admin cards) and AddUserCard.tsx. Deleted PrivacyAdminCard.jsx:
TypeScript surfaced that it's imported nowhere AND calls non-existent
api.privacySettings/setPrivacySettings (the geolocation toggle actually lives on
/profile/settings) — dead code with broken calls, so removed rather than
converted. typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:44:40 -05:00
null 9ad8cc7e8a refactor(ts): convert SearchFilterPanel, RecentlyDeletedBillsDialog, BillRulesManager to TSX (B15)
SearchFilterPanel (presentational props), RecentlyDeletedBillsDialog (Bill[],
days_left cast, formatUSD on Dollars), BillRulesManager (MerchantRule/RuleGroup,
reduce<Record<number,RuleGroup>> with `?? (acc[key]=…)` to satisfy
noUncheckedIndexedAccess). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:42:24 -05:00
null 6a9b8b3cb8 refactor(ts): convert transactions/ + snowball/ dirs to TSX (B14)
transactions/: CategoryPicker (Category[], Node-cast for outside-click),
MatchBillDialog (BankTransaction/Bill, advisory_filter cast). snowball/:
PayoffChart (TrackPoint chart geometry, asDollars for formatUSD), PlanHistory
Panel + PlanStatusBanner (SnowballPlan/ActivePlan types with Dollars-branded debt
balances; asDollars at fmt boundaries, ?? guards for optional-money comparisons).
typecheck 0, lint 0 errors, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:39:30 -05:00
null f34f03af51 refactor(ts): convert content pages to TSX (B13)
PrivacyPage (PrivacyDoc/PrivacySection + Record-typed iconByTitle), NotFoundPage
(typed GlitchDigit rAF loop, HTMLSpanElement ref), AboutPage (UpdateStatus/
AboutInfo + UpdateBadge), ReleaseNotesPage (ReleaseHistoryData, typed markdown
line parser). typecheck 0, lint 0 errors (35 warns), build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:32:06 -05:00
null 5e803f4547 refactor(ts): convert AdminPage + SubscriptionCatalogPage to TSX (B12)
First two pages — confirms the page pattern (self-contained, untyped .jsx
feature-children accept any props). AdminPage: User/boolean|null/unknown[] state
+ API-response casts. SubscriptionCatalogPage: consumes the now-typed BillModal
(Bill/Category), casts the partial fallback bill. typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:14:30 -05:00
null 73be95d12d refactor(ts): convert BillModal to TSX (B11) — bill-modal feature done
The app's largest, most-central component (1092 ln, the add/edit form reached
from 9 entry points). Typed ~40 useState vars (Bill/Payment/BankTransaction/
BulkUnmatchState/Date), the useActionState save (isNew narrows `bill` non-null
via the aliased `!bill` const), FormErrors = Record<string,string>, and every
API response cast (billPayments/billTransactions/syncBillSimplefinPayments/
verifyAutopay/billMerchantRules). Its now-typed sub-components type-check the
props BillModal passes them. cycle_day coerced to string for Radix Select;
Bill index-sig fields (autopay_verified_at, source_bill_id) cast at use.

client/components/bill-modal/ + BillModal are 100% .tsx. typecheck 0, lint 0
errors (35 warns), build green, 48 tests, 17/17 e2e probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:10:48 -05:00
null 31ff6adbd9 refactor(ts): convert bill-modal sub-components to TSX (B10)
The 7 BillModal sub-components + transactionDisplay helper: TemplateSection,
AutopayTrustIndicator, PaymentFormFields, PaymentHistoryList, DebtDetailsSection,
LinkedTransactionsSection, UnmatchDialogs. New @/types.BankTransaction — raw bank
amounts stay in CENTS on the wire, so branded `Cents` (formatCentsUSD, not
formatUSD), the complement to the Dollars brand. BulkUnmatchState typed with
null-safe setState callbacks. typecheck 0, lint 0 errors, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:03:09 -05:00
null 8a4b51915f refactor(ts): convert SummaryCard + ReleaseNotesDialog to TSX (B9)
SummaryCard (top-level; CardType/CardDef, value: Dollars) and ReleaseNotesDialog
(the version-ack modal we hardened; typed useAuth, HTMLElement-cast for
activeElement focus). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:54:40 -05:00
null 8937dc5e25 refactor(ts): convert app-wide common components to TSX (B8)
PageLoader, PageTransition, StatusBadge (top-level), MarkdownText (typed
renderInlineMarkdown, isValidElement for keys), and ErrorBoundary (typed class
component: ErrorBoundaryProps/State, getDerivedStateFromError, ErrorInfo;
generic withErrorBoundary<P> HOC). typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:52:47 -05:00
null 609de2a946 refactor(ts): convert the app shell to TSX (B7)
App.tsx (routing + QueryClient), main.tsx (entry; index.html updated to
main.tsx), useAuth.tsx (typed AuthContextValue/User + MeResponse; uses the safe
useContext()||defaults pattern so no `never` trap), and the layout components
(Layout, Sidebar, NavPill, BrandBlock — NavItem type, LucideIcon-typed nav
config, casts for the untyped overdue/simplefin API responses). TS caught a dead
prop: App passed mainContentId to TrackerPage, which takes no params — removed.

typecheck 0, lint 0 errors (39 warns), build green, 17/17 e2e probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:49:57 -05:00
null c2097efc49 refactor(ts): convert TrackerRow + TrackerBucket to TSX (B6) — tracker/ dir done
The two largest tracker components. TrackerRow (726 ln): typed threshold/
optimistic-actual as branded Dollars, amount_suggestion/autopay_stats now real
@/types (AmountSuggestion/AutopayStats) with null-safe `stats?.` access,
querySelectorAll<HTMLElement> for keyboard nav, Date.now()-getTime() arithmetic,
drag-handler casts for the motion.tr TableRow. TrackerBucket: typed
dragPropsFor/moveControlsFor returns (DragProps/MoveControls), bulkPay result
cast, asDollars at every total's fmt boundary.

client/components/tracker/ is now 100% .tsx (18 components). typecheck 0, lint 0
errors (40 warns), build green, 48 client tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:42:26 -05:00
null f4f1dadaab refactor(ts): convert MobileTrackerRow to TSX (B5)
The mobile tracker row (consumes branded TrackerRow + shared payment hooks).
Supporting: TrackerRow.sparkline typed number[]|null; ROW_STATUS_CLS typed
Record<string,string> (it has no `skipped` key, so indexing by TrackerStatus
needed a string index). DragProps/MoveControls interfaces exported. Notable: the
HTML5 drag handlers conflict with framer-motion's own onDrag* gesture types on
motion.div — cast via ComponentProps<typeof motion.div>; quick-pay/remaining
amounts need asDollars at the boundary. typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:36:01 -05:00
null ee31513dc9 refactor(ts): convert Drift + Overdue panels to TSX (B4)
DriftInsightPanel (new @/types DriftBill — its own row shape, money in dollars)
and OverdueCommandCenter (TrackerRow[]). Note: TS flags `Date - Date`
arithmetic → use .getTime(); the overdue/net-delta totals need asDollars at the
fmt boundary. typecheck 0, build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:32:39 -05:00
null c1ec0a4f2e refactor(ts): convert tracker dialogs + cards to TSX (B3)
Six tracker components consuming the branded domain types:
AutopaySuggestionActions, CashFlowCard (typed TrackerCashflow + TimelineChart/
UpcomingList), MonthlyStateDialog, PaymentModal, PaymentLedgerDialog,
StartingAmountsEditDialog. New @/types: AutopaySuggestion, TimelineBill; Payment
gains method/autopay_failure. Branded money paid off again — fmt(Math.abs(safe))
and the starting-amounts arithmetic correctly required asDollars wraps at the
format boundary. typecheck 0, lint 0 errors (41 warns), build green, 48 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:29:42 -05:00
null 1f18a720c4 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
null dc675fbecd refactor(ts): convert tracker leaf components to TSX (B1)
First .tsx conversions — the tracker leaf components, where the branded Dollars
now flows into the UI: StatusBadge (status: TrackerStatus), FilterChip,
PaymentProgress (paymentSummary's Dollars → fmt type-checks), SummaryCards
(SummaryCard value: Dollars, CardDef/CardType/TrendInfo typed). Props interfaces
added; no behavior change. Confirms the .tsx pattern: a typed parent importing
untyped .jsx children is fine (children accept any props — no forced cascade).

typecheck 0, lint 0 errors, build green, 48 client tests, 17/17 e2e probe (home
page renders).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:31:35 -05:00
null 4af738f947 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
null 337ad95a3e feat(ts): type Bill + Payment API responses with branded Dollars (A2-A3)
Adds Bill and reuses Payment domain types (money → Dollars, mirroring
serializeBill/serializePayment which spread the DB row and convert the money
columns). Wired: bills/allBills/deletedBills → Bill[], bill/createBill/updateBill
→ Bill; quickPay/createPayment/updatePayment/restorePayment → Payment. Replaced
api.ts's minimal PaymentRecord with the richer @/types Payment (usePaymentActions
still resolves payment.id). typecheck 0, build green, 48 client tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:24:35 -05:00
null 3c7a55c3a1 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
null d02d3c9c72 refactor(ts): convert React Query hooks to TypeScript (TS10)
hooks/useQueries.js -> .ts. Typed params and QueryParams-typed query builders
(QueryParams now exported from api.ts). Query result data stays unknown until
endpoint response shapes are typed.

TypeScript caught dead config: three hooks passed `cacheTime` to useQuery, which
React Query v5 renamed to `gcTime` and silently ignores — so those caches were
garbage-collected at the 5-min default instead of the intended 30 min / 2 hr.
Renamed to gcTime so the intended retention takes effect (memory-retention only;
no fetch or correctness change).

Verified: typecheck 0, lint 0 errors (47 warns), build green, 48 client tests,
17/17 e2e probe (every page renders).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:13:05 -05:00
null 9b805e60b2 refactor(ts): convert the API client to TypeScript (TS9)
client/api.js -> api.ts. Types the fetch infrastructure: generic _fetch<T>
and get/post/put/patch/del<T> helpers, an ApiError interface (status/code/
details/data), a QueryParams type for the query-string builder, and File-typed
upload helpers. Endpoint response shapes are typed incrementally — most methods
default to Promise<unknown>; the ones consumed by typed .ts files get real
shapes (quickPay -> PaymentRecord, togglePaid -> TogglePaidResult, settings ->
settings map).

Typing togglePaid's result surfaced a latent narrowing bug in usePaymentActions:
the un-pay Undo closure read result.paymentId (number|undefined) inside a
deferred async callback where TS re-widens the if-narrowed value — fixed by
capturing the id in a const before the closure.

The 16 files importing '@/api.js' with an explicit extension were normalized to
'@/api' so Vite/TS resolve the .ts.

Verified: typecheck 0, lint 0 errors (47 warns), build green, 48 client tests,
and 17/17 e2e probe (every page renders, all API paths respond) — the central
fetch-module rename is runtime-safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:09:04 -05:00
null 8c30a7ab09 refactor(ts): convert small hooks + version to TypeScript (TS8)
- useAutoSave.ts — generic useAutoSave<T>; AutoSaveStatus union; timer ref
  typed ReturnType<typeof setTimeout>; pending ref T|null narrows on != null.
- useSearchPanelPreference.ts — typed [boolean, setter] tuple return.
- version.ts — ReleaseNotes/ReleaseHighlight shapes; the Vite-injected
  __APP_VERSION__ global declared inline (declare const).

No behavior change. useAutoSave.test.jsx imports the .ts transparently and
passes. typecheck 0, lint 0 errors (47 warns), build green, 48 client tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:01:30 -05:00
null 3c51464bec refactor(ts): convert pure lib utilities to TypeScript (TS7)
Five self-contained leaf modules, no behavior change:
- reorder.ts — generic moveInArray<T>, reorderPayload, movedItemId
- billingSchedule.ts — a Schedule union + typed normalize/label helpers
- billDrafts.ts — SourceBill/Template/Category shapes for makeBillDraft
- trackerTableColumns.ts — typed column parse/normalize
- cashflowUtils.ts — typed SVG step-path timeline geometry (Safe-to-Spend)

noUncheckedIndexedAccess handled via in-range `!` (guarded lengths) and
destructuring defaults. Their .test.js suites import the .ts transparently via
Vite/vitest and still pass. typecheck 0, lint 0 errors (47 warns), build green,
48 client tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:59:16 -05:00
null 7c6be66794 refactor(ts): convert client/lib/trackerUtils to TypeScript (TS6)
The tracker row/status/sort helpers are now typed. Introduces a TrackerRow
domain interface (the fields these helpers read) and a TrackerStatus union
(paid|autodraft|upcoming|due_soon|late|missed|skipped). Row money fields stay
plain `number` for now — branding them Dollars is a later increment, done when
the tracker API response itself is typed so the brand flows in from the source.

Strict-mode handling: STATUS_SORT_ORDER is Record<TrackerStatus,number> (no
undefined on lookup); moveInArray guards the noUncheckedIndexedAccess `T |
undefined` from splice; the sort comparator relies on aliased-condition
narrowing to reach string|number in each branch. typecheck 0 errors, lint 0
errors (47 warnings unchanged), build green, 48 client tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:53:45 -05:00
null 7bb68db442 refactor(ts): convert usePaymentActions to TypeScript (TS5)
The shared quick-pay / toggle-paid mutation hooks are now typed: PayableRow for
the row refs, typed mutation payloads, and Dollars for the money amounts (so the
branded type flows through — a caller must supply branded dollars). Strict catch
clauses narrowed via an errMessage(unknown) helper. typecheck + lint (react-hooks
now enforced on this .ts) + build + 48 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:47:12 -05:00
null b221e02d85 refactor(ts): convert client/lib/utils to TypeScript (TS4)
The highest-traffic leaf module (cn, fmt, date/byte/uptime formatters,
categoryColor) is now strict .ts. fmt inherits formatUSD's branded-dollars input
via Parameters<typeof formatUSD>; noUncheckedIndexedAccess handled (destructuring
defaults, in-range assertion). .jsx callers unaffected (Vite resolves the .ts).
typecheck + build + 48 client tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:42:46 -05:00
null 255036afc2 feat(ts): branded Cents/Dollars money types — first TS conversion (T2)
Converted client/lib/money.js -> money.ts with branded types:
  type Cents = number & { __unit: 'cents' }
  type Dollars = number & { __unit: 'dollars' }
plus asCents/asDollars/centsToDollars/dollarsToCents. The formatters now require
the correct branded unit (a bare number won't do), so a typed caller physically
cannot format cents as dollars (the 100×-too-big bug) or vice-versa. Existing
.jsx callers are unaffected (checkJs off) — gradual adoption.

money.type-test.ts is a compile-time guard (never imported/bundled): its
@ts-expect-error lines assert each unit mixup is a real error, so typecheck
fails loudly if the branding ever regresses. Verified: removing a guard makes
tsc error 'Argument of type 1234 is not assignable to DollarsInput'.

typecheck + build (with React Compiler) + 48 client tests all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:36:24 -05:00
null a0a8579a08 refactor(tracker): shared useTogglePaid mutation hook — finish Tracker mutations (F1)
Toggle paid/unpaid was duplicated across the desktop and mobile rows (optimistic
flip + Undo/paid toasts + refresh). Extracted into a shared useTogglePaid()
React Query mutation hook: the caller keeps the instant local optimistic flip,
the hook rolls it back on error and invalidates the tracker/badge caches on
settle. isPending replaces the desktop row's local loading state (badge spinner
+ un-pay confirm dialog). With useQuickPay (P3), both core Tracker write actions
are now idiomatic useMutation hooks living in one place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:18:18 -05:00
null ad9fcbd56f refactor(tracker): shared useQuickPay mutation hook (P3)
Quick-pay was duplicated verbatim in TrackerRow and MobileTrackerRow (create
payment + Undo toast + refresh, with a manual busy flag on mobile). Extracted
into a shared useQuickPay() React Query mutation hook: isPending comes for free
(replaces the quickPaySaving state), the tracker/badge caches invalidate on
settle, and the flow lives in one place. Behavior identical.

(togglePaid keeps its local optimistic flip as-is; other mutations can adopt the
same useMutation pattern incrementally.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:04:43 -05:00