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

@@ -23,19 +23,19 @@ import { cn } from '@/lib/utils';
import { EXPENSE_CATEGORIES, PAYMENT_METHODS } from '@/lib/constants';
import { runTesseract } from '@/lib/ocr/tesseract-client';
// Lazy-loaded compression the worker bundle isn't on the critical path,
// Lazy-loaded compression - the worker bundle isn't on the critical path,
// and most users won't reach this code without first granting camera/file
// access, by which point the module is already paged in.
async function compressReceiptIfHeavy(file: File): Promise<File> {
// Only compress raster images > ~1 MB. PDFs, SVGs, and small files pass
// through untouched. Magic-byte check via mime type the caller is the
// through untouched. Magic-byte check via mime type - the caller is the
// file picker which trusts the picker output already.
if (!file.type.startsWith('image/') || file.type === 'image/svg+xml') return file;
if (file.size < 1 * 1024 * 1024) return file;
const { default: imageCompression } = await import('browser-image-compression');
try {
const compressed = await imageCompression(file, {
maxSizeMB: 0.5, // ~500 KB target plenty of resolution for OCR
maxSizeMB: 0.5, // ~500 KB target - plenty of resolution for OCR
maxWidthOrHeight: 2000, // tesseract.js's sweet spot for receipt text
useWebWorker: true, // off the main thread; UI stays responsive
// Auto-rotate to EXIF orientation, strip metadata. Phones often
@@ -52,7 +52,7 @@ async function compressReceiptIfHeavy(file: File): Promise<File> {
if (typeof File !== 'undefined' && blob instanceof File) return blob;
return new File([blob], file.name, { type: blob.type });
} catch {
// Fall back to the original we don't want a corner-case compression
// Fall back to the original - we don't want a corner-case compression
// bug to block the user from saving an expense.
return file;
}
@@ -316,7 +316,7 @@ function VerifyForm({
interface ScanShellProps {
/** Per-port brand logo resolved server-side by the page wrapper.
* Null hides the logo block we never fall back to another tenant's
* Null hides the logo block - we never fall back to another tenant's
* imagery. */
logoUrl?: string | null;
portName?: string | null;
@@ -495,7 +495,7 @@ export function ScanShell({ logoUrl, portName }: ScanShellProps = {}) {
return (
<main
// pb-[max(1.5rem,env(safe-area-inset-bottom))] mobile-pwa-auditor
// pb-[max(1.5rem,env(safe-area-inset-bottom))] - mobile-pwa-auditor
// caught that the "Save expense" button was sitting flush against
// the home indicator on iPhone 14/15 in standalone PWA mode
// (viewportFit:cover + statusBarStyle:default exposes the safe-