Correctness:
- Resume AI now releases ANY assignment (not just the caller's own).
A handoff-assigned thread previously stayed assigned after resume, so
the eligibility gate silently kept the bot muted — Resume was a no-op.
- GET /api/ai/usage: guard days >= 1 (Number(null)===0 is finite, so the
30-day default was unreachable and the window collapsed to 1 day).
- GET /api/ai/usage: align the query cutoff to the start of the oldest
charted local day so rows in the oldest partial day no longer count in
totals while missing from the daily series (totals now match the bars).
- ai-thread-banner: cache account AI-status per accountId and never cache
a transient fetch failure — fixes cross-account leak and the banner
staying hidden all session after one failed /api/ai/config call.
- /api/ai/draft: construct the service-role client inside a guard and
fire-and-forget the usage log, so a missing service-role key (or any
log failure) can't 500 an already-generated, already-billed draft.
Efficiency:
- Don't await usage logging on the reply hot path (auto-reply + draft);
logAiUsage swallows its own errors, so fire-and-forget is safe and
removes a DB round-trip from the customer-facing send.
Reuse / consistency:
- usage route reuses date-utils (daysAgoStart/lastNDayKeys/localDayKey)
and now buckets by LOCAL day like every other dashboard chart (was UTC).
- extract formatCompactNumber (shared with formatCurrencyShort); drop the
hand-rolled month table in favour of date-fns; extract
fetchAccountMembers/memberLabel and use them in the AI settings picker.
Resume still resets the per-conversation reply count (a deliberate,
manual, rate-limited re-engagement — documented in the handler).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
deals.currency has no DB CHECK (only accounts.default_currency does),
so legacy rows, imports, or hand-edited data can hold a structurally
invalid code (e.g. "United States"). Intl.NumberFormat throws a
RangeError on such codes, which would crash any deal-card / contact
render that passes deal.currency straight through.
Guard formatCurrency with a try/catch that falls back to "CODE 1,234"
so a bad value degrades gracefully instead of taking down the view.
Add currency.test.ts locking in the defaults, the no-throw contract,
and formatCurrencyShort's symbol/fallback behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wacrm hardcoded USD across every deal-value formatter, the new-deal
form, and automation-created deals, making deal tracking unhelpful for
non-US self-hosted users. Add a per-account default currency.
- Migration 021: accounts.default_currency TEXT NOT NULL DEFAULT 'USD'
with a 3-letter-code CHECK. The existing admin-only accounts_update
RLS policy already gates writes.
- New src/lib/currency.ts: single source of truth (CURRENCIES list,
formatCurrency, formatCurrencyShort), replacing ~6 duplicated
USD-hardcoded helpers.
- useAuth exposes defaultCurrency (joined from accounts), falling back
to USD while loading / on pre-021 schemas.
- Settings → Deals tab to pick the currency (admin-only, read-only for
others to match RLS).
- Deal form defaults new deals to the account currency and sources its
dropdown from CURRENCIES; existing deals keep their saved currency.
- Pipeline board/analytics, dashboard "Open Deals Value", and the
pipeline donut format totals in the account currency. Individual
deal cards still show each deal's own currency.
- Automation create_deal sets the new deal's currency from the
account default instead of the static DB default.
One currency per account (no FX conversion) per the issue's
recommended first pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>