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

@@ -21,7 +21,7 @@
*/
// Note: this module is imported by client components (e.g. the EOI
// upload dialog), so we deliberately avoid `@/lib/logger` that file
// upload dialog), so we deliberately avoid `@/lib/logger` - that file
// pulls in `request-context.ts` which uses `node:async_hooks`, an
// import Turbopack can't include in a client bundle. `console.warn`
// works on both client and server and is more than enough for a

View File

@@ -21,7 +21,7 @@
* - `yacht.*` → write to the interest's linked yacht (if present)
*
* Not in the catalog (intentional):
* - Polymorphic ownership columns (yacht.current_owner_*) needs
* - Polymorphic ownership columns (yacht.current_owner_*) - needs
* ownership-flow service, not a flat write.
* - Anything on companies (M43 first pass scopes to client+yacht).
*/
@@ -33,7 +33,7 @@ export interface BindableField {
path: string;
/** Human label shown in the admin picker + the field-history popover. */
label: string;
/** Entity bucket drives the picker grouping + write routing. */
/** Entity bucket - drives the picker grouping + write routing. */
entity: 'interest' | 'client' | 'client_address' | 'yacht';
/** Column on the entity's row. */
column: string;
@@ -43,11 +43,11 @@ export interface BindableField {
/**
* Path naming convention:
* - `client.<column>` top-level clients row
* - `client.primaryEmail|primaryPhone` synthesised over client_contacts
* - `client.address.<column>` the primary client_addresses row
* - `yacht.<column>` interest's linked yachts row
* - `interest.<column>` interests row resolved from the token
* - `client.<column>` - top-level clients row
* - `client.primaryEmail|primaryPhone` - synthesised over client_contacts
* - `client.address.<column>` - the primary client_addresses row
* - `yacht.<column>` - interest's linked yachts row
* - `interest.<column>` - interests row resolved from the token
*
* `interest_field_history.field_path` stores these strings verbatim, so the
* detail-page history popover can `WHERE field_path = ?` to surface the
@@ -186,7 +186,7 @@ export const BINDABLE_PATHS: readonly string[] = BINDABLE_FIELDS.map((f) => f.pa
/**
* Grouped form for the admin picker. Returns entries in the order entities
* should appear in the dropdown (Client, then Yacht, then Interest, then
* address most-frequent first).
* address - most-frequent first).
*/
export function bindableFieldsByEntity(): Array<{
entity: BindableField['entity'];

View File

@@ -1,9 +1,9 @@
/**
* Phase 7 PDF template editor field-map type definitions.
* Phase 7 - PDF template editor field-map type definitions.
*
* The editor (deferred to 7.1/7.2) operates on the existing
* `document_templates.overlay_positions` JSONB column with the extended
* shape below. No schema migration is required JSONB already accepts
* shape below. No schema migration is required - JSONB already accepts
* any shape; the only contract is the Zod validator on the API boundary.
*
* Coordinates are stored as percent of page width/height (0..1) so
@@ -19,13 +19,13 @@ export const fieldMapEntrySchema = z.object({
token: z.string().min(1).max(100),
/** 1-indexed page number. */
page: z.number().int().min(1),
/** Percent of page width top-left corner of the marker. */
/** Percent of page width - top-left corner of the marker. */
x: z.number().min(0).max(1),
/** Percent of page height top-left corner of the marker. */
/** Percent of page height - top-left corner of the marker. */
y: z.number().min(0).max(1),
/** Percent width defaults to 0.15 if omitted by an older editor. */
/** Percent width - defaults to 0.15 if omitted by an older editor. */
w: z.number().min(0).max(1).optional(),
/** Percent height defaults to 0.04. */
/** Percent height - defaults to 0.04. */
h: z.number().min(0).max(1).optional(),
/** Optional explicit font size; otherwise auto-fit to the box. */
fontSize: z.number().int().min(6).max(72).optional(),

View File

@@ -98,7 +98,7 @@ export const VALID_MERGE_TOKENS: ReadonlySet<string> = new Set(
* machine identifier, not the display label). Field names are validated
* at definition time as `[a-z][a-z0-9_]*` so this regex is the matching
* recogniser. Tokens that match this shape bypass the static
* `VALID_MERGE_TOKENS` check the resolver fetches the actual
* `VALID_MERGE_TOKENS` check - the resolver fetches the actual
* definitions per port at expand time.
*/
export const CUSTOM_MERGE_TOKEN_RE = /^\{\{custom\.[a-z][a-z0-9_]*\}\}$/;