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:
@@ -94,7 +94,7 @@ export type UpdateBerthStatusInput = z.infer<typeof updateBerthStatusSchema>;
|
||||
// ─── Archive Berth ────────────────────────────────────────────────────────────
|
||||
|
||||
// Post-audit F5: archive replaces hard-delete. A `reason` is required so
|
||||
// the audit trail captures intent — "decommissioned 2026", "duplicate of
|
||||
// the audit trail captures intent - "decommissioned 2026", "duplicate of
|
||||
// A3", etc. min(5) blocks one-letter throwaways.
|
||||
export const archiveBerthSchema = z.object({
|
||||
reason: z.string().trim().min(5, 'Reason must be at least 5 characters'),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createInsertSchema, createUpdateSchema } from 'drizzle-zod';
|
||||
|
||||
import { brochures } from '@/lib/db/schema/brochures';
|
||||
|
||||
// Derived from the Drizzle table — adding a column to `brochures`
|
||||
// Derived from the Drizzle table - adding a column to `brochures`
|
||||
// auto-includes it here. Refinements override per-field.
|
||||
export const createBrochureSchema = createInsertSchema(brochures, {
|
||||
label: (s) => s.trim().min(1).max(120),
|
||||
@@ -21,7 +21,7 @@ export const registerBrochureVersionSchema = z.object({
|
||||
.min(1)
|
||||
.max(500)
|
||||
// Mirrors the `validateStorageKey` regex in `src/lib/storage/filesystem.ts`
|
||||
// — defense-in-depth against path-traversal payloads from the client.
|
||||
// - defense-in-depth against path-traversal payloads from the client.
|
||||
.regex(/^[a-zA-Z0-9/_.-]+$/, 'Invalid storage key format')
|
||||
.refine((s) => !s.includes('..'), 'Storage key may not contain ".."')
|
||||
.refine((s) => !s.startsWith('/'), 'Storage key may not be absolute'),
|
||||
|
||||
@@ -41,7 +41,7 @@ const createTemplateBaseSchema = z.object({
|
||||
bodyHtml: z.string().min(1).optional(),
|
||||
mergeFields: mergeFieldsSchema,
|
||||
isActive: z.boolean().default(true),
|
||||
// Phase 7.1 — PDF overlay markers (percent-coord) saved by the
|
||||
// Phase 7.1 - PDF overlay markers (percent-coord) saved by the
|
||||
// in-app editor. Reused by templateFormat='pdf_overlay' at fill time.
|
||||
// Stays optional so legacy html/pdf_form templates can be PATCHed
|
||||
// without an empty array round-trip.
|
||||
@@ -70,7 +70,7 @@ export const generateSchema = z.object({
|
||||
});
|
||||
|
||||
/**
|
||||
* Phase 3b — per-field override descriptor used by the EOI dialog.
|
||||
* Phase 3b - per-field override descriptor used by the EOI dialog.
|
||||
*
|
||||
* Three modes:
|
||||
* - `useOnlyForThisEoi=true` → write the value to documents.override_*
|
||||
@@ -87,7 +87,7 @@ const fieldOverrideSchema = z.object({
|
||||
useOnlyForThisEoi: z.boolean().default(false),
|
||||
setAsDefault: z.boolean().default(false),
|
||||
/** When the value comes from an existing client_contacts row, the rep
|
||||
* picked it from the combobox — pass the id so the service can skip
|
||||
* picked it from the combobox - pass the id so the service can skip
|
||||
* re-inserting and just promote it (when setAsDefault is set). */
|
||||
contactId: z.string().uuid().optional(),
|
||||
});
|
||||
@@ -109,13 +109,13 @@ export const generateAndSignSchema = generateSchema.extend({
|
||||
* EOI's Length/Width/Draft formValues. The drawer's toggle drives this;
|
||||
* server defaults to the yacht's `lengthUnit` column when omitted. */
|
||||
dimensionUnit: z.enum(['ft', 'm']).optional(),
|
||||
/** Phase 3b/3-follow-up — optional per-field overrides applied at generation. */
|
||||
/** Phase 3b/3-follow-up - optional per-field overrides applied at generation. */
|
||||
overrides: z
|
||||
.object({
|
||||
clientEmail: fieldOverrideSchema.optional(),
|
||||
clientPhone: fieldOverrideSchema.optional(),
|
||||
yachtName: fieldOverrideSchema.optional(),
|
||||
// Phase 3 follow-up — multi-component address override. Treated as
|
||||
// Phase 3 follow-up - multi-component address override. Treated as
|
||||
// one logical "field" with one pair of checkboxes (the dialog
|
||||
// surfaces it that way, and the side-effects helper applies the
|
||||
// intent to the whole address rather than per-component).
|
||||
|
||||
@@ -104,7 +104,7 @@ export const listDocumentsSchema = baseListQuerySchema
|
||||
.optional(),
|
||||
sentSince: z.string().datetime().optional(),
|
||||
sentUntil: z.string().datetime().optional(),
|
||||
/** Entity-aggregated projection params — mutually exclusive with folderId. */
|
||||
/** Entity-aggregated projection params - mutually exclusive with folderId. */
|
||||
entityType: z.enum(['client', 'company', 'yacht']).optional(),
|
||||
entityId: z.string().uuid().optional(),
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { baseListQuerySchema } from '@/lib/api/list-query';
|
||||
import { EXPENSE_CATEGORIES, PAYMENT_METHODS } from '@/lib/constants';
|
||||
|
||||
/**
|
||||
* Inner-shape ZodObject — kept exported (without .refine wrapping) so
|
||||
* Inner-shape ZodObject - kept exported (without .refine wrapping) so
|
||||
* `updateExpenseSchema` can still call `.partial()`. The `.refine()` rule
|
||||
* for "receipt or acknowledgement" is applied via `createExpenseSchema`.
|
||||
*/
|
||||
|
||||
@@ -29,7 +29,7 @@ export const listFilesSchema = baseListQuerySchema
|
||||
.uuid()
|
||||
.optional()
|
||||
.transform((v) => (v === '' ? null : v)),
|
||||
/** Entity-aggregated projection params — mutually exclusive with folderId. */
|
||||
/** Entity-aggregated projection params - mutually exclusive with folderId. */
|
||||
entityType: z.enum(['client', 'company', 'yacht']).optional(),
|
||||
entityId: z.string().uuid().optional(),
|
||||
})
|
||||
|
||||
@@ -163,7 +163,7 @@ export const listInterestsSchema = baseListQuerySchema.extend({
|
||||
|
||||
/**
|
||||
* Filters accepted by GET /api/v1/interests/board. Strict subset of
|
||||
* listInterestsSchema — `pipelineStage` and `includeArchived` are
|
||||
* listInterestsSchema - `pipelineStage` and `includeArchived` are
|
||||
* intentionally omitted (the columns ARE the stages, archived deals
|
||||
* never belong on the board). No pagination params either.
|
||||
*/
|
||||
|
||||
@@ -83,7 +83,7 @@ export const listInvoicesSchema = baseListQuerySchema.extend({
|
||||
// `z.input` keeps fields with `.default()` (paymentTerms, currency, kind)
|
||||
// optional from the caller's perspective. The route layer runs the
|
||||
// schema through `parseBody`, so the service body can rely on those
|
||||
// defaults being present at runtime — narrow with a local cast where
|
||||
// defaults being present at runtime - narrow with a local cast where
|
||||
// the post-parse shape matters (e.g. coerced `unitPrice` is `number`).
|
||||
export type CreateInvoiceInput = z.input<typeof createInvoiceSchema>;
|
||||
export type UpdateInvoiceInput = z.input<typeof updateInvoiceSchema>;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
||||
/**
|
||||
* Per-port qualification criterion. Admin-configurable: label / description /
|
||||
* enabled state / display order. The `key` is the stable identifier code
|
||||
* references (templates, derivations) — it can't be changed after creation
|
||||
* references (templates, derivations) - it can't be changed after creation
|
||||
* because per-interest state rows reference it via composite PK.
|
||||
*/
|
||||
export const createQualificationCriterionSchema = z.object({
|
||||
@@ -24,7 +24,7 @@ export const updateQualificationCriterionSchema = createQualificationCriterionSc
|
||||
|
||||
/**
|
||||
* Whole-list reorder. The IDs array must cover exactly the port's current
|
||||
* criteria — the service rejects partial / extraneous IDs to keep the
|
||||
* criteria - the service rejects partial / extraneous IDs to keep the
|
||||
* resulting display_order contiguous.
|
||||
*/
|
||||
export const reorderQualificationCriteriaSchema = z.object({
|
||||
@@ -33,7 +33,7 @@ export const reorderQualificationCriteriaSchema = z.object({
|
||||
|
||||
/**
|
||||
* Per-interest qualification state. Only `confirmed` + optional `notes` are
|
||||
* writable — `confirmedAt` / `confirmedBy` are stamped server-side from
|
||||
* writable - `confirmedAt` / `confirmedBy` are stamped server-side from
|
||||
* the auth context.
|
||||
*/
|
||||
export const setInterestQualificationSchema = z.object({
|
||||
|
||||
@@ -47,7 +47,7 @@ export const listResidentialClientsSchema = baseListQuerySchema.extend({
|
||||
// ─── Residential interest ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Default pipeline stages — used as the fallback when a port hasn't
|
||||
* Default pipeline stages - used as the fallback when a port hasn't
|
||||
* configured its own list via the residential admin page. Mirror the
|
||||
* legacy hard-coded set so existing data continues to validate.
|
||||
*
|
||||
@@ -87,7 +87,7 @@ export const listResidentialInterestsSchema = baseListQuerySchema.extend({
|
||||
// multi-select. The legacy single-string form stays accepted so existing
|
||||
// callers (e.g. residential-client-tabs) don't need to migrate.
|
||||
pipelineStage: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
// Source filter — mirrors the main interest list. Comma-separated when
|
||||
// Source filter - mirrors the main interest list. Comma-separated when
|
||||
// submitted as a query string ("website,referral").
|
||||
source: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
assignedTo: z.string().optional(),
|
||||
|
||||
@@ -20,13 +20,13 @@ const BUCKET_TYPES = [
|
||||
|
||||
export const searchQuerySchema = z.object({
|
||||
// 2-char minimum keeps `to_tsquery('a:*')` from returning every word
|
||||
// starting with "a" — short queries return overwhelming match sets.
|
||||
// starting with "a" - short queries return overwhelming match sets.
|
||||
q: z.string().min(2).max(200),
|
||||
/** Restrict the result set to a single bucket. */
|
||||
type: z.enum(BUCKET_TYPES).optional(),
|
||||
/** Per-bucket cap. Defaults to 5 (dropdown). 25 is the typical /search-page value. */
|
||||
limit: z.coerce.number().int().min(1).max(50).optional(),
|
||||
/** Super-admin only — search ports beyond the current one. */
|
||||
/** Super-admin only - search ports beyond the current one. */
|
||||
includeOtherPorts: z
|
||||
.union([z.literal('true'), z.literal('1'), z.literal('false'), z.literal('0')])
|
||||
.transform((v) => v === 'true' || v === '1')
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createInsertSchema } from 'drizzle-zod';
|
||||
|
||||
import { tags } from '@/lib/db/schema/system';
|
||||
|
||||
// Derive the schema from the Drizzle table — adding a column to `tags`
|
||||
// Derive the schema from the Drizzle table - adding a column to `tags`
|
||||
// auto-includes it here; omitting / refining is per-field. Eliminates
|
||||
// the validator-drift class of bugs the audit flagged.
|
||||
export const createTagSchema = createInsertSchema(tags, {
|
||||
|
||||
@@ -22,11 +22,18 @@ export const updateUserPreferencesSchema = z.object({
|
||||
*/
|
||||
dashboardWidgets: z.record(z.string(), z.boolean()).optional(),
|
||||
/**
|
||||
* Rep-chosen dashboard widget order (drag-drop). Missing ids fall
|
||||
* through to registry order so newly-added widgets always surface.
|
||||
* Kept loose (array-of-string) for the same reason as above.
|
||||
* Rep-chosen dashboard widget order for the **desktop / xl layout**
|
||||
* (>= 1280px). Missing ids fall through to registry order so newly-
|
||||
* added widgets always surface.
|
||||
*/
|
||||
dashboardWidgetOrder: z.array(z.string()).optional(),
|
||||
/**
|
||||
* Rep-chosen dashboard widget order for the **stacked layout**
|
||||
* (< 1280px - single column on tablet/mobile). When unset, the
|
||||
* dashboard falls back to `dashboardWidgetOrder` (then registry
|
||||
* order).
|
||||
*/
|
||||
dashboardWidgetOrderMobile: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export type UpdateUserPreferencesInput = z.infer<typeof updateUserPreferencesSchema>;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
||||
/**
|
||||
* Canonical username shape.
|
||||
*
|
||||
* - 2..30 characters (yes, 2 — initials like "dm" are real and the
|
||||
* - 2..30 characters (yes, 2 - initials like "dm" are real and the
|
||||
* director uses them)
|
||||
* - lowercase letters, digits, `.`, `_`, `-`
|
||||
* - case-insensitive uniqueness is enforced by a partial unique index on
|
||||
|
||||
@@ -40,7 +40,7 @@ function isBlockedIpv4(host: string): boolean {
|
||||
if (a === 0) return true; // 0/8 zero
|
||||
if (a >= 224) return true; // multicast / reserved
|
||||
// outbound-webhook-auditor M1: Oracle Cloud metadata endpoint
|
||||
// (192.0.0.192) — was missing from the original denylist.
|
||||
// (192.0.0.192) - was missing from the original denylist.
|
||||
if (a === 192 && b === 0 && c === 0 && d === 192) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const createYachtSchema = z.object({
|
||||
status: z.enum(['active', 'retired', 'sold_away']).optional().default('active'),
|
||||
notes: z.string().optional(),
|
||||
tagIds: z.array(z.string()).optional().default([]),
|
||||
// Phase 3c — origin tracking. Defaults to 'manual'; the EOI spawn flow
|
||||
// Phase 3c - origin tracking. Defaults to 'manual'; the EOI spawn flow
|
||||
// sends 'eoi-generated' and the migration-0073 CHECK enforces the
|
||||
// values at the DB level.
|
||||
source: z.enum(['manual', 'imported', 'eoi-generated']).optional(),
|
||||
|
||||
Reference in New Issue
Block a user