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

@@ -87,7 +87,7 @@ async function verifyParentBelongsToPort(
* can show "from interest E17" or "from yacht Sea Breeze" badges
* and offer a "Group by source" view alongside chronological.
*
* Company-owned yachts the client is a member of are excluded
* Company-owned yachts the client is a member of are excluded -
* those are properly the company's notes, not the client's.
*/
export interface AggregatedClientNote {
@@ -100,7 +100,7 @@ export interface AggregatedClientNote {
authorId: string;
authorName: string | null;
source: 'client' | 'interest' | 'yacht';
/** Origin entity id interest_id / yacht_id / client_id. */
/** Origin entity id - interest_id / yacht_id / client_id. */
sourceId: string;
/** Human label for the source (interest's berth mooring, yacht
* name, or "Client" for client-level). */
@@ -283,7 +283,7 @@ export async function listForYachtAggregated(
? await db
.select({ id: clients.id, name: clients.fullName })
.from(clients)
// M-MT04: defense-in-depth port_id filter without it a stale
// M-MT04: defense-in-depth port_id filter - without it a stale
// ownerClientId persisted by a prior cross-port migration could
// surface the wrong tenant's client name. Belt-and-braces given
// yacht ownership is polymorphic via a non-FK pair.
@@ -384,7 +384,7 @@ export async function listForYachtAggregated(
* company (polymorphic ownership: `owner_type='company' AND
* owner_id=companyId`) + every interest currently linked to those
* yachts. Personal-side notes from individual company members are
* NOT included they belong on the client's own dossier.
* NOT included - they belong on the client's own dossier.
*/
export async function listForCompanyAggregated(
portId: string,
@@ -871,7 +871,7 @@ export async function update(
const [updated] = await db
.update(yachtNotes)
.set({ content: data.content, updatedAt: new Date() })
// M-MT02: defense-in-depth pin the UPDATE to the (id, parent) pair
// M-MT02: defense-in-depth - pin the UPDATE to the (id, parent) pair
// so a swapped noteId can't land on a sibling yacht's note even if
// the existing read above already validated ownership.
.where(and(eq(yachtNotes.id, noteId), eq(yachtNotes.yachtId, entityId)))