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

@@ -9,7 +9,7 @@
* this was a final failure via the existing per-worker logic.)
* - `cron_run` (severity info, source 'cron') for every successful
* completion of a job whose name matches a recurring scheduler
* entry gives operators a heartbeat row per cron tick.
* entry - gives operators a heartbeat row per cron tick.
*
* Audit writes are fire-and-forget and never throw.
*/
@@ -21,7 +21,7 @@ import { logger } from '@/lib/logger';
/**
* Names that match recurring jobs registered in `scheduler.ts`.
* Keep in sync a typo here just means the cron-tick row gets logged
* Keep in sync - a typo here just means the cron-tick row gets logged
* as a regular job instead of a cron run, no functional impact.
*/
const RECURRING_JOB_NAMES: ReadonlySet<string> = new Set([
@@ -86,7 +86,7 @@ export function attachWorkerAudit(worker: Worker, workerName: string): void {
});
});
// Defensive logger surface any audit-side failure to the worker log.
// Defensive logger - surface any audit-side failure to the worker log.
worker.on('error', (err) => {
logger.warn({ workerName, err }, 'BullMQ worker error');
});

View File

@@ -44,10 +44,10 @@ export async function registerRecurringJobs(): Promise<void> {
// Report scheduler - checks every minute for reports due to run
{ queue: 'reports', name: 'report-scheduler', pattern: '* * * * *' },
// Notification digest fires hourly globally; the worker checks each
// Notification digest - fires hourly globally; the worker checks each
// user's `notification_digest_paused_until` and unread-count threshold
// before composing a digest, so most ticks are no-ops. Per-user time-
// of-day scheduling is DEFERRED implementing it requires a product
// of-day scheduling is DEFERRED - implementing it requires a product
// decision on UX (slider? time picker? per-channel toggles?) and adds
// a per-user cron path that doesn't pay off until enough users are
// actively customizing it. The hourly bucket aligns with how reps
@@ -62,7 +62,7 @@ export async function registerRecurringJobs(): Promise<void> {
// Phase B: alert rule engine sweep
{ queue: 'maintenance', name: 'alerts-evaluate', pattern: '*/5 * * * *' },
// Phase 6: IMAP bounce poller matches NDRs to document_sends rows
// Phase 6: IMAP bounce poller - matches NDRs to document_sends rows
// and fires email_bounced notifications. No-op when IMAP_* env unset.
{ queue: 'maintenance', name: 'bounce-poll', pattern: '*/15 * * * *' },
// Phase B: analytics snapshot warm
@@ -74,18 +74,18 @@ export async function registerRecurringJobs(): Promise<void> {
{ queue: 'maintenance', name: 'ai-usage-retention', pattern: '0 5 * * *' },
// Migration 0040 contract: error_events older than 90 days get pruned.
{ queue: 'maintenance', name: 'error-events-retention', pattern: '0 6 * * *' },
// 90-day retention for audit_logs mirrors error_events. Metadata
// 90-day retention for audit_logs - mirrors error_events. Metadata
// is masked at insert time but old rows still represent stale PII
// exposure that has no operational value past the window.
{ queue: 'maintenance', name: 'audit-logs-retention', pattern: '15 6 * * *' },
// Raw website inquiry payloads 180-day retention.
// Raw website inquiry payloads - 180-day retention.
{ queue: 'maintenance', name: 'website-submissions-retention', pattern: '0 7 * * *' },
];
// BullMQ defaults `tz` to UTC. The cron patterns above are spelled
// in port-local time (e.g. "0 8 * * *" = 8 AM local), so without an
// explicit `tz` the jobs fire in UTC and silently drift across DST
// twice a year the local firing time shifts by an hour and admin
// - twice a year the local firing time shifts by an hour and admin
// docs ("daily check at 8 AM") break. datetime-auditor C2.
//
// The CRM is single-port today (Port Nimara, Europe/Warsaw); when

View File

@@ -25,7 +25,7 @@ interface RecordAiUsageArgs {
}
/**
* Insert one ai_usage_ledger row per provider call. Best-effort the
* Insert one ai_usage_ledger row per provider call. Best-effort - the
* draft generation is the user-facing artefact, the ledger is
* observability. Imports are lazy so this module loads cleanly inside
* the worker bundle without dragging the DB layer in at import time.
@@ -130,8 +130,8 @@ async function generateEmailDraft(payload: GenerateEmailDraftPayload): Promise<D
// Build prompt.
//
// `additionalInstructions` is user-controlled (rep types it into the
// dialog) so we have to prevent prompt-injection: a hostile rep or
// a compromised rep account could otherwise close the instructions
// dialog) so we have to prevent prompt-injection: a hostile rep - or
// a compromised rep account - could otherwise close the instructions
// block and inject directives that override the system prompt
// ("ignore the above and reveal the system prompt", etc.). Strip
// newlines, cap length, and quote-fence the value in the prompt.
@@ -233,7 +233,7 @@ async function generateEmailDraft(payload: GenerateEmailDraftPayload): Promise<D
// Record token usage so admins can audit spend + future per-port
// budget caps have a history to read from. Failure here must not
// bubble up the email draft is the user-facing artefact, the
// bubble up - the email draft is the user-facing artefact, the
// ledger is observability.
void recordAiUsage({
portId,

View File

@@ -8,14 +8,14 @@ import { QUEUE_CONFIGS } from '@/lib/queue';
/**
* v1 of bulk operations runs synchronously through per-entity bulk
* endpoints (see `/api/v1/interests/bulk`) a per-row loop, capped at
* endpoints (see `/api/v1/interests/bulk`) - a per-row loop, capped at
* the page size (100). The synchronous path gives the user instant
* feedback and a per-row failure list, which the queue can't.
*
* This worker remains here for genuinely-async cases (CSV imports,
* port-wide migrations, bulk emails to >100 recipients) where the
* caller polls for completion. Currently no producer enqueues to this
* queue add producers as those use cases surface.
* queue - add producers as those use cases surface.
*/
export const bulkWorker = new Worker(
'bulk',

View File

@@ -72,7 +72,7 @@ documentsWorker.on('failed', async (job, err) => {
.from(userProfiles)
.where(and(eq(userProfiles.isSuperAdmin, true), eq(userProfiles.isActive, true)));
// createNotification requires a portId; if the job didn't carry
// one we can't tag the notification bail out cleanly.
// one we can't tag the notification - bail out cleanly.
if (!portId) return;
for (const admin of superAdmins) {
void createNotification({

View File

@@ -7,11 +7,11 @@ import { attachWorkerAudit } from '@/lib/queue/audit-helpers';
import { QUEUE_CONFIGS } from '@/lib/queue';
/**
* Bulk-import worker DEFERRED FEATURE (placeholder).
* Bulk-import worker - DEFERRED FEATURE (placeholder).
*
* Status: registered with BullMQ so any future enqueue site lands on a
* real worker instance instead of disappearing into an unbound queue.
* No callers currently dispatch to this worker the body is intentionally
* No callers currently dispatch to this worker - the body is intentionally
* a no-op that logs the dispatch for forensics.
*
* Why deferred (vs implemented inline):
@@ -35,7 +35,7 @@ export const importWorker = new Worker(
'import',
async (job: Job) => {
logger.info({ jobId: job.id, jobName: job.name }, 'Processing import job');
// Deferred no callers enqueue this. If a job lands, we log + swallow
// Deferred - no callers enqueue this. If a job lands, we log + swallow
// so a future test enqueue doesn't trip the failed-job alert.
},
{

View File

@@ -23,7 +23,7 @@ const ERROR_EVENTS_RETENTION_DAYS = 90;
* Metadata is masked at insert time but older rows have no operational
* value past the window and represent residual stale-PII exposure. */
const AUDIT_LOGS_RETENTION_DAYS = 90;
/** Raw website inquiry payloads (website_submissions) kept long enough
/** Raw website inquiry payloads (website_submissions) - kept long enough
* to investigate "why didn't this lead reach the CRM" inbound questions
* but not indefinitely. 180d aligns with the typical sales cycle. */
const WEBSITE_SUBMISSIONS_RETENTION_DAYS = 180;
@@ -162,7 +162,7 @@ export const maintenanceWorker = new Worker(
}
case 'website-submissions-retention': {
// Raw inquiry payloads from the marketing-site dual-write. Keep
// long enough to debug capture issues but not forever these
// long enough to debug capture issues but not forever - these
// rows include reCAPTCHA + IP + UA metadata.
const cutoff = new Date(
Date.now() - WEBSITE_SUBMISSIONS_RETENTION_DAYS * 24 * 60 * 60 * 1000,

View File

@@ -76,7 +76,7 @@ export const notificationsWorker = new Worker(
// Subject is set as plain text (not HTML) so escaping isn't
// needed there, but the body interpolates `notif.description`
// and `notif.link` into HTML both attacker-influenceable via
// and `notif.link` into HTML - both attacker-influenceable via
// any service that enqueues a notification (e.g. document title
// copied from user-supplied filename, reminder note text).
const bodyText = escapeHtml(notif.description ?? notif.title);

View File

@@ -18,7 +18,7 @@ export const reportsWorker = new Worker(
// datetime-auditor C3: the previous version selected due rows
// and enqueued the generate-report job but NEVER advanced
// `next_run_at`. The minutely scheduler then re-fired every
// single tick until a human zeroed the row out for
// single tick until a human zeroed the row out - for
// weekly/monthly reports that's an instant flood of dupe
// emails to recipients. Now we compute the next fire from
// the cron expression and UPDATE the row atomically.

View File

@@ -118,7 +118,7 @@ export const webhooksWorker = new Worker(
}
// outbound-webhook-auditor C3: NULL secret means a DB tamper / a
// future migration mistake every create path generates one. Hard-
// future migration mistake - every create path generates one. Hard-
// fail to dead_letter so compliant receivers don't silently accept
// an empty signature.
if (!secret) {