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

@@ -143,7 +143,7 @@ export async function deleteCriterion(id: string, portId: string, meta: AuditMet
* Whole-list reorder. Rewrites display_order to match the array index of
* each id, inside a single transaction so partial failure can't leave the
* port with a scrambled order. The ids array must cover exactly the port's
* current criteria any mismatch (extra or missing id) is rejected so the
* current criteria - any mismatch (extra or missing id) is rejected so the
* UI can't silently drop a criterion by sending a stale list.
*/
export async function reorderCriteria(
@@ -226,7 +226,7 @@ export interface QualificationRow {
* - `dimensions` → ticked when EITHER (a) the linked yacht has all
* three dims (length/width/draft) OR (b) the interest itself has
* desired-berth dims set. The "no yacht needed" case is the second
* branch a client buying a berth doesn't have to own a vessel,
* branch - a client buying a berth doesn't have to own a vessel,
* they just have to know the berth size they want.
*/
autoSatisfied: boolean;
@@ -234,7 +234,7 @@ export interface QualificationRow {
* Human-readable summary of WHY a criterion is auto-satisfied (e.g.
* "Desired: 60 × 25 × 6 ft"). Empty string when the criterion is not
* auto-satisfied OR when no derivation rule applies. Surfaced on the
* checklist row so the rep can see the evidence behind the tick the
* checklist row so the rep can see the evidence behind the tick - the
* "why is this checked?" question came up in UAT.
*/
evidence: string;
@@ -242,7 +242,7 @@ export interface QualificationRow {
/**
* The qualification state for a specific interest, joined with the port's
* current criterion definitions. Returns only currently-enabled criteria
* current criterion definitions. Returns only currently-enabled criteria -
* disabled ones are hidden from the rep but their state rows are preserved
* in the DB for audit.
*/
@@ -250,7 +250,7 @@ export async function listInterestQualifications(
interestId: string,
portId: string,
): Promise<QualificationRow[]> {
// Pull the interest row with the fields needed to derive auto-satisfaction
// Pull the interest row with the fields needed to derive auto-satisfaction -
// desired-berth dims (length/width/draft) plus a linked yacht if any. Cost
// is one extra column-select vs the previous columns:{id:true} probe, so
// negligible.
@@ -310,7 +310,7 @@ export async function listInterestQualifications(
const explicit = s?.confirmed ?? false;
const evidence = autoSatisfied ? computeEvidence(c.key, ctx) : '';
// Derived-only criteria (e.g. `dimensions`) ignore the explicit tick
// entirely if the underlying evidence disappears, the row un-ticks.
// entirely - if the underlying evidence disappears, the row un-ticks.
// Judgement-based criteria keep the OR semantic so a rep's explicit
// confirmation survives an evidence change.
const confirmed = isDerivedOnly(c.key) ? autoSatisfied : explicit || autoSatisfied;
@@ -370,7 +370,7 @@ function computeAutoSatisfied(key: string, ctx: AutoCtx): boolean {
return hasYachtDims || hasDesiredDims;
}
if (key === 'intent_confirmed') {
// Signing an EOI (or later) is the strongest signal of intent
// Signing an EOI (or later) is the strongest signal of intent -
// auto-tick once the rep has moved past Qualified. The criterion
// can still be ticked manually before then.
const stageIdx = PIPELINE_STAGES.indexOf(ctx.pipelineStage);
@@ -383,7 +383,7 @@ function computeAutoSatisfied(key: string, ctx: AutoCtx): boolean {
/**
* Returns a short human-readable string explaining what data drove the
* auto-satisfaction. Mirrors `computeAutoSatisfied`'s branching so the UI
* can render "Auto · <evidence>" closes the "why is this ticked?" gap.
* can render "Auto · <evidence>" - closes the "why is this ticked?" gap.
*/
function computeEvidence(key: string, ctx: AutoCtx): string {
if (key === 'dimensions') {
@@ -411,7 +411,7 @@ function computeEvidence(key: string, ctx: AutoCtx): string {
/**
* Upsert a single criterion's confirmed-state for an interest. Stamping the
* server-side fields (confirmedBy / confirmedAt) makes the row a proper
* audit record the caller can't backdate it.
* audit record - the caller can't backdate it.
*/
export async function setInterestQualification(
interestId: string,
@@ -425,7 +425,7 @@ export async function setInterestQualification(
});
if (!interest) throw new NotFoundError('Interest');
// Refuse keys the port doesn't have a criterion for keeps state rows
// Refuse keys the port doesn't have a criterion for - keeps state rows
// referentially consistent with the visible config.
const criterion = await db.query.qualificationCriteria.findFirst({
where: and(