feat(post-audit): Phase 1.3 + 1.4 + Phase 2 signals + pulse admin
Phase 1.3 — signing-invitation role copy - Order-agnostic phrasing (was assuming client→developer→approver order; ports configure any sequence so the "client has already signed" assumption was brittle). - Explicit developer-role branch + safe default for unknown roles. Phase 1.4 — supplemental form per-port URL - New supplemental_form_url registry entry (email.from section). - Threaded through getPortEmailConfig → PortEmailConfig.supplementalFormUrl. - /api/v1/interests/[id]/supplemental-info-request resolves the link via per-port URL when set, falls back to /public/supplemental-info/<token> CRM route when blank. Phase 2 — deal-pulse signal expansion + admin config - Compute function gains: - +5 eoi_sent_recent (≤14d) — was previously invisible - +15 deposit_received — strongest near-commit signal - +10 contract_signed — closed-loop reinforcement until outcome flips - -25 document_declined — strongest cooling signal - -20 reservation_cancelled — booked-then-cancelled warning - -30 berth_sold_to_other — primary berth lost to another deal - Each signal honours optional per-port `signal_<id>_enabled` toggle. - Registry adds master toggle (pulse_enabled), per-signal toggles, and per-port label overrides (Hot/Warm/Cold rename). - New /admin/pulse page mounted via RegistryDrivenForm. - AdminSectionsBrowser entry under Configuration. Data-wiring for the 3 risk signals (declined/cancelled/sold-to-other) needs follow-up: requires either schema timestamps on interests or derivation from event tables. Master plan §B captures the gap. Tests: 1374/1374 passing. tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -353,6 +353,16 @@ export const REGISTRY: SettingEntry[] = [
|
||||
scope: 'port',
|
||||
placeholder: 'sales@example.com',
|
||||
},
|
||||
{
|
||||
key: 'supplemental_form_url',
|
||||
section: 'email.from',
|
||||
label: 'Supplemental info form URL (optional)',
|
||||
description:
|
||||
"When set, the supplemental-info email links to this URL with ?token=… appended (typically the marketing site's hosted form). Leave blank to use the built-in CRM form at /public/supplemental-info/<token>. Useful when you want the client to land on a branded marketing-site page instead of the CRM domain.",
|
||||
type: 'string',
|
||||
scope: 'port',
|
||||
placeholder: 'https://portnimara.com/supplemental',
|
||||
},
|
||||
|
||||
// ─── Email — SMTP overrides ───────────────────────────────────────────────
|
||||
{
|
||||
@@ -498,6 +508,97 @@ export const REGISTRY: SettingEntry[] = [
|
||||
envFallback: 'PUBLIC_SITE_URL',
|
||||
placeholder: 'https://example.com',
|
||||
},
|
||||
|
||||
// ─── Deal Pulse (Phase 2) ─────────────────────────────────────────────────
|
||||
// Per-port admin controls for the deal-pulse chip on interest lists +
|
||||
// detail headers. Master toggle hides the chip entirely; per-signal
|
||||
// toggles let admins quiet specific signal types; label overrides
|
||||
// rename tier labels for ports that prefer their own vocabulary.
|
||||
{
|
||||
key: 'pulse_enabled',
|
||||
section: 'pulse',
|
||||
label: 'Show deal pulse chips',
|
||||
description:
|
||||
'Master toggle. When off, the pulse chip is hidden on every interest list row + detail header for this port. Useful when a port prefers to triage pipelines without the AI-tinted chip.',
|
||||
type: 'boolean',
|
||||
scope: 'port',
|
||||
},
|
||||
{
|
||||
key: 'pulse_signal_eoi_sent_recent_enabled',
|
||||
section: 'pulse',
|
||||
label: 'Signal: recent EOI sent (positive)',
|
||||
description: 'Default on. Brightens chip when EOI was sent in last 14 days.',
|
||||
type: 'boolean',
|
||||
scope: 'port',
|
||||
},
|
||||
{
|
||||
key: 'pulse_signal_deposit_received_enabled',
|
||||
section: 'pulse',
|
||||
label: 'Signal: deposit received (positive)',
|
||||
description: 'Default on. Strong forward signal once a deposit invoice flips to paid.',
|
||||
type: 'boolean',
|
||||
scope: 'port',
|
||||
},
|
||||
{
|
||||
key: 'pulse_signal_contract_signed_enabled',
|
||||
section: 'pulse',
|
||||
label: 'Signal: contract signed (positive)',
|
||||
description: 'Default on. Reinforces closed-loop progress until outcome flips to won.',
|
||||
type: 'boolean',
|
||||
scope: 'port',
|
||||
},
|
||||
{
|
||||
key: 'pulse_signal_document_declined_enabled',
|
||||
section: 'pulse',
|
||||
label: 'Signal: document declined (risk)',
|
||||
description:
|
||||
'Default on. Strongest cooling signal — client refused to sign an EOI / contract / reservation. Requires the risk-data wiring shipped alongside Phase 2 to populate.',
|
||||
type: 'boolean',
|
||||
scope: 'port',
|
||||
},
|
||||
{
|
||||
key: 'pulse_signal_reservation_cancelled_enabled',
|
||||
section: 'pulse',
|
||||
label: 'Signal: reservation cancelled (risk)',
|
||||
description: 'Default on. Booked-then-cancelled signals require rep attention.',
|
||||
type: 'boolean',
|
||||
scope: 'port',
|
||||
},
|
||||
{
|
||||
key: 'pulse_signal_berth_sold_to_other_enabled',
|
||||
section: 'pulse',
|
||||
label: 'Signal: berth resold (risk)',
|
||||
description: 'Default on. Primary berth got linked to a different completed interest.',
|
||||
type: 'boolean',
|
||||
scope: 'port',
|
||||
},
|
||||
{
|
||||
key: 'pulse_label_hot',
|
||||
section: 'pulse',
|
||||
label: 'Custom label: Hot tier',
|
||||
description: 'Leave blank to use the built-in "Hot" label.',
|
||||
type: 'string',
|
||||
scope: 'port',
|
||||
placeholder: 'Hot',
|
||||
},
|
||||
{
|
||||
key: 'pulse_label_warm',
|
||||
section: 'pulse',
|
||||
label: 'Custom label: Warm tier',
|
||||
description: 'Leave blank to use the built-in "Warm" label.',
|
||||
type: 'string',
|
||||
scope: 'port',
|
||||
placeholder: 'Warm',
|
||||
},
|
||||
{
|
||||
key: 'pulse_label_cold',
|
||||
section: 'pulse',
|
||||
label: 'Custom label: Cold tier',
|
||||
description: 'Leave blank to use the built-in "Cold" label.',
|
||||
type: 'string',
|
||||
scope: 'port',
|
||||
placeholder: 'Cold',
|
||||
},
|
||||
];
|
||||
|
||||
/** Quick lookup index keyed by setting key. */
|
||||
|
||||
Reference in New Issue
Block a user