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

@@ -57,7 +57,7 @@ export interface FolderNode extends DocumentFolder {
/**
* Returns the entire folder tree for a port, nested under their
* parents. Roots come back at the top level. Order is alphabetical
* (case-insensitive) within each parent matches the sibling-uniqueness
* (case-insensitive) within each parent - matches the sibling-uniqueness
* index ordering and gives reps a stable browsing experience.
*
* Uses a single SELECT + JS nesting rather than a recursive CTE; the
@@ -241,14 +241,14 @@ export async function moveFolder(
// Cycle check: walk the destination's ancestor chain. If we hit
// folderId, the destination is a descendant of the folder being
// moved moving would create a cycle.
// moved - moving would create a cycle.
let cursor: string | null = newParent.parentId;
const seen = new Set<string>([newParent.id]);
while (cursor) {
if (cursor === folderId) {
throw new ValidationError('Cannot move a folder under one of its descendants (cycle)');
}
if (seen.has(cursor)) break; // defensive pre-existing cycle, bail
if (seen.has(cursor)) break; // defensive - pre-existing cycle, bail
seen.add(cursor);
const next = await tx.query.documentFolders.findFirst({
where: and(eq(documentFolders.id, cursor), eq(documentFolders.portId, portId)),
@@ -312,7 +312,7 @@ export async function deleteFolderSoftRescue(
.set({ folderId: newParent })
.where(and(eq(documents.folderId, folderId), eq(documents.portId, portId)));
// G-C1: files.folder_id is ON DELETE SET NULL without this UPDATE,
// G-C1: files.folder_id is ON DELETE SET NULL - without this UPDATE,
// files in the deleted folder would scatter to root while documents
// in the same folder land at the deleted folder's parent. Re-parent
// files explicitly so the soft-rescue is symmetric across both.
@@ -387,7 +387,7 @@ export async function ensureSystemRoots(portId: string, userId: string): Promise
// inserts can only collide on `uniq_document_folders_sibling_name`
// (entityId is null on roots, so the partial index
// `uniq_document_folders_entity` is excluded). Do not copy this
// pattern into helpers that insert per-entity subfolders they
// pattern into helpers that insert per-entity subfolders - they
// need an explicit target to avoid masking real conflicts.
await db.insert(documentFolders).values(values).onConflictDoNothing();
@@ -401,7 +401,7 @@ export async function ensureSystemRoots(portId: string, userId: string): Promise
if (!row) {
logger.error(
{ portId, missingRoot: name, foundNames: rows.map((r) => r.name) },
'ensureSystemRoots: invariant violated system root missing after upsert',
'ensureSystemRoots: invariant violated - system root missing after upsert',
);
throw new Error(`ensureSystemRoots: missing root ${name} after upsert`);
}
@@ -451,11 +451,11 @@ async function resolveEntityDisplayName(
});
if (!i) throw new NotFoundError('Interest');
// Defer to the interest-berths service for the primary berth label
// circular-dep avoidance via dynamic import. Falls back to the
// - circular-dep avoidance via dynamic import. Falls back to the
// ISO date slice ("Deal 2026-05-12") when no berth is linked yet.
const { getPrimaryBerth } = await import('@/lib/services/interest-berths.service');
const primary = await getPrimaryBerth(entityId).catch(() => null);
if (primary?.mooringNumber) return `Deal ${primary.mooringNumber}`;
if (primary?.mooringNumber) return `Deal - ${primary.mooringNumber}`;
const dateSlice = i.createdAt.toISOString().slice(0, 10);
return `Deal ${dateSlice}`;
}
@@ -495,7 +495,7 @@ function isEntityFolderConflict(err: unknown): boolean {
* regardless of whether it was newly created or already existed.
*
* Concurrent callers race safely via the partial unique index
* `uniq_document_folders_entity` the loser INSERT errors and the
* `uniq_document_folders_entity` - the loser INSERT errors and the
* re-SELECT returns the winner's row.
*
* On sibling-name collision (two entities want the same display name),
@@ -540,7 +540,7 @@ export async function ensureEntityFolder(
columns: { clientId: true },
});
if (!interestRow) throw new NotFoundError('Interest');
// Recursively ensure the parent client's folder first guarantees
// Recursively ensure the parent client's folder first - guarantees
// we always land inside the existing Clients/<Name>/ subfolder even
// when the deal's first artifact predates any client-level upload.
parent = await ensureEntityFolder(portId, 'client', interestRow.clientId, userId);
@@ -610,12 +610,12 @@ export async function ensureEntityFolder(
* Rename the per-entity subfolder to match the entity's current display
* name. Called from the entity rename services (`updateClient`,
* `updateCompany`, `updateYacht`). No-op when the folder does not exist
* (lazy creation entities without a folder skip the sync entirely).
* (lazy creation - entities without a folder skip the sync entirely).
*
* Sibling-name collision is resolved by suffix bump (matches
* `ensureEntityFolder` semantics).
*
* Intentionally does NOT call `assertNotSystemManaged` this helper
* Intentionally does NOT call `assertNotSystemManaged` - this helper
* is the legitimate path for renaming a system folder.
*/
export async function syncEntityFolderName(
@@ -633,7 +633,7 @@ export async function syncEntityFolderName(
eq(documentFolders.entityId, entityId),
),
});
if (!folder) return; // Lazy creation nothing to sync yet.
if (!folder) return; // Lazy creation - nothing to sync yet.
// Preserve archived suffix if present.
const isArchived = folder.name.endsWith(' (archived)');
@@ -689,7 +689,7 @@ const DELETED_SUFFIX = ' (deleted)';
/**
* Stamp an entity's subfolder as archived: append " (archived)" to the
* name (idempotent won't double-append) and set archived_at. No-op
* name (idempotent - won't double-append) and set archived_at. No-op
* when the folder does not exist (lazy creation). Used by the entity
* archive paths in clients / companies / yachts services.
*/
@@ -730,7 +730,7 @@ export async function applyEntityArchivedSuffix(
}
/**
* Inverse of `applyEntityArchivedSuffix` strip " (archived)" from
* Inverse of `applyEntityArchivedSuffix` - strip " (archived)" from
* the name and clear archived_at. No-op when the folder does not
* exist or wasn't archived.
*/
@@ -775,7 +775,7 @@ export async function applyEntityRestoredSuffix(
* folder by clearing `system_managed`, appending " (deleted)" to the
* name, and dropping the entity FK so the partial unique index no
* longer constrains it. Files still inside the folder retain their
* snapshotted entity FKs (orphaned they appear in the root-view
* snapshotted entity FKs (orphaned - they appear in the root-view
* Files section once the rep cleans up).
*
* Idempotent: re-demoting an already-demoted folder is a no-op because
@@ -814,7 +814,7 @@ export async function demoteSystemFolderOnEntityDelete(
/**
* Phase 2 nested-subfolders lifecycle hook. Re-renames an interest's
* document folder when its outcome changes e.g. `Deal A1-A3` becomes
* document folder when its outcome changes - e.g. `Deal A1-A3` becomes
* `Deal A1-A3 (Won)`, `(Lost)`, or `(Cancelled)`. No-op when the
* folder doesn't exist yet (uploads happen later) or when the outcome
* is null (still in flight).