chore(autonomous-session): consolidate uncommitted work from prior session

Bundles the prior autonomous-session output that was sitting unstaged:

- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
  never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
  after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
  redirects (ocr to ai, reports to dashboard, invitations to users),
  docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
  flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
  let-reassign), set-state-in-effect disables in CountryFlag and
  UploadForSigning preview-bytes effect, unused 'confirm' destructures in
  interest contract + reservation tabs, unescaped apostrophe in test-template
  card copy
This commit is contained in:
2026-05-23 00:52:59 +02:00
parent 43719b49e9
commit 221ae5784e
749 changed files with 7440 additions and 3118 deletions

View File

@@ -1,16 +1,16 @@
/**
* Phase 6 IMAP bounce poller.
* Phase 6 - IMAP bounce poller.
*
* Polls the configured IMAP inbox for delivery-status notifications, runs
* each through `parseBounce()`, and matches the original recipient against
* a recent `document_sends` row. When matched, updates the send row's
* bounce_* columns and fires an `email_bounced` notification to the rep
* who originated the send (hard/soft only out-of-office is logged but
* who originated the send (hard/soft only - out-of-office is logged but
* not surfaced as an actionable alert).
*
* The job runs globally (no per-port context). IMAP creds are read from
* environment variables (`IMAP_HOST` / `IMAP_PORT` / `IMAP_USER` /
* `IMAP_PASS`) when any is missing the poll is a no-op so the worker
* `IMAP_PASS`) - when any is missing the poll is a no-op so the worker
* boots happily in dev. Run cadence is set in `src/lib/queue/scheduler.ts`
* (every 15 minutes).
*
@@ -31,7 +31,7 @@ import { createNotification } from '@/lib/services/notifications.service';
const STATE_KEY = 'bounce_poller_state';
const FIRST_RUN_LOOKBACK_HOURS = 24;
/** How far back to look for the originating document_sends row. Any send
* whose bounce arrives after this window won't be matched the SMTP
* whose bounce arrives after this window won't be matched - the SMTP
* protocol guarantees NDRs typically arrive within minutes / hours, so
* 7 days is generous. */
const SEND_MATCH_WINDOW_DAYS = 7;
@@ -81,12 +81,12 @@ export async function processImapBouncePoll(): Promise<void> {
// copy-paste with the visual spaces preserved still works.
const pass = process.env.IMAP_PASS?.replace(/\s+/g, '');
if (!host || !portStr || !user || !pass) {
logger.debug('IMAP bounce poll skipped IMAP_* env not configured');
logger.debug('IMAP bounce poll skipped - IMAP_* env not configured');
return;
}
const port = Number.parseInt(portStr, 10);
if (!Number.isFinite(port)) {
logger.warn({ portStr }, 'IMAP bounce poll skipped IMAP_PORT not numeric');
logger.warn({ portStr }, 'IMAP bounce poll skipped - IMAP_PORT not numeric');
return;
}
@@ -141,7 +141,7 @@ export async function processImapBouncePoll(): Promise<void> {
const lookback = new Date(Date.now() - SEND_MATCH_WINDOW_DAYS * 86_400_000);
// Most-recent matching send to this recipient; the recipient
// may have been sent multiple files in the same window the
// may have been sent multiple files in the same window - the
// bounce always refers to the latest.
const candidates = await db
.select()
@@ -174,7 +174,7 @@ export async function processImapBouncePoll(): Promise<void> {
.where(eq(documentSends.id, target.id));
matched++;
// Skip OOO informational, not actionable. Hard/soft notify
// Skip OOO - informational, not actionable. Hard/soft notify
// the original sender so they can re-send or escalate.
if (
target.sentByUserId &&
@@ -185,7 +185,7 @@ export async function processImapBouncePoll(): Promise<void> {
userId: target.sentByUserId,
type: 'email_bounced',
title: 'Email bounced',
description: `Your email to ${parsed.originalRecipient} bounced ${parsed.reason}`,
description: `Your email to ${parsed.originalRecipient} bounced - ${parsed.reason}`,
link: target.interestId ? `/interests/${target.interestId}` : undefined,
entityType: 'document_send',
entityId: target.id,
@@ -207,7 +207,7 @@ export async function processImapBouncePoll(): Promise<void> {
try {
await client.logout();
} catch {
// Logout failures are non-fatal the connection will be torn down
// Logout failures are non-fatal - the connection will be torn down
// by the timeout settings above.
}
}