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

@@ -1,5 +1,5 @@
/**
* Phase 4c Auto-detect anchor scanner.
* Phase 4c - Auto-detect anchor scanner.
*
* Scans a PDF for common signing-block keywords ("Signature:", "Date:",
* "Initials", a long run of underscores, etc.) and proposes Documenso
@@ -12,7 +12,7 @@
* regexes are tried in priority order so a `"Date of Signature:"`
* anchor doesn't double-place as both DATE and SIGNATURE.
*
* This is intentionally pdf-content driven (text-extraction based)
* This is intentionally pdf-content driven (text-extraction based) -
* the alternative (image-of-PDF + OCR) is the bigger berth-PDF parser
* tier-3 path; we keep this lightweight so it runs in <500ms on a
* 10-page contract.
@@ -30,7 +30,7 @@ export interface DetectedField {
pageY: number;
pageWidth: number;
pageHeight: number;
/** 0..1 how sure the scanner is. */
/** 0..1 - how sure the scanner is. */
confidence: number;
/** Verbatim anchor that triggered the detection (display + debug). */
anchorText: string;
@@ -42,7 +42,7 @@ export interface DetectedField {
/** Anchor → field-type pattern table. Order matters: earlier patterns
* win when two anchors overlap on the same text item (e.g. "Date of
* Signature" matches both DATE and SIGNATURE DATE goes first because
* Signature" matches both DATE and SIGNATURE - DATE goes first because
* it's the more specific pattern). */
interface AnchorPattern {
type: DocumensoFieldType;
@@ -58,7 +58,7 @@ interface AnchorPattern {
}
const ANCHOR_PATTERNS: AnchorPattern[] = [
// DATE more specific than SIGNATURE for the common "Date of
// DATE - more specific than SIGNATURE for the common "Date of
// Signature:" case, so listed first.
{
type: 'DATE',
@@ -67,7 +67,7 @@ const ANCHOR_PATTERNS: AnchorPattern[] = [
heightPt: 20,
confidenceBoost: 0.2,
},
// INITIALS pre-empts NAME because "Initial:" is short and unique.
// INITIALS - pre-empts NAME because "Initial:" is short and unique.
{
type: 'INITIALS',
match: /(?:^|\b)(?:initials?)[:\s_-]+/i,
@@ -75,7 +75,7 @@ const ANCHOR_PATTERNS: AnchorPattern[] = [
heightPt: 30,
confidenceBoost: 0.2,
},
// EMAIL explicit email anchor.
// EMAIL - explicit email anchor.
{
type: 'EMAIL',
match: /(?:^|\b)e-?mail[:\s_-]+/i,
@@ -83,7 +83,7 @@ const ANCHOR_PATTERNS: AnchorPattern[] = [
heightPt: 20,
confidenceBoost: 0.2,
},
// NAME printed/full name labels.
// NAME - printed/full name labels.
{
type: 'NAME',
match: /(?:^|\b)(?:printed\s*)?(?:full\s+)?name[:\s_-]+/i,
@@ -91,7 +91,7 @@ const ANCHOR_PATTERNS: AnchorPattern[] = [
heightPt: 20,
confidenceBoost: 0.15,
},
// SIGNATURE broadest of the signing-block patterns.
// SIGNATURE - broadest of the signing-block patterns.
{
type: 'SIGNATURE',
match: /(?:^|\b)(?:signature|sign\s*here|signed\s*by|signed\s*at)[:\s_-]+/i,
@@ -99,7 +99,7 @@ const ANCHOR_PATTERNS: AnchorPattern[] = [
heightPt: 30,
confidenceBoost: 0.2,
},
// SIGNATURE explicit "X" mark followed by a blank line.
// SIGNATURE - explicit "X" mark followed by a blank line.
{
type: 'SIGNATURE',
match: /X\s*_{4,}/,
@@ -141,7 +141,7 @@ interface PdfTextItem {
transform: number[];
/** Item width in PDF user-space units. */
width?: number;
/** Item height usually equals scaleY. */
/** Item height - usually equals scaleY. */
height?: number;
}
@@ -169,7 +169,7 @@ export async function detectFields(pdfBuffer: Buffer): Promise<DetectedField[]>
for (const page of pages) {
for (const item of page.items) {
const lower = item.str.toLowerCase();
// Skip if the item has no positional data defensive against
// Skip if the item has no positional data - defensive against
// exotic PDF encodings.
if (!Array.isArray(item.transform) || item.transform.length < 6) continue;
const translateX = Number(item.transform[4]);
@@ -187,7 +187,7 @@ export async function detectFields(pdfBuffer: Buffer): Promise<DetectedField[]>
const fieldXPt = translateX + anchorWidthPt + 5;
// PDF user-space origin is the lower-left; transform[5] is the
// baseline of the text so the field's lower-left also lives
// there. CSS/web origin is top-left we keep the percent in
// there. CSS/web origin is top-left - we keep the percent in
// PDF coordinates here because Documenso accepts both (the
// existing placeFields helper handles the conversion).
const fieldYPt = translateY;
@@ -197,7 +197,7 @@ export async function detectFields(pdfBuffer: Buffer): Promise<DetectedField[]>
const pageWidth = (pattern.widthPt / page.widthPt) * 100;
const pageHeight = (pattern.heightPt / page.heightPt) * 100;
// Hard-skip fields that would land off-page (defensive a
// Hard-skip fields that would land off-page (defensive - a
// misparsed transform can blow up the coordinate space).
if (pageX < 0 || pageX > 95 || pageY < 0 || pageY > 95) continue;
if (pageWidth <= 0 || pageHeight <= 0) continue;
@@ -215,7 +215,7 @@ export async function detectFields(pdfBuffer: Buffer): Promise<DetectedField[]>
anchorText: item.str.trim(),
inferredRecipientLabel: recipientLabel,
});
// First matching pattern wins for this item earlier
// First matching pattern wins for this item - earlier
// (more-specific) patterns shadow later ones.
break;
}
@@ -258,7 +258,7 @@ function inferRecipient(
* import it dynamically so the heavy native-bindings dep only loads
* when the detector actually runs.
*
* Returns an empty array if pdfjs fails to parse the rep gets the
* Returns an empty array if pdfjs fails to parse - the rep gets the
* manual placement flow without an error toast.
*/
export async function extractPdfPages(pdfBuffer: Buffer): Promise<PdfPageView[]> {
@@ -285,7 +285,7 @@ export async function extractPdfPages(pdfBuffer: Buffer): Promise<PdfPageView[]>
return pages;
} catch {
// Image-only scans or corrupt PDFs land here. The dialog falls
// back to manual placement no rep-facing error needed.
// back to manual placement - no rep-facing error needed.
return [];
}
}