Phase 5: alert subscriptions UI + multiple watchlists + queue fixes
CI / Test & Type-Check (push) Canceled after 0s

UI:
- /alerts page: event history with acknowledge, subscription create/manage with toggle
- /admin/smtp: SMTP config form (host, port, auth, test)
- Watchlist sidebar: dropdown selector for multiple watchlists, create/delete
- Sidebar: alerts count badge, SMTP link under admin
- Mobile tab nav: alerts tab added
- Client trpc.ts: all new API methods + types

Backend:
- watchlists.listByWatchlist procedure + listSymbolsByWatchlist repo fn
- yfinance min-interval 1500->2000ms to reduce Edge 429s
- Fixed e.date.slice error in yfinance-adjustments with typeof guard
- Removed defunct BITF from watchlist+queue
- Cleared 83 failed + 12 backoff queue jobs

Docs:
- FUNCTIONAL_DESIGN.md: alerts + multiple watchlists + SMTP documented
- TECH_DESIGN.md: new modules, tRPC procs, routes updated
This commit is contained in:
Investor Flow Build
2026-07-23 20:51:47 -04:00
parent f71987c119
commit 5b9f770aa4
23 changed files with 1567 additions and 146 deletions
@@ -19,8 +19,8 @@ export interface PriceAdjustment {
ratio: number;
}
type SplitEvent = { date: string; numerator: number; denominator: number; splitRatio?: string };
type DividendEvent = { date: string; amount: number };
type SplitEvent = { date: string | number; numerator: number; denominator: number; splitRatio?: string };
type DividendEvent = { date: string | number; amount: number };
/**
* Parse a yahoo-finance2 v3 chart response's `events` object into a flat list of
@@ -64,21 +64,23 @@ function emitFrom(
if (type === "split") {
const e = entry as SplitEvent;
if (!e.date) continue;
const dateStr = typeof e.date === 'string' ? e.date : String(e.date);
const numerator = typeof e.numerator === "number" ? e.numerator : 1;
const denominator = typeof e.denominator === "number" && e.denominator !== 0 ? e.denominator : 1;
out.push({
symbol,
exDate: e.date.slice(0, 10),
exDate: dateStr.slice(0, 10),
type: "split",
ratio: numerator / denominator,
});
} else {
const e = entry as DividendEvent;
if (!e.date) continue;
const dateStr = typeof e.date === 'string' ? e.date : String(e.date);
const amount = typeof e.amount === "number" ? e.amount : 0;
out.push({
symbol,
exDate: e.date.slice(0, 10),
exDate: dateStr.slice(0, 10),
type: "dividend",
ratio: amount,
});