diff --git a/client/src/styles/collections.css b/client/src/styles/collections.css index 6682f2cdd..d56ee751d 100644 --- a/client/src/styles/collections.css +++ b/client/src/styles/collections.css @@ -503,6 +503,7 @@ .trek-dash .col-detail-btn:hover { background: var(--surface); color: var(--ink); } .trek-dash .col-detail-btn svg { flex-shrink: 0; } .trek-dash .col-detail-btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); } +.trek-dash .col-detail-btn:disabled { opacity: .5; cursor: not-allowed; } .trek-dash .col-detail-btn.danger { color: oklch(0.6 0.2 25); border-color: transparent; background: transparent; } .trek-dash .col-detail-btn.danger:hover { background: oklch(0.6 0.2 25 / .12); } diff --git a/server/src/nest/memories/memories.helpers.ts b/server/src/nest/memories/memories.helpers.ts index f9652c94b..1f7c91cac 100644 --- a/server/src/nest/memories/memories.helpers.ts +++ b/server/src/nest/memories/memories.helpers.ts @@ -213,6 +213,8 @@ export type AssetInfo = { fileName?: string | null; } +const NODE_OPERATOR_HINT = /;\s*if the root CA is installed locally.*$/is; + /** * Why a request to a provider never got an answer, worded for the settings card. * @@ -220,12 +222,16 @@ export type AssetInfo = { * (an unknown host, a refused connection, a self-signed certificate), so the * message alone gave the user nothing to act on (#2475). The first cause that * says something is appended; an error without one reads exactly as before. + * Node's own advice to operators that rides along on a certificate error + * ("try running Node.js with --use-system-ca") is cut off: whoever reads the + * card cannot act on it. */ export function describeFetchFailure(err: unknown): string { if (!(err instanceof Error)) return 'Connection failed'; let cause: unknown = err.cause; for (let depth = 0; depth < 3 && cause instanceof Error; depth++) { - if (cause.message && cause.message !== err.message) return `${err.message} (${cause.message})`; + const reason = cause.message.replace(NODE_OPERATOR_HINT, '').trim(); + if (reason && reason !== err.message) return `${err.message} (${reason})`; cause = cause.cause; } return err.message; diff --git a/server/tests/unit/nest/memories.helpers.test.ts b/server/tests/unit/nest/memories.helpers.test.ts index 2d139bb9c..b5aa95f47 100644 --- a/server/tests/unit/nest/memories.helpers.test.ts +++ b/server/tests/unit/nest/memories.helpers.test.ts @@ -175,4 +175,9 @@ describe('describeFetchFailure', () => { it('MEM-FETCH-005: a rejection that is not an Error falls back to the old wording', () => { expect(describeFetchFailure('boom')).toBe('Connection failed'); }); + + it('MEM-FETCH-006: leaves out the advice Node adds for whoever runs the server', () => { + const cause = new Error('self-signed certificate; if the root CA is installed locally, try running Node.js with --use-system-ca'); + expect(describeFetchFailure(new TypeError('fetch failed', { cause }))).toBe('fetch failed (self-signed certificate)'); + }); });