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

@@ -40,7 +40,7 @@ export interface DocumensoTemplatePayload {
* (e.g. "A1-A3, B5") produced by `formatBerthRange()`. Single-
* berth output is byte-identical to the legacy primary-only path.
* 2026-05-14: collapsed the prior separate `Berth Range` form field
* into this one the Documenso template has only `Berth Number`,
* into this one - the Documenso template has only `Berth Number`,
* and Documenso silently dropped unknown formValues.
*/
'Berth Number': string;
@@ -95,7 +95,7 @@ export interface DocumensoPayloadOptions {
/** Redirect URL after signing. Defaults to the app URL. */
redirectUrl?: string;
/**
* PARALLEL (default) or SEQUENTIAL v2-only enforcement (v1 ignores).
* PARALLEL (default) or SEQUENTIAL - v2-only enforcement (v1 ignores).
* Set via per-port `documenso_signing_order` system_settings key.
*/
signingOrder?: 'PARALLEL' | 'SEQUENTIAL';
@@ -123,7 +123,7 @@ export interface DocumensoPayloadOptions {
// Empty string lets Documenso fall back to its own default post-sign
// landing page when the port admin hasn't configured a redirect URL.
// Never hardcode a tenant's marketing-site URL here that would route
// Never hardcode a tenant's marketing-site URL here - that would route
// every other port's signers to the wrong host.
const DEFAULT_REDIRECT_URL = '';
@@ -171,16 +171,16 @@ async function resolveCrmUser(
* Resolve the developer + approver name/email for the EOI signing trio.
*
* Priority chain per slot (highest → lowest):
* 1. Linked CRM user (`documenso_<role>_user_id`) recommended path
* 1. Linked CRM user (`documenso_<role>_user_id`) - recommended path
* because "the person on this slot" changes via a CRM admin re-link,
* not a Documenso template edit. The display name comes from
* `userProfiles.displayName`, the email from `user.email`.
* 2. Free-text overrides (`documenso_<role>_name` +
* `documenso_<role>_email`) for ports where the signer isn't a
* `documenso_<role>_email`) - for ports where the signer isn't a
* CRM-platform user (e.g. external counsel).
* 3. Legacy `eoi_signers` JSON blob kept for backward compat with
* 3. Legacy `eoi_signers` JSON blob - kept for backward compat with
* ports that haven't migrated to the registry-driven settings yet.
* 4. Empty strings let the Documenso template's stored values win.
* 4. Empty strings - let the Documenso template's stored values win.
*
* Either slot can resolve via a different tier than the other.
*/
@@ -250,7 +250,7 @@ export function buildDocumensoPayload(
/**
* Cached field name → ID map from the per-port `documenso_eoi_field_map`
* setting (populated by the admin "Sync from Documenso" button). When
* provided, the payload also emits `prefillFields` keyed by ID required
* provided, the payload also emits `prefillFields` keyed by ID - required
* by v2's /template/use. v1 instances ignore this field; v2 instances
* accept either prefillFields OR the legacy formValues shape.
*/
@@ -260,7 +260,7 @@ export function buildDocumensoPayload(
// 'ft' for legacy call sites that don't pass `dimensionUnit`; new code
// paths (generateAndSign + the drawer) always set it explicitly.
// Append the unit suffix to every dimension value so the rendered EOI
// reads "45 ft" / "13.7 m" rather than the bare number the original
// reads "45 ft" / "13.7 m" rather than the bare number - the original
// form field doesn't tell signers which unit they're looking at.
const dimUnit: 'ft' | 'm' = options.dimensionUnit ?? 'ft';
const yachtLength = dimUnit === 'ft' ? context.yacht?.lengthFt : context.yacht?.lengthM;
@@ -279,7 +279,7 @@ export function buildDocumensoPayload(
Length: withUnit(yachtLength),
Width: withUnit(yachtWidth),
Draft: withUnit(yachtDraft),
// formatBerthRange(['A1']) === 'A1' so single-berth EOIs render
// formatBerthRange(['A1']) === 'A1' - so single-berth EOIs render
// identically to the legacy primary-only flow; multi-berth EOIs
// now actually show the full range instead of just the primary
// mooring.
@@ -290,7 +290,7 @@ export function buildDocumensoPayload(
// v2's prefillFields-by-ID emission. Map every formValue entry through the
// cached field map; skip entries that aren't in the map (template doesn't
// have that field, which is fine Documenso silently drops unknown ones
// have that field, which is fine - Documenso silently drops unknown ones
// in v1 too).
const prefillFields = fieldMap
? Object.entries(formValues)
@@ -328,7 +328,7 @@ export function buildDocumensoPayload(
// Per Documenso v2's /template/use schema, `email` and `name` accept "" as
// a sentinel meaning "use the value baked into the template recipient".
// So when an admin leaves the developer/approver name/email blank in our
// admin settings, we pass "" rather than a hardcoded fallback Documenso
// admin settings, we pass "" rather than a hardcoded fallback - Documenso
// then takes the email/name set on the template itself. A non-empty
// admin value still wins (overrides the template at send time).
recipients: [