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

@@ -3,7 +3,7 @@
*
* After a smart-archive releases a berth back to available, the sales
* team should be told who else expressed interest in that berth so they
* can follow up. This is informational only no automatic stage
* can follow up. This is informational only - no automatic stage
* transitions on the next interests.
*
* Recipients = port users whose role grants `interests.change_stage`
@@ -24,7 +24,7 @@ export interface BerthReleaseNotificationInput {
mooringNumber: string;
archivedClientName: string;
/** ids of the next-in-line interests on this berth (with the metadata
* needed for the notification body comes from the dossier). */
* needed for the notification body - comes from the dossier). */
nextInLineInterests: Array<{
interestId: string;
clientName: string | null;
@@ -62,7 +62,7 @@ export async function notifyNextInLine(input: BerthReleaseNotificationInput): Pr
const previewLines = input.nextInLineInterests.slice(0, 5).map((i) => {
const stageLabel =
STAGE_LABELS[i.pipelineStage as PipelineStage] ?? i.pipelineStage.replace(/_/g, ' ');
return `${i.clientName ?? '(unknown)'} ${stageLabel}`;
return `${i.clientName ?? '(unknown)'} - ${stageLabel}`;
});
const more =
input.nextInLineInterests.length > 5
@@ -70,7 +70,7 @@ export async function notifyNextInLine(input: BerthReleaseNotificationInput): Pr
: '';
const description = input.nextInLineInterests.length
? `${previewLines.join('\n')}${more}`
: 'No prior interests recorded this berth is fully available again.';
: 'No prior interests recorded - this berth is fully available again.';
// 3. Fire-and-forget per recipient. dedupeKey collapses duplicate
// fires within the cooldown window if multiple events queue up.
@@ -79,7 +79,7 @@ export async function notifyNextInLine(input: BerthReleaseNotificationInput): Pr
portId: input.portId,
userId,
type: 'berth_released',
title: `Berth ${input.mooringNumber} released ${input.archivedClientName} archived`,
title: `Berth ${input.mooringNumber} released - ${input.archivedClientName} archived`,
description,
link: `/berths/${input.berthId}`,
entityType: 'berth',