fix(audit-wave-9): onboarding + first-run UX fixes (onboarding-auditor)

Address the CRITICAL and high-leverage HIGH items from the
onboarding-auditor report:

**C1 — checklist auto-checks were reading the wrong setting keys**
A port that had actually been configured still showed three steps as
incomplete, permanently capping the checklist at < 70 %.

- email step: `sales_email_smtp_host` → `smtp_host_override` (the key
  the email admin page actually persists).
- documenso step: `documenso_api_url` → compound gate
  `documenso_api_url_override` + `documenso_developer_email` +
  `documenso_approver_email` + `documenso_eoi_template_id`. All four
  are required for `buildDocumensoPayload` not to error out; checking
  only the URL falsely greenlit the step until a rep tried to send an
  EOI and Documenso 404'd.
- settings step: `recommender_top_n_default` → `heat_weight_recency`.
  The defaults are layered (port > global > built-in), so a port using
  the built-ins never writes the `top_n_default` row — old key was an
  unreachable green. heat_weight_recency genuinely means "admin tuned
  the recommender".

**C2 — forms step href was broken**
`STEPS[8].href = '../'` resolved through the Link template to the
dashboard, not `/admin/forms`. Fixed to `'forms'`.

**C3 — EOI signer-identity gate**
Folded into the new compound-gate logic on the documenso step
(see C1). Now matches what the EOI pipeline actually requires before
it can send.

**C4 — ensureSystemRoots failure mode poisoned port creation**
`ports.service.createPort` awaited `ensureSystemRoots` after the port
row had committed, so a throw bubbled out as a 500 even though the
inline comment said "non-fatal if this throws". Wrap in try/catch +
logger.warn — the row stays live, the next admin action self-heals
via `ensureEntityFolder`, and the operator doesn't retry into a 409.

**H5 — berth-list empty-state copy misleads fresh ports**
"Berths are imported from external sources. Adjust your filters..."
implied data existed but was hidden. Branch on whether any filter is
active: with none, suggest running `import-berths-from-nocodb.ts`;
with filters, the original "adjust filters" message.

**M4 — admin-sections-browser description was wrong**
"Setup checklist for fresh ports (read-only references)" implied the
page was read-only when it has working manual-completion checkboxes
and discouraged clicking in. Reworded.

Additionally, the OnboardingStep type gains an optional
`autoCheckSettingKeysAll` field for compound gates (used by the
documenso step), and the auto-detected hint shows all keys when the
gate is compound.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 12:15:46 +02:00
parent 689a114aba
commit a8dec0bada
4 changed files with 68 additions and 17 deletions

View File

@@ -5,6 +5,7 @@ import { ports } from '@/lib/db/schema';
import type { PortSettings } from '@/lib/db/schema/ports';
import { createAuditLog, type AuditMeta } from '@/lib/audit';
import { ConflictError, NotFoundError } from '@/lib/errors';
import { logger } from '@/lib/logger';
import { emitToRoom } from '@/lib/socket/server';
import type { CreatePortInput, UpdatePortInput } from '@/lib/validators/ports';
import { ensureSystemRoots } from '@/lib/services/document-folders.service';
@@ -41,9 +42,20 @@ export async function createPort(data: CreatePortInput, meta: AuditMeta) {
})
.returning();
// Non-fatal if this throws: ensureSystemRoots is re-runnable, and
// scripts/backfill-document-folders.ts heals orphaned ports.
await ensureSystemRoots(port!.id, meta.userId);
// Non-fatal if this throws: ensureSystemRoots is re-runnable (any
// subsequent admin action self-heals via `ensureEntityFolder`'s
// fallback, and `scripts/backfill-document-folders.ts` covers
// orphaned ports). Swallow + log instead of propagating, so the
// operator doesn't see a 500 from `createPort` against an already-
// committed port row — they'd retry and hit a 409 slug-exists.
try {
await ensureSystemRoots(port!.id, meta.userId);
} catch (err) {
logger.warn(
{ portId: port!.id, err },
'ensureSystemRoots failed after port create — port row is live; system folders will be created on first admin action.',
);
}
void createAuditLog({
userId: meta.userId,