diff --git a/eslint.config.mjs b/eslint.config.mjs index 6b175387..7cf2b5c9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -44,7 +44,10 @@ const eslintConfig = [ files: ['src/components/**/*.tsx', 'src/app/**/*.tsx'], rules: { 'no-restricted-syntax': [ - 'warn', + // Bumped from warn → error after the 2026-05-21 sweep cleared + // the existing 108 instances. New code reintroducing em-dashes + // now fails the lint gate. + 'error', { selector: "JSXText[value=/\\u2014/]", message: diff --git a/src/app/(auth)/setup/page.tsx b/src/app/(auth)/setup/page.tsx index 6fd399da..e02b4fa3 100644 --- a/src/app/(auth)/setup/page.tsx +++ b/src/app/(auth)/setup/page.tsx @@ -91,7 +91,7 @@ export default function SetupPage() { password: data.password, }, }); - toast.success('Administrator account created — sign in to continue.'); + toast.success('Administrator account created - sign in to continue.'); router.replace('/login'); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to create administrator account'); @@ -114,7 +114,7 @@ export default function SetupPage() {
- No administrator account exists yet. Create one to get started — you’ll be the + No administrator account exists yet. Create one to get started - you’ll be the super-administrator for this installation.
The CRM supports both Documenso 1.13.x (v1) and 2.x (v2). v1 is the default for
backwards compatibility. v2 is recommended for new ports and unlocks the features below.
- Switching versions does not require any code changes — version-aware
+ Switching versions does not require any code changes - version-aware
client methods pick the right endpoint per port. Switch, save, then run the
test-connection button to confirm the chosen instance is actually on the matching
Documenso version.
@@ -111,7 +111,7 @@ export default function DocumensoSettingsPage() {
/>
Percent-based field coordinates. No page-dimension lookup needed
- — coordinates are portable across page sizes. v1 requires us to assume A4 for
+ - coordinates are portable across page sizes. v1 requires us to assume A4 for
auto-placed fields.
@@ -122,7 +122,7 @@ export default function DocumensoSettingsPage() {
/>
Richer field metadata. TEXT labels & required flags, NUMBER
- min/max + format, CHECKBOX/DROPDOWN/RADIO option lists with defaults — all ignored
+ min/max + format, CHECKBOX/DROPDOWN/RADIO option lists with defaults - all ignored
by v1, surfaced by v2 in the signing UI.
@@ -134,7 +134,7 @@ export default function DocumensoSettingsPage() {
v2-flavoured webhook events. RECIPIENT_VIEWED,{' '}
RECIPIENT_SIGNED, DOCUMENT_RECIPIENT_COMPLETED,{' '}
- DOCUMENT_DECLINED, DOCUMENT_REMINDER_SENT — all routed
+ DOCUMENT_DECLINED, DOCUMENT_REMINDER_SENT - all routed
through the same dedup + audit pipeline as v1 events.
@@ -147,9 +147,9 @@ export default function DocumensoSettingsPage() {
Envelope CRUD endpoints. GET, DELETE,
POST /envelope/create (multipart),{' '}
POST /envelope/distribute, POST /envelope/redistribute,{' '}
- GET /envelope/{'{id}'}/download — all routed through{' '}
+ GET /envelope/{'{id}'}/download - all routed through{' '}
/api/v2/envelope/... when v2 is selected. The template-generate path
- is intentionally still v1 (relies on Documenso 2.x's backward-compat window —
+ is intentionally still v1 (relies on Documenso 2.x's backward-compat window -
see the deferred-roadmap below).
@@ -160,7 +160,7 @@ export default function DocumensoSettingsPage() {
/>
One-call send. v2's /envelope/distribute{' '}
- returns per-recipient signingUrl in the same response — v1 requires a
+ returns per-recipient signingUrl in the same response - v1 requires a
separate GET to fetch them. Faster send flow on the rep side.
@@ -186,7 +186,7 @@ export default function DocumensoSettingsPage() {
behaviour" card; Documenso redirects the signer to that URL after they
complete signing. Use to land clients on the marketing site's success page or
back in the portal instead of Documenso's default thank-you page. (v1 honours
- this too — listed here because the admin setting was added with the v2 work.)
+ this too - listed here because the admin setting was added with the v2 work.)
@@ -201,7 +201,7 @@ export default function DocumensoSettingsPage() {
Single-shot /template/use
{' '}
- with v2 prefillFields by ID — current EOI flow uses{' '}
+ with v2 prefillFields by ID - current EOI flow uses{' '}
/api/v1/templates/{'{id}'}/generate-document with{' '}
formValues keyed by name. v2 instances accept both during their
backward-compat window; full migration requires per-template field-ID capture in
@@ -211,17 +211,17 @@ export default function DocumensoSettingsPage() {
Update envelope metadata after creation (/envelope/update)
{' '}
- — change title / subject / redirectUrl on a doc already in DRAFT/PENDING without
+ - change title / subject / redirectUrl on a doc already in DRAFT/PENDING without
re-generating.
- Sequential signing and post-signing redirect URL are now wired — see + Sequential signing and post-signing redirect URL are now wired - see the new "v2 signing behaviour" card below to configure them.
@@ -244,13 +244,13 @@ export default function DocumensoSettingsPage() {Every error code the platform can return, with its HTTP status and the plain-language - message a user sees. Codes are stable identifiers — once shipped, they never get + message a user sees. Codes are stable identifiers - once shipped, they never get renamed.
diff --git a/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx b/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx index 9b4257f1..b0d07bf4 100644 --- a/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx @@ -30,34 +30,34 @@ const TRIGGERS: Array<{ { key: 'eoi_sent', label: 'EOI sent', - description: 'Rep generates an EOI for signing — moves the deal to "EOI" stage.', + description: 'Rep generates an EOI for signing - moves the deal to "EOI" stage.', defaultMode: 'auto', }, { key: 'eoi_signed', label: 'EOI signed (all parties)', description: - 'All signatories complete the EOI — moves the deal to "Reservation" stage. Conventional CRM behaviour.', + 'All signatories complete the EOI - moves the deal to "Reservation" stage. Conventional CRM behaviour.', defaultMode: 'auto', }, { key: 'reservation_signed', label: 'Reservation agreement signed', description: - 'Reservation paperwork signed by all parties — keeps the deal at "Reservation" with sub-status signed.', + 'Reservation paperwork signed by all parties - keeps the deal at "Reservation" with sub-status signed.', defaultMode: 'auto', }, { key: 'deposit_received', label: 'Deposit received in full', description: - 'Deposit total reaches the expected amount — moves the deal to "Deposit Paid" stage.', + 'Deposit total reaches the expected amount - moves the deal to "Deposit Paid" stage.', defaultMode: 'auto', }, { key: 'contract_signed', label: 'Sales contract signed', - description: 'Final contract signed by all parties — moves the deal to "Contract" stage.', + description: 'Final contract signed by all parties - moves the deal to "Contract" stage.', defaultMode: 'auto', }, ]; @@ -166,7 +166,7 @@ export default function PipelineRulesPage() { >Custom
- Mix and match — the per-trigger toggles below override the preset. + Mix and match - the per-trigger toggles below override the preset.
diff --git a/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx b/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx index 4faba48a..2003201d 100644 --- a/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx @@ -24,7 +24,7 @@ export default function PulseAdminPage() {Every interest row carries a small coloured chip in the detail header. It scores the deal from 0–100 using rule-based signals (no AI). Click the chip on any interest to see - the per-signal breakdown — every +N or -N traces back to a dated event on the deal. + the per-signal breakdown - every +N or -N traces back to a dated event on the deal.
Positive signals (recent EOI sent, deposit received, contract signed) push the score up. diff --git a/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx b/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx index 3e124914..228f70cf 100644 --- a/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx +++ b/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx @@ -206,14 +206,14 @@ export default function ScanReceiptPage() { )} {uploadMutation.isError && ( - Receipt upload failed — save will still create the expense without an image. + Receipt upload failed - save will still create the expense without an image. )} ) : (
- To update name, phone, or address, please contact your port team — they keep the records + To update name, phone, or address, please contact your port team - they keep the records authoritative.
diff --git a/src/app/docs/deal-pulse/page.tsx b/src/app/docs/deal-pulse/page.tsx index d8362559..8aad3b79 100644 --- a/src/app/docs/deal-pulse/page.tsx +++ b/src/app/docs/deal-pulse/page.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Deal Pulse & Heat — Port Nimara CRM', + title: 'Deal Pulse & Heat - Port Nimara CRM', description: 'How the deal pulse chip + heat score work: signals, calibration, and what to do when a deal goes cold.', }; @@ -35,7 +35,7 @@ export default function DealPulseDocsPage() {The colored chip on each interest is a fast read of{' '} how hot the deal is right now based on what's been happening on it - lately — not a prediction, not an AI score, just a mechanical rollup of recent activity. + lately - not a prediction, not an AI score, just a mechanical rollup of recent activity.
@@ -53,7 +53,7 @@ export default function DealPulseDocsPage() {@@ -128,7 +128,7 @@ export default function DealPulseDocsPage() { Can I override the chip on a specific deal?
- Not directly — the chip is a read-only summary. To change it, change the inputs: log a + Not directly - the chip is a read-only summary. To change it, change the inputs: log a contact, advance a stage, or close the deal.
diff --git a/src/components/admin/audit/audit-log-list.tsx b/src/components/admin/audit/audit-log-list.tsx index ee3c4ff4..6602f41a 100644 --- a/src/components/admin/audit/audit-log-list.tsx +++ b/src/components/admin/audit/audit-log-list.tsx @@ -354,7 +354,7 @@ export function AuditLogList() { row.original.ipAddress ? ({row.original.ipAddress}
) : (
- —
+ -
),
size: 130,
},
@@ -457,7 +457,7 @@ export function AuditLogList() {
- From date must be on or before To date — date filter ignored. + From date must be on or before To date - date filter ignored.
)} @@ -642,7 +642,7 @@ export function AuditLogList() { <>backups/<id>.dump via{' '}
- getStorageBackend().put(). Restore is intentionally not exposed in the UI —
+ getStorageBackend().put(). Restore is intentionally not exposed in the UI -
download the .dump file and run pg_restore manually.
{`{{custom.fieldName}}`} now
expand in EOI/contract/email templates for client/interest/berth contexts. They still
don’t plug into the global search index, the berth recommender, or the entity-diff
- audit log — use them for rep-only annotations and template-merge values, but anything
+ audit log - use them for rep-only annotations and template-merge values, but anything
load-bearing for the deal flow still needs a first-class column.
diff --git a/src/components/admin/documenso/embedded-signing-card.tsx b/src/components/admin/documenso/embedded-signing-card.tsx
index 0b91fa9d..ef239409 100644
--- a/src/components/admin/documenso/embedded-signing-card.tsx
+++ b/src/components/admin/documenso/embedded-signing-card.tsx
@@ -72,7 +72,7 @@ export function EmbeddedSigningCard() {
};
setResult({ ...res.data, at: new Date() });
if (res.data.ok) toast.success('Embedded signing host reachable.');
- else toast.error('Embedded signing host probe failed — see card.');
+ else toast.error('Embedded signing host probe failed - see card.');
} catch (err) {
toastError(err);
setResult({
@@ -200,7 +200,7 @@ export function EmbeddedSigningCard() {
The marketing site needs to handle /sign/[role]/[token] by forwarding
to the underlying Documenso signing URL (or embedding it in an iframe). Role is one
- of client / developer / approver — useful for
+ of client / developer / approver - useful for
tracking which slot the signer is in.
Minimum Next.js example:
@@ -228,7 +228,7 @@ export default function SignPage({ params }) {
Use the Test connection button to verify / and{' '}
/sign/success return 2xx. If either fails, the marketing site
- isn't ready — fix the route before flipping live or signers will land on a 404
+ isn't ready - fix the route before flipping live or signers will land on a 404
page from outbound emails.
Read from the template itself on Documenso. These values are bound to the
- template, so every envelope generated from it inherits them —{' '}
+ template, so every envelope generated from it inherits them -{' '}
/template/use does not accept overrides for these.
Change them in Documenso's template editor.
prefillFields
{lastResult.fieldCount === 0 && (
- — that's fine if your template is a fillable PDF (AcroForm). The CRM will
+ - that's fine if your template is a fillable PDF (AcroForm). The CRM will
fill it via formValues-by-name instead, same as on v1.{' '}
prefillFields is only needed if you placed field overlays directly in
the Documenso template editor.
@@ -314,7 +314,7 @@ export function TemplateSyncButton() {
These are the fillable fields actually in the PDF binary on Documenso. The CRM - fills them by name at send time — this is the same mechanism the prod v1 server + fills them by name at send time - this is the same mechanism the prod v1 server uses.
{lastResult.acroForm.map((report) => ( @@ -427,7 +427,7 @@ export function TemplateSyncButton() { {sync.isError && !lastResult && (
Paste or edit TipTap JSON. Use{' '}
{'{{scope.field}}'} tokens for
- dynamic content — see the list below.
+ dynamic content - see the list below.
getSystemSetting(portId, key). Values can be JSON
- objects, plain strings, numbers, or booleans. Most reps will never need this section —
+ objects, plain strings, numbers, or booleans. Most reps will never need this section -
touch only if you know which key affects what.
diff --git a/src/components/admin/shared/registry-driven-form.tsx b/src/components/admin/shared/registry-driven-form.tsx
index b1e1ff55..b422f5a8 100644
--- a/src/components/admin/shared/registry-driven-form.tsx
+++ b/src/components/admin/shared/registry-driven-form.tsx
@@ -616,7 +616,7 @@ function UserSelectInput({
Switch + migrate copies every existing file to the new backend then flips the pointer atomically. Reversible with a follow-up reverse-migration.{' '} - Switch only flips the pointer immediately — old files become + Switch only flips the pointer immediately - old files become inaccessible until you migrate them or revert the backend.
{s.backend} will
not be reachable from the CRM after the switch unless you migrate them later. This is
- rarely the right choice — prefer Switch + migrate.
+ rarely the right choice - prefer Switch + migrate.
)}
{error.message}
- {error.source === 'queue' ? 'Queue' : 'Audit'} —{' '} + {error.source === 'queue' ? 'Queue' : 'Audit'} -{' '} {new Date(error.timestamp).toLocaleString()}
- How this user appears across the app — usually their full name, but they can pick + How this user appears across the app - usually their full name, but they can pick a nickname.
@@ -238,7 +238,7 @@ function UserFormBody({ open, onOpenChange, user, onSuccess }: UserFormProps) { /> {isEdit && email.trim().toLowerCase() !== originalEmail.toLowerCase() ? (- You'll be asked to confirm — the original address will receive an automated + You'll be asked to confirm - the original address will receive an automated notice that you, the admin, changed their sign-in email.
) : isEdit ? ( diff --git a/src/components/admin/users/user-permission-matrix.tsx b/src/components/admin/users/user-permission-matrix.tsx index 326da5f3..fd66b029 100644 --- a/src/components/admin/users/user-permission-matrix.tsx +++ b/src/components/admin/users/user-permission-matrix.tsx @@ -210,7 +210,7 @@ export function UserPermissionMatrix({ userId }: UserPermissionMatrixProps) { if (isSuperAdmin) { return (EOIs, contracts, and other deal documents attached to interests currently linked to this - berth. Read-only — to send, sign, or edit, open the document on the linked interest's + berth. Read-only - to send, sign, or edit, open the document on the linked interest's page.
- This action is reversible — restore individually from each archived client. + This action is reversible - restore individually from each archived client.
EOI documents — retained for audit (always)
- {dossier.hasPortalUser &&Portal user — deactivated (login revoked)
} +EOI documents - retained for audit (always)
+ {dossier.hasPortalUser &&Portal user - deactivated (login revoked)
} {dossier.companies.length > 0 && ( -Company memberships — end-dated to today (history preserved)
+Company memberships - end-dated to today (history preserved)
)} -Notes, contacts, tags, addresses — survive on the archived client
+Notes, contacts, tags, addresses - survive on the archived client
How the weighted forecast works
- Each pipeline stage has a close-probability — how likely a deal at that stage is to + Each pipeline stage has a close-probability - how likely a deal at that stage is to actually close. Multiplying the berth price by the stage weight gives an{' '} expected value for that deal. Summing across every active deal - yields the weighted forecast — a defensible “what will likely land” + yields the weighted forecast - a defensible “what will likely land” number, vs the gross which assumes every deal closes at full value.
- Leave all unchecked to cancel silently — no emails will be sent. + Leave all unchecked to cancel silently - no emails will be sent.
- Optional (Section 3 — left blank if absent) + Optional (Section 3 - left blank if absent)
{ctx.yacht ? (- Fill the fields below — they'll be saved to the client's record before + Fill the fields below - they'll be saved to the client's record before the EOI renders.