Commit Graph

347 Commits

Author SHA1 Message Date
null 4898987a18 feat(brand): shared app-primitives UI kit
Adds the cross-cutting UI primitives the rest of the brand refresh
consumes. The 159-line file bundles the small, repeated bits every
page/dialog needs so the per-page commits can stay minimal:

- TonedCard: bordered card with neutral / good / warn / danger / info
  variants (border + bg + text per tone), consumed by the
  overdue/summary/bank-sync cards.
- ToneDot: small status dot for the new badges.
- EmptyState: lucide Inbox + heading + body used for filtered-empty
  and zero-data states.
- SectionHeading / FieldRow: small typographic primitives for the
  Settings / Profile / Admin pages.
- BrandGlyph (re-export of the Brand module) so consumers only need
  to import from '@/components/ui/app-primitives'.

This is commit 2 of 9 in the v0.42.0 brand refresh. Depends on
dfee7f9 (brand module); consumed by the layout/UI/consumer commits.
2026-07-05 17:18:23 -05:00
null dfee7f9401 feat(brand): new brand module + logo assets
Adds the canonical brand definition used by the v0.42.0 brand refresh:

- client/lib/brand.ts — single source of truth for name, tagline,
  repository URL, asset paths, navigation glyphs, and accent palette
  (BRAND_GLYPHS / accent tokens consumed by Brand.tsx + page chrome).
- client/components/brand/Brand.tsx — BrandMark (img with the
  logo asset), BrandWordmark, BrandGlyph, and a small BrandStack
  used by the layout chrome.
- client/public/brand/{logo,pwa-192,pwa-512,apple-touch-icon}.png —
  replaces the inline img-cut references in README/HISTORY with a
  canonical set; PWA manifest + favicon now point here.

This is commit 1 of 9 splitting the working-tree brand refresh into
per-layer commits. Each commit is independently buildable; the order
matches the dependency direction (lib → component → chrome → UI
primitives → page consumers → admin/dialog consumers → docs).
2026-07-05 17:18:04 -05:00
null 1f57b22ff8 feat(settings): configurable reminder send-time (plan Tier 5)
The daily reminder job ran at a hardcoded 6 AM. Make the hour configurable:
- dailyWorker: resolveReminderHour() reads the global 'reminder_hour' setting
  (clamp 0–23, default 6) as the single source for both the cron expression and
  the next-run display; the task is stored so rescheduleDailyWorker() can restart
  it live (defensively wrapped so a bad value can't take the worker down).
- notifications admin GET/PUT expose reminder_hour; PUT clamps, persists, and
  live-reschedules the worker.
- Admin Email Notifications card: a "Reminder send time" select (account-wide).

It's a global setting by design — there's one shared daily job, so a per-user
hour would require an hourly-worker refactor (out of scope) and would otherwise
be a dead setting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:10:25 -05:00
null 0e6f04c7bc feat(settings): sign out of other devices (plan Tier 7)
- New POST /auth/logout-others keeps the current session and invalidates all
  others (invalidateOtherSessions with the current session id), writes a
  logout.others audit entry, and returns the count ended.
- api.logoutOthers(); a "Sign out of other devices" button in the Login History
  dialog, guarded by an AlertDialog confirmation. On success: toast with the
  count and refetch the history so it collapses to just this device.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:05:59 -05:00
null 7e5e012434 feat(settings): default landing page (plan Tier 6)
- New per-user 'default_landing_page' setting (tracker default / calendar /
  summary / spending), registered in the whitelist + defaults.
- Implemented as a post-login redirect (keeps /=Tracker so the Home link and
  direct nav are unaffected). Precedence: admin → deep-link they were bounced
  from (state.from) → default landing → /. Resolved by fetching settings in the
  login success path, with a safe / fallback so a fetch failure never blocks login.
- Settings: Select in the Regional section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:03:01 -05:00
null 01030fe7d0 feat(settings): configurable "Due soon" window (plan Tier 4)
- statusService: resolveDueSoonDays (clamp 0–31, default 3) replaces the
  hardcoded 3-day threshold in calculateStatus; threaded via options.dueSoonDays
  through rowOptions in trackerService (×2) and routes/calendar.
- New per-user 'due_soon_days' setting registered in the whitelist + defaults.
- Settings: numeric "Due soon window" row next to Grace period, with min/max +
  clamp guard. Tooltips added to Grace period, Due-soon, and Price-change
  sensitivity explaining each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:00:32 -05:00
null 0f24016bcf feat(settings): first day of week (plan Tier 3)
- New per-user 'week_start' setting (0=Sun default / 1=Mon), registered in
  USER_SETTING_KEYS + USER_SETTING_DEFAULTS.
- CalendarPage reads it and offsets the month grid: leading-blank count
  (getDay()-weekStart+7)%7 and a rotated weekday header. Purely presentational.
- Settings: Sun/Mon Select in the new "Regional" section. Also aligns the
  grace_period_days client default (3→5) with the server seed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:57:35 -05:00
null 0fa80a77ef feat(settings): make Currency + Date-format real (plan Tier 2)
The Currency/Date-format dropdowns were decorative (nothing read them). Wire
them into the formatters, display-only (single active currency, no conversion):
- money.ts: activeCurrency/activeLocale read synchronously from localStorage at
  load (correct first paint), setMoneyFormat() write-through; formatUSD/
  formatUSDWhole/formatCentsUSD honor them. Default USD/en-US is byte-identical,
  so money.test + the probe stay green.
- utils.ts: fmtDate honors an activeDateFormat (MM/DD/YYYY | DD/MM/YYYY |
  YYYY-MM-DD) with setDateFormat() write-through.
- FormatSync (mounted in Layout) reconciles the server settings → localStorage/
  module and re-renders once only on a genuine cross-device divergence.
- Settings selects write through on change, currency list expanded (AUD/CHF/JPY/
  INR), "Regional" section, tooltips clarifying display-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:54:19 -05:00
null e2d4a9f703 feat(settings): theme "System" + reduce-motion (plan Tier 1)
- ThemeContext gains a 'system' theme that follows prefers-color-scheme and
  updates live when the OS flips; exposes resolvedTheme (consumed by Sonner) and
  keeps the <meta theme-color> in sync. Pre-bundle inline script in index.html
  prevents a light/dark flash. Both theme selectors (Settings ThemeCards +
  theme-toggle dropdown) gain the System option.
- New MotionPreferenceContext mounts framer-motion <MotionConfig>; a Reduce-motion
  toggle (localStorage device pref) forces reducedMotion="always" (else "user",
  which still respects the OS).
- Add an accessible SettingHelp tooltip (focusable button + aria-label) wrapped by
  a page-level TooltipProvider; used for the System + Reduce-motion explanations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:50:37 -05:00
null 1d73c8f13e fix(settings): invalidate ['settings'] cache on save so tracker toggles apply
Tier B moved the tracker's display settings onto a React Query cache
(staleTime 5m). SettingsPage saves via its own path and didn't touch that
cache, so toggling a region off (e.g. the Overdue Command Center) could leave
the tracker showing the stale value for up to 5 minutes. Invalidate the
['settings'] query after a successful save so returning to the tracker refetches
and the toggle takes effect immediately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:06:57 -05:00
null 21651b3f67 feat(tracker): a11y live regions + keyboard-shortcut hint (plan D/C5)
- aria-label on the pencil "Edit bill" buttons (were title-only), matching the
  autopay buttons' title+aria-label pattern.
- role="status" aria-live on the filter result count so filtering announces
  "N of M shown"; a visually-hidden live region announces drag/keyboard reorder
  ("Moved <bill> to position N of M").
- Header "Keyboard shortcuts" dropdown documenting the (previously invisible)
  j/k/arrows/Enter/p/Esc row shortcuts + ⌘K palette.

Deferred as low-value/optional: extra display toggles (C4) and a due-this-week
chip (C5) — avoided header/option clutter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:00:16 -05:00
null 0abe861928 feat(tracker): multi-select bulk pay/skip (plan C3)
- Add a leading selection checkbox to each desktop + mobile row, threaded from
  the page through TrackerBucket via an optional RowSelection prop (renders only
  when the page supplies a toggle handler).
- Page owns the selection Set; a sticky bottom action bar appears when ≥1 bill is
  checked: "N selected · Pay · Skip · Clear".
- Pay selected reuses the bulkPay item shape + Undo from "Pay all due".
- Skip selected fans out saveBillMonthlyState (no bulk endpoint; skip has no money
  effect) with Promise.allSettled, an aggregated "n skipped / m failed" toast, and
  an Undo that un-skips. Selection clears on month change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:56:34 -05:00
null 88874f3f3d feat(tracker): export/print the month + calendar link (plan C1/C2)
- Header "Export" menu: Export CSV (via the existing /api/export?from&to date
  range for the viewed month) and Print (window.print()). Extract the shared
  downloadFile blob helper to lib/download.ts (reused by DownloadMyDataSection).
- Add api.exportRangeUrl(from, to, fmt).
- Tracker-scoped @media print CSS: hide interactive chrome (.tracker-print-hide),
  keep the month title + summary + buckets, avoid splitting a row across pages.
- Header "Calendar" button deep-links to /calendar?year&month; CalendarPage now
  reads those params as its initial month (falls back to today).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:49:09 -05:00
null 313aafcb79 refactor(tracker): move settings/simplefin onto React Query + defer search (plan B)
- B1: replace the two raw useEffect/api fetches (simplefinStatus, settings) with
  useSimplefinStatus() and useSettings() query hooks — cached/deduped like the
  rest of the app. Tracker settings now derive from the ['settings'] cache
  merged under module-level TRACKER_SETTING_DEFAULTS.
- B2: settings save is now useSaveSettings() — a useMutation with optimistic
  cache update + rollback on error, replacing the hand-rolled optimistic/refetch.
- B3: useDeferredValue on the search term feeding filteredRows so typing stays
  snappy on large months (matches BillsPage/SubscriptionsPage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:43:13 -05:00
null 03009e2a83 refactor(client): de-duplicate parseUtc + settingEnabled to lib/utils (plan A2/A3)
- Extract parseUtc (was copy-pasted in 4 files) and settingEnabled (3 copies,
  one named settingsBool) into client/lib/utils.ts as the single source.
- Replace all local copies with the shared import.
- Add tracker_show_safe_to_spend to the tracker's local settings defaults so it
  matches SettingsPage's list (behavior unchanged; aligns the two sources).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:38:12 -05:00
null 3973560e92 refactor(tracker): remove dead code and unreferenced files (plan A1)
- Delete unreachable in-file dialogs: MonthlyStateDialog block (setShowMbs
  never called) and inline PaymentModal in TrackerRow + MobileTrackerRow
  (setEditPayment only ever null; editing works via PaymentLedgerDialog).
- Delete fully-unreferenced files: tracker/EditableCell.tsx,
  ui/confirm-dialog.tsx, ui/separator.tsx (+ orphaned @radix-ui/react-separator).
- Simplify OverdueCommandCenter dead branch (early-returns null when empty).
- Correct stale roadmap/FUTURE docs: tracker keyboard nav is implemented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:35:13 -05:00
null db1be4b0bd fix(tracker): pin-Due sort uses reconciled status; add filtered-empty state
- Pin-upcoming sort ranked on raw row.status, so a bill paid-by-threshold
  but still carrying a stale 'late'/'missed' status was pinned to the top.
  Rank on rowEffectiveStatus (same source the overdue filter uses) so it
  sorts to the bottom instead.
- Filtering to zero results showed a blank content area (rows.length checks
  the unfiltered list). Add a "No bills match your filters" state with a
  Clear-filters action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:35:03 -05:00
null a6bc85b18c perf(ui): useDeferredValue on the Bills + Subscriptions search filters (Phase 2 stack leverage)
Both pages filter the whole list on every keystroke. The input now updates
instantly while the (deferred) filter+render lags behind — React 19's
useDeferredValue, which the app wasn't using anywhere. Typing stays snappy
on large lists; zero behavior change otherwise. typecheck/lint/build clean,
probe 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:16:21 -05:00
null 57e7ede670 refactor(ts): close the 4 client any's + document a best-effort catch (Phase 1b)
parseJsonSafe/upload-body → a structured ResponseBody type (typed error
envelope + index sig) instead of any, no cast churn; declineRecommendation
→ Recommendation; snowballPlans endpoint typed so the hook drops its (d: any).
Annotated the Spending settings-load catch as best-effort (Finding #8) —
cosmetic options fall back to defaults, no mount-time nag. Client 0 any's.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:13:32 -05:00
null 398caec29d fix(resilience): client fetch timeout + global-handler headersSent guard (Phase 1b)
Client: _fetch had no timeout — a hung request (server accepted the
connection but never responds) spun the UI forever with no error. Now
wrapped in AbortSignal.timeout(120s, generous enough for the slowest
legit import/sync/backup on a LAN) and both timeout and network failure
map to a clean ApiError (REQUEST_TIMEOUT / NETWORK_ERROR) so they surface
as a normal error toast instead of a freeze or raw 'Failed to fetch'.

Server: the global error handler now returns next(err) when res.headersSent,
so an error after a partial response can't throw 'cannot set headers'.

(The async-handler-hang gap is folded into the Express 5 upgrade in Phase
2.2, which forwards async rejections natively — no throwaway wrapper.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:10:00 -05:00
null 1df4b1bf87 refactor(ts): move User to @/types + type auth endpoints; add code to 500 (Track C/D)
Track C (auth): User lived in @/hooks/useAuth; moved it to @/types
(re-exported from useAuth for the 7 importers) and typed me/login/
totpChallenge/hasUsers, dropping those Admin/Login casts. Left the
single-consumer non-money casts (adminUsers, version/about/privacy/
health docs) and the optimistic-UI rewrite as deliberate low-value/
high-churn stops.

Track D: global 500 handler now emits code:'INTERNAL_ERROR' so an
unexpected error carries the same {error,code} field as structured 4xx.

typecheck/lint(0 err)/build/server-193 all green; probe 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:26:11 -05:00
null 65a477cab1 refactor(ts): type snowball projection + settings responses (Track C)
Promoted SnowballProjection (+details) and SnowballSettings into @/types;
wired snowball (Bill[]), snowballSettings/saveSnowballSettings, and
snowballProjection with get<T>/_fetch<T>. Removed the projection/settings/
bills casts. Plan-lifecycle SnowballPlan casts kept (component-owned type).
typecheck/lint/build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:16:03 -05:00
null 6bb8c63d39 refactor(ts): type the subscriptions API responses (Track C)
Promoted SubscriptionsPage's Subscription/SubData/Recommendation/
CatalogMatch/SubTx (+ deps) into @/types and wired subscriptions/
recommendations/transaction-matches/create/match-bill with get<T>/
post<T>. Removed the (r: any) in useSubscriptionRecommendations, the 5
page casts, and an unused CatalogMatch import. typecheck/lint/build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:11:36 -05:00
null 8265b4a97f refactor(ts): type the spending API responses (Track C)
Promoted SpendingPage's local shapes into @/types (SpendingCategoryEntry,
SpendingSummary, SpendingTransaction(s), SpendingIncome*, SpendingRule,
CategoryGroup, CopyBudgetsResponse) and wired the /spending/* + category-
groups endpoints with get<T>/post<T>. Hooks return typed data; 6 casts
removed; 2 orphaned type aliases dropped. typecheck/lint/build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:06:40 -05:00
null b267599ba5 refactor(ts): type the bank-ledger API responses (Track C)
Promoted BankTransactionsPage's local Tx/Account/Ledger/... interfaces
into @/types (BankLedgerTransaction extends BankTransaction so amounts
stay branded Cents) and wired bankTransactionsLedger/match/unmatch/
ignore/unignore/apply-merchant-match/auto-categorize with get<T>/post<T>.
useBankLedger data is now typed; all 9 as {...} casts in the page removed.
Dropped a dead Cents import. typecheck/lint/build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:02:28 -05:00
null dd5bf92fc1 fix(money): manual payment path flags suspected dupes (409) w/o losing legit ones (Track A)
The deliberate manual POST /payments had no dedupe guard (double-submit
made a second payment), but silently deduping it like the auto paths
would lose a legitimately-identical same-day payment — a money bug. So
the server returns 409 DUPLICATE_SUSPECTED (existing payment attached),
and a shared client helper (client/lib/paymentActions.ts, used by the
tracker inline cell, partial-payment dialog, and bill modal) turns it
into an 'add anyway?' toast that resends with allow_duplicate. Automated
one-click paths keep deduping silently (a repeat there is a retry).

Dropped a now-unused api import in PaymentLedgerDialog (Track E sweep).
Server suite 188 green; typecheck/lint/build clean; probe 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 11:51:06 -05:00
null 8229d21ab8 refactor(ts): SubscriptionsPage → TypeScript (final .jsx page)
The last .jsx page — 0 remain under client/. Recurring-services view
typed: Subscription/SubSummary/Recommendation/CatalogMatch/SubTx local
types, useOptimistic<Subscription[]> typed reducer, virtualizer
FlatItem discriminated union, drag/move-controls via DragProps/
MoveControls, setQueryData<T> optimistic caches, local branded fmt.
Fixed daysUntil date subtraction (getTime), guarded optional .length
chains, cast api.* results at boundaries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:48:06 -05:00
null aa9c357fdf refactor(ts): BillsPage → TypeScript
Bill list/reorder/history-visibility page typed: Bill[] from useBills,
BillFilters/DisplayPrefs/ModalState/HistoryRange local types, drag +
move-controls typed via DragProps/MoveControls, makeBillDraft(...) as
Partial<Bill> at the modal call sites, setQueryData<Bill[]> for
optimistic list edits, errMessage(unknown) for strict catches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:35:48 -05:00
null 3b1e7e4da9 refactor(ts): SpendingPage → TypeScript
YNAB-style spending view typed: CatEntry (category_id number|'other'|
null) with a RealCatEntry narrowing for the budget-indexed paths,
SpendingSummary/SpendingTx/IncomeTx/SpendingRule/CategoryGroup local
types, budgets as Record<string,number|null>, React Query
setQueryData<T> for optimistic budget/categorize edits, cast-at-boundary
for api.* results. Dropped a pre-existing dead 'total' state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:29:39 -05:00
null d49a2ae4dc refactor(ts): BankTransactionsPage → TypeScript
Cents-based bank ledger typed end-to-end: local Tx interface (amount:
Cents, index sig for BankTransaction compat), Ledger/Account/Summary
types, React Query setQueryData<Ledger> for optimistic row patches,
cast-at-boundary for api.* results, Promise.allSettled bulk handlers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 23:20:47 -05:00
null 18eaec04e2 refactor(ts): SnowballPage → TypeScript (debt attack order + projections + plans)
Typed Bill[]/Projection/SnowballSettings; reused SnowballPlan and exported
ActivePlan from the plan components; React Query cache setters typed.
2026-07-04 23:12:57 -05:00
null d559f4b00b refactor(ts): TrackerPage → TypeScript (the home page)
The tracker: URL-driven month/filters/sort, React Query data (TrackerResponse),
bank-projection banner, summary cards, safe-to-spend, overdue center, drift
insights, drag-reorder buckets, and all row/modal orchestration. TrackerSummary.trend
is typed unknown in @/types so it's cast at the TrendCard boundary.
2026-07-04 23:06:46 -05:00
null dbf3c42d62 refactor(ts): CalendarPage → TypeScript (money map + grid + cashflow + snowball glance) 2026-07-04 22:58:34 -05:00
null 82bd13036a refactor(ts): ProfilePage → TypeScript; widen AuthContext setUser to accept updaters
ProfilePage (profile summary + login history + edit + notifications + push +
2FA + privacy) — exports asSettings/NotificationPreferences/PushNotifications
now typed, so SettingsPage picks up real types. Widened useAuth setUser to
Dispatch<SetStateAction<User|null|undefined>> to allow the updater form.
2026-07-04 22:53:51 -05:00
null 818df6d410 refactor(ts): AnalyticsPage → TypeScript (SVG charts + forecast); drop unused Skeleton import 2026-07-04 22:48:28 -05:00
null f15046bce3 refactor(ts): CategoriesPage → TypeScript (category list + drag-reorder + expand) 2026-07-04 22:44:39 -05:00
null 8c9702adbe refactor(ts): SummaryPage → TypeScript (React Query summary + drag-reorder expenses) 2026-07-04 22:41:25 -05:00
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