feat(desktop): unify frontend error notices

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Senior Frontend Engineer
2026-03-28 11:08:01 +00:00
parent 8d50243d36
commit a9524dfbf6
9 changed files with 825 additions and 96 deletions

View File

@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite --host 0.0.0.0 --port 1420",
"build": "vite build",
"test": "vitest run",
"tauri": "tauri"
},
"dependencies": {
@@ -23,6 +24,7 @@
"@tauri-apps/cli": "^2.1.0",
"@vitejs/plugin-react": "^5.0.2",
"tailwindcss": "^4.1.12",
"vite": "^7.1.0"
"vite": "^7.1.0",
"vitest": "^3.2.4"
}
}

View File

@@ -37,6 +37,14 @@ import {
} from "./components/ui/dialog";
import { Input } from "./components/ui/input";
import { Textarea } from "./components/ui/textarea";
import {
toBackendError,
type BackendErrorContext,
createAccentNotice,
createNeutralNotice,
toBackendErrorNotice,
type OperatorNotice,
} from "./lib/operator-notices";
import { cn } from "./lib/utils";
declare const __APP_VERSION__: string;
@@ -107,12 +115,6 @@ type BackendBootstrap = {
next_backend_milestone: string;
};
type BackendError = {
code: string;
message: string;
detail: string | null;
};
type RedisConnectionRequest = {
target: ConnectionProfile["target"];
password: string | null;
@@ -375,7 +377,11 @@ function App() {
const [consoleInput, setConsoleInput] = useState("GET cache:homepage");
const [consoleHistory, setConsoleHistory] = useState(initialConsoleHistory);
const [bootstrap, setBootstrap] = useState(fallbackBootstrap);
const [banner, setBanner] = useState("String edits stay local to the shell until the backend write contract is wired.");
const [banner, setBanner] = useState<OperatorNotice>(
createNeutralNotice(
"String edits stay local to the shell until the backend write contract is wired.",
),
);
const [connectionDialogOpen, setConnectionDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [isConnectionTestRunning, setIsConnectionTestRunning] = useState(false);
@@ -403,6 +409,10 @@ function App() {
const appVersion = __APP_VERSION__;
const buildMode = import.meta.env.DEV ? "dev" : "production";
const runtimeMode = tauriAvailable ? "live desktop bridge" : "browser demo fallback";
const showNeutralBanner = (message: string) => setBanner(createNeutralNotice(message));
const showAccentBanner = (message: string) => setBanner(createAccentNotice(message));
const showBackendErrorBanner = (error: unknown, context: BackendErrorContext) =>
setBanner(toBackendErrorNotice(error, context));
const activeConnection =
connections.find((connection) => connection.id === activeConnectionId) ??
@@ -467,7 +477,7 @@ function App() {
`latestCommand=${latestConsoleEntry?.command ?? "none"}`,
`latestCommandSource=${latestConsoleEntry?.source ?? "none"}`,
`latestCommandStatus=${latestConsoleEntry?.status ?? "none"}`,
`banner=${banner}`,
`banner=${banner.message}`,
].join("\n");
useEffect(() => {
@@ -558,7 +568,6 @@ function App() {
return;
}
const backendError = toBackendError(error);
startTransition(() => {
setLiveKeys([]);
setLiveNextCursor("0");
@@ -568,7 +577,11 @@ function App() {
setDraftValue("");
setTtlDraft("");
setIsBrowseLoading(false);
setBanner(`Live key browse failed on ${activeConnection.name} / ${activeDb}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "key_browse",
connectionName: activeConnection.name,
databaseLabel: activeDb,
});
});
});
@@ -634,13 +647,17 @@ function App() {
return;
}
const backendError = toBackendError(error);
startTransition(() => {
setSelectedValueRecord(null);
setDraftValue("");
setTtlDraft("");
setIsInspectorLoading(false);
setBanner(`Live value read failed for ${selectedKey.name} on ${activeConnection.name}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "value_read",
connectionName: activeConnection.name,
databaseLabel: activeDb,
keyName: selectedKey.name,
});
});
});
@@ -665,7 +682,9 @@ function App() {
setActiveConnectionId(connection.id);
setActiveDb(formatDatabaseLabel(connection.target.database));
setSelectedKeyId(null);
setBanner(`Switched workspace context to ${connection.name} on ${formatDatabaseLabel(connection.target.database)}.`);
showNeutralBanner(
`Switched workspace context to ${connection.name} on ${formatDatabaseLabel(connection.target.database)}.`,
);
});
};
@@ -688,13 +707,13 @@ function App() {
: connection,
),
);
setBanner(`Scoped the workspace to ${database} for ${activeConnection.name}.`);
showNeutralBanner(`Scoped the workspace to ${database} for ${activeConnection.name}.`);
});
};
const handleCreateDraftConnection = () => {
if (draftErrorList.length > 0) {
setBanner(`Connection draft is incomplete: ${draftErrorList[0]}`);
showNeutralBanner(`Connection draft is incomplete: ${draftErrorList[0]}`);
return;
}
@@ -721,19 +740,23 @@ function App() {
setActiveConnectionId(nextConnection.id);
setActiveDb(formatDatabaseLabel(nextDatabase));
setSelectedKeyId(null);
setBanner("A draft connection profile was added to the shell. Credentials remain memory-only.");
showAccentBanner(
"A draft connection profile was added to the shell. Credentials remain memory-only.",
);
setConnectionDialogOpen(false);
});
};
const handleSaveString = async () => {
if (!selectedKey) {
setBanner(`Select a key in ${activeDb} before attempting a shell-level save.`);
showNeutralBanner(`Select a key in ${activeDb} before attempting a shell-level save.`);
return;
}
if (!tauriAvailable) {
setBanner(`Saved mock string value for ${selectedKey.name} in ${activeDb}. Backend persistence is not wired yet.`);
showNeutralBanner(
`Saved mock string value for ${selectedKey.name} in ${activeDb}. Backend persistence is not wired yet.`,
);
return;
}
@@ -763,13 +786,19 @@ function App() {
: formatRedisValueData(result.data),
);
setIsSaveRunning(false);
setBanner(`Saved live string value for ${selectedKey.name} in ${activeDb} and preserved the current TTL boundary.`);
showAccentBanner(
`Saved live string value for ${selectedKey.name} in ${activeDb} and preserved the current TTL boundary.`,
);
});
} catch (error) {
const backendError = toBackendError(error);
startTransition(() => {
setIsSaveRunning(false);
setBanner(`Live string save failed for ${selectedKey.name}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "value_write",
connectionName: activeConnection.name,
databaseLabel: activeDb,
keyName: selectedKey.name,
});
});
}
};
@@ -780,23 +809,27 @@ function App() {
}
setDeleteDialogOpen(false);
setBanner(`Delete confirmation completed for ${selectedKey.name} in ${activeDb}. Mutation stays mocked until a delete contract is approved.`);
showNeutralBanner(
`Delete confirmation completed for ${selectedKey.name} in ${activeDb}. Mutation stays mocked until a delete contract is approved.`,
);
};
const handleApplyTtl = async () => {
if (!selectedKey) {
setBanner(`Select a key in ${activeDb} before attempting a TTL update.`);
showNeutralBanner(`Select a key in ${activeDb} before attempting a TTL update.`);
return;
}
const validationError = validateTtlDraft(ttlDraft);
if (validationError) {
setBanner(`TTL update is invalid: ${validationError}`);
showNeutralBanner(`TTL update is invalid: ${validationError}`);
return;
}
if (!tauriAvailable) {
setBanner(`Browser dev mode keeps TTL changes in demo fallback for ${selectedKey.name}. Use desktop runtime for a live TTL update.`);
showNeutralBanner(
`Browser dev mode keeps TTL changes in demo fallback for ${selectedKey.name}. Use desktop runtime for a live TTL update.`,
);
return;
}
@@ -824,25 +857,33 @@ function App() {
: "",
);
setIsTtlUpdateRunning(false);
setBanner(`Updated live TTL for ${selectedKey.name} to ${formatRedisKeyTtl(result.metadata.ttl)} in ${activeDb}.`);
showAccentBanner(
`Updated live TTL for ${selectedKey.name} to ${formatRedisKeyTtl(result.metadata.ttl)} in ${activeDb}.`,
);
});
} catch (error) {
const backendError = toBackendError(error);
startTransition(() => {
setIsTtlUpdateRunning(false);
setBanner(`Live TTL update failed for ${selectedKey.name}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "ttl_update",
connectionName: activeConnection.name,
databaseLabel: activeDb,
keyName: selectedKey.name,
});
});
}
};
const handlePersistTtl = async () => {
if (!selectedKey) {
setBanner(`Select a key in ${activeDb} before attempting to remove TTL.`);
showNeutralBanner(`Select a key in ${activeDb} before attempting to remove TTL.`);
return;
}
if (!tauriAvailable) {
setBanner(`Browser dev mode keeps TTL removal in demo fallback for ${selectedKey.name}. Use desktop runtime for a live TTL change.`);
showNeutralBanner(
`Browser dev mode keeps TTL removal in demo fallback for ${selectedKey.name}. Use desktop runtime for a live TTL change.`,
);
return;
}
@@ -865,25 +906,35 @@ function App() {
syncUpdatedMetadata(result.metadata);
setTtlDraft("");
setIsTtlUpdateRunning(false);
setBanner(`Removed TTL for ${selectedKey.name}; the key is now ${formatRedisKeyTtl(result.metadata.ttl)}.`);
showAccentBanner(
`Removed TTL for ${selectedKey.name}; the key is now ${formatRedisKeyTtl(result.metadata.ttl)}.`,
);
});
} catch (error) {
const backendError = toBackendError(error);
startTransition(() => {
setIsTtlUpdateRunning(false);
setBanner(`Removing TTL failed for ${selectedKey.name}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "ttl_remove",
connectionName: activeConnection.name,
databaseLabel: activeDb,
keyName: selectedKey.name,
});
});
}
};
const handleRefreshInspector = async () => {
if (!selectedKey) {
setBanner(`No key is selected in ${activeDb}. Adjust or clear the current search pattern first.`);
showNeutralBanner(
`No key is selected in ${activeDb}. Adjust or clear the current search pattern first.`,
);
return;
}
if (!tauriAvailable) {
setBanner(`Refreshed shell metadata for ${selectedKey.name} in ${activeDb}. Live value reload remains downstream of backend browse contracts.`);
showNeutralBanner(
`Refreshed shell metadata for ${selectedKey.name} in ${activeDb}. Live value reload remains downstream of backend browse contracts.`,
);
return;
}
@@ -912,13 +963,19 @@ function App() {
);
setTtlDraft(result.metadata.ttl.kind === "expires_in_millis" ? String(result.metadata.ttl.value) : "");
setIsInspectorLoading(false);
setBanner(`Refreshed live metadata and value surface for ${selectedKey.name} in ${activeDb}.`);
showAccentBanner(
`Refreshed live metadata and value surface for ${selectedKey.name} in ${activeDb}.`,
);
});
} catch (error) {
const backendError = toBackendError(error);
startTransition(() => {
setIsInspectorLoading(false);
setBanner(`Live metadata refresh failed for ${selectedKey.name}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "inspector_refresh",
connectionName: activeConnection.name,
databaseLabel: activeDb,
keyName: selectedKey.name,
});
});
}
};
@@ -926,17 +983,21 @@ function App() {
const handleCopyQaSnapshot = async () => {
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
setCopyFeedback("unavailable");
setBanner("Clipboard export is unavailable in this runtime. Copy the QA snapshot text manually from the side rail.");
showNeutralBanner(
"Clipboard export is unavailable in this runtime. Copy the QA snapshot text manually from the side rail.",
);
return;
}
try {
await navigator.clipboard.writeText(qaSnapshot);
setCopyFeedback("copied");
setBanner("Copied the current QA snapshot to the clipboard for smoke evidence.");
showAccentBanner("Copied the current QA snapshot to the clipboard for smoke evidence.");
} catch {
setCopyFeedback("unavailable");
setBanner("Clipboard export failed in this runtime. Copy the QA snapshot text manually from the side rail.");
showNeutralBanner(
"Clipboard export failed in this runtime. Copy the QA snapshot text manually from the side rail.",
);
}
};
@@ -981,10 +1042,13 @@ function App() {
setIsBrowseLoading(false);
});
} catch (error) {
const backendError = toBackendError(error);
startTransition(() => {
setIsBrowseLoading(false);
setBanner(`Loading more keys failed on ${activeConnection.name} / ${activeDb}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "key_browse",
connectionName: activeConnection.name,
databaseLabel: activeDb,
});
});
}
};
@@ -1006,7 +1070,9 @@ function App() {
: item,
),
);
setBanner(`Testing ${connection.name} against ${activeDb} using the current desktop contract.`);
showNeutralBanner(
`Testing ${connection.name} against ${activeDb} using the current desktop contract.`,
);
});
if (!tauriAvailable) {
@@ -1022,7 +1088,9 @@ function App() {
: item,
),
);
setBanner(`Tauri runtime is unavailable in browser dev mode, so connection testing stayed in demo fallback for ${connection.name}.`);
showNeutralBanner(
`Tauri runtime is unavailable in browser dev mode, so connection testing stayed in demo fallback for ${connection.name}.`,
);
setIsConnectionTestRunning(false);
});
return;
@@ -1051,12 +1119,12 @@ function App() {
),
);
setActiveDb(formatDatabaseLabel(result.selected_database));
setBanner(`Live Redis test passed for ${connection.name} on ${formatDatabaseLabel(result.selected_database)}. Round-trip status: ${result.round_trip_status}.`);
showAccentBanner(
`Live Redis test passed for ${connection.name} on ${formatDatabaseLabel(result.selected_database)}. Round-trip status: ${result.round_trip_status}.`,
);
setIsConnectionTestRunning(false);
});
} catch (error) {
const backendError = toBackendError(error);
startTransition(() => {
setConnections((current) =>
current.map((item) =>
@@ -1064,12 +1132,16 @@ function App() {
? {
...item,
status: "error",
detail: backendError.code,
detail: formatBackendErrorCodeLabel(toBackendError(error).code),
}
: item,
),
);
setBanner(`Live Redis test failed for ${connection.name}: ${formatBackendError(backendError)}.`);
showBackendErrorBanner(error, {
operation: "connection_test",
connectionName: connection.name,
databaseLabel: activeDb,
});
setIsConnectionTestRunning(false);
});
}
@@ -1102,7 +1174,9 @@ function App() {
},
...current.slice(0, 4),
]);
setBanner(`Blocked ${command.toUpperCase()} in ${activeDb}. Administrative commands remain outside the visible shell.`);
showNeutralBanner(
`Blocked ${command.toUpperCase()} in ${activeDb}. Administrative commands remain outside the visible shell.`,
);
});
return;
}
@@ -1122,7 +1196,9 @@ function App() {
},
...current.slice(0, 4),
]);
setBanner(`Tauri runtime is unavailable in browser dev mode, so ${command.toUpperCase()} ran against the visible demo data only.`);
showNeutralBanner(
`Tauri runtime is unavailable in browser dev mode, so ${command.toUpperCase()} ran against the visible demo data only.`,
);
setConsoleInput(trimmed);
setIsCommandRunning(false);
});
@@ -1148,24 +1224,34 @@ function App() {
},
...current.slice(0, 4),
]);
setBanner(`Live Redis command completed on ${activeConnection.name} / ${formatDatabaseLabel(result.database)}.`);
showAccentBanner(
`Live Redis command completed on ${activeConnection.name} / ${formatDatabaseLabel(result.database)}.`,
);
setConsoleInput(trimmed);
setIsCommandRunning(false);
});
} catch (error) {
const backendError = toBackendError(error);
startTransition(() => {
setConsoleHistory((current) => [
{
command: trimmed,
output: formatBackendError(backendError),
output: toBackendErrorNotice(error, {
operation: "command_execution",
connectionName: activeConnection.name,
databaseLabel: activeDb,
commandName: command,
}).message,
status: "error",
source: "live",
},
...current.slice(0, 4),
]);
setBanner(`Live Redis command failed on ${activeConnection.name} / ${activeDb}: ${backendError.message}.`);
showBackendErrorBanner(error, {
operation: "command_execution",
connectionName: activeConnection.name,
databaseLabel: activeDb,
commandName: command,
});
setConsoleInput(trimmed);
setIsCommandRunning(false);
});
@@ -1219,8 +1305,28 @@ function App() {
</div>
</header>
<div className="mb-4 rounded-[24px] border border-[var(--line-soft)] bg-[var(--surface-raised)] px-4 py-3 shadow-[var(--shadow-panel)]">
<p className="text-sm text-[var(--ink-soft)]">{banner}</p>
<div
className={cn(
"mb-4 rounded-[24px] border px-4 py-3 shadow-[var(--shadow-panel)]",
banner.tone === "danger"
? "border-[var(--danger-soft)] bg-[var(--danger-faint)]"
: banner.tone === "accent"
? "border-[var(--accent-strong)] bg-[var(--accent-faint)]"
: "border-[var(--line-soft)] bg-[var(--surface-raised)]",
)}
>
<p
className={cn(
"text-sm",
banner.tone === "danger"
? "text-[var(--danger-strong)]"
: banner.tone === "accent"
? "text-[var(--accent-strong)]"
: "text-[var(--ink-soft)]",
)}
>
{banner.message}
</p>
</div>
<main className="grid min-h-0 flex-1 gap-4 xl:grid-cols-[310px_minmax(360px,1.1fr)_minmax(420px,1fr)]">
@@ -1902,6 +2008,10 @@ function formatConnectionCardDetail(connection: ConnectionProfile) {
return connection.detail;
}
function formatBackendErrorCodeLabel(code: string) {
return code.replace(/_/g, " ");
}
function toRedisConnectionRequest(
connection: ConnectionProfile,
activeDb: string,
@@ -1984,37 +2094,6 @@ function validateConnectionDraft(
return errors;
}
function toBackendError(error: unknown): BackendError {
if (
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof error.message === "string"
) {
return {
code:
"code" in error && typeof error.code === "string"
? error.code
: "internal",
message: error.message,
detail:
"detail" in error && typeof error.detail === "string"
? error.detail
: null,
};
}
return {
code: "internal",
message: error instanceof Error ? error.message : "Unknown backend error.",
detail: null,
};
}
function formatBackendError(error: BackendError) {
return error.detail ? `${error.message} (${error.detail})` : error.message;
}
function formatRedisResponse(response: RedisResponse) {
const normalized = normalizeRedisResponse(response);
return typeof normalized === "string"

View File

@@ -0,0 +1,89 @@
import { describe, expect, it } from "vitest";
import {
createAccentNotice,
createNeutralNotice,
toBackendError,
toBackendErrorNotice,
} from "./operator-notices";
describe("operator notices", () => {
it("creates explicit accent notices for successful actions", () => {
expect(createAccentNotice("Saved")).toEqual({
tone: "accent",
message: "Saved",
});
});
it("normalizes unknown thrown values into internal backend errors", () => {
expect(toBackendError(new Error("boom"))).toEqual({
code: "internal",
message: "boom",
detail: null,
});
});
it("maps authentication failures to a consistent operator-facing notice", () => {
expect(
toBackendErrorNotice(
{
code: "authentication_failed",
message: "Authentication failed.",
detail: "WRONGPASS invalid username-password pair",
},
{
operation: "connection_test",
connectionName: "redis-local-fixture",
databaseLabel: "db0",
},
),
).toEqual({
tone: "danger",
message:
"Redis rejected the credentials used while testing redis-local-fixture on db0. Verify username, password, and ACL scope for the selected connection. Backend detail: Authentication failed. (WRONGPASS invalid username-password pair).",
});
});
it("keeps unsupported operations in a non-destructive neutral notice", () => {
expect(
toBackendErrorNotice(
{
code: "unsupported_operation",
message: "Only string replacement is supported in V1.",
detail: null,
},
{
operation: "value_write",
connectionName: "redis-local-fixture",
databaseLabel: "db0",
keyName: "session:42",
},
),
).toEqual(
createNeutralNotice(
"The current desktop contract does not allow saving session:42 on redis-local-fixture / db0 yet. Stay on the visible read-only path or wait for a backend contract change before retrying. Backend detail: Only string replacement is supported in V1.",
),
);
});
it("describes command failures with command and scope context", () => {
expect(
toBackendErrorNotice(
{
code: "command_failed",
message: "ERR unknown command 'FOOO'",
detail: null,
},
{
operation: "command_execution",
connectionName: "redis-local-fixture",
databaseLabel: "db0",
commandName: "fooo",
},
),
).toEqual({
tone: "danger",
message:
"Redis returned an error while running FOOO on redis-local-fixture / db0. Adjust the Redis input first; retrying the same command unchanged is unlikely to help. Backend detail: ERR unknown command 'FOOO'.",
});
});
});

View File

@@ -0,0 +1,205 @@
export type NoticeTone = "neutral" | "accent" | "danger";
export type OperatorNotice = {
tone: NoticeTone;
message: string;
};
export type BackendErrorCode =
| "invalid_connection_config"
| "unsupported_tls_mode"
| "unsupported_operation"
| "connection_failed"
| "authentication_failed"
| "command_failed"
| "internal";
export type BackendError = {
code: BackendErrorCode;
message: string;
detail: string | null;
};
export type BackendErrorContext =
| {
operation: "connection_test";
connectionName: string;
databaseLabel: string;
}
| {
operation: "key_browse";
connectionName: string;
databaseLabel: string;
}
| {
operation: "value_read" | "value_write" | "ttl_update" | "ttl_remove" | "inspector_refresh";
connectionName: string;
databaseLabel: string;
keyName: string;
}
| {
operation: "command_execution";
connectionName: string;
databaseLabel: string;
commandName: string;
};
export function createNeutralNotice(message: string): OperatorNotice {
return {
tone: "neutral",
message,
};
}
export function createAccentNotice(message: string): OperatorNotice {
return {
tone: "accent",
message,
};
}
export function createDangerNotice(message: string): OperatorNotice {
return {
tone: "danger",
message,
};
}
export function toBackendError(error: unknown): BackendError {
if (
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof error.message === "string"
) {
return {
code: normalizeBackendErrorCode("code" in error ? error.code : undefined),
message: error.message,
detail:
"detail" in error && typeof error.detail === "string"
? error.detail
: null,
};
}
return {
code: "internal",
message: error instanceof Error ? error.message : "Unknown backend error.",
detail: null,
};
}
export function toBackendErrorNotice(
error: unknown,
context: BackendErrorContext,
): OperatorNotice {
const backendError = toBackendError(error);
const tone =
backendError.code === "invalid_connection_config" ||
backendError.code === "unsupported_operation" ||
backendError.code === "unsupported_tls_mode"
? "neutral"
: "danger";
const headline = buildHeadline(backendError, context);
const hint = buildHint(backendError.code, context);
const detail = buildTechnicalDetail(backendError);
return {
tone,
message: [headline, hint, detail].filter(Boolean).join(" "),
};
}
function normalizeBackendErrorCode(value: unknown): BackendErrorCode {
switch (value) {
case "invalid_connection_config":
case "unsupported_tls_mode":
case "unsupported_operation":
case "connection_failed":
case "authentication_failed":
case "command_failed":
case "internal":
return value;
default:
return "internal";
}
}
function buildHeadline(error: BackendError, context: BackendErrorContext) {
const target = describeContext(context);
switch (error.code) {
case "invalid_connection_config":
return `The request for ${target} was rejected before Redis was contacted.`;
case "unsupported_tls_mode":
return `The request for ${target} requires TLS handling that this foundation build does not support yet.`;
case "unsupported_operation":
return `The current desktop contract does not allow ${target} yet.`;
case "connection_failed":
return `Redis could not be reached while ${target}.`;
case "authentication_failed":
return `Redis rejected the credentials used while ${target}.`;
case "command_failed":
return `Redis returned an error while ${target}.`;
case "internal":
return `The desktop bridge failed while ${target}.`;
default:
return `The desktop bridge failed while ${target}.`;
}
}
function buildHint(code: BackendErrorCode, context: BackendErrorContext) {
switch (code) {
case "invalid_connection_config":
return "Check host, port, database, search pattern, and key input before retrying.";
case "unsupported_tls_mode":
return "Keep TLS disabled in this foundation build unless product scope explicitly adds transport controls.";
case "unsupported_operation":
return "Stay on the visible read-only path or wait for a backend contract change before retrying.";
case "connection_failed":
return `Verify that ${context.connectionName} is reachable from this machine and that the selected database is available.`;
case "authentication_failed":
return "Verify username, password, and ACL scope for the selected connection.";
case "command_failed":
return "Adjust the Redis input first; retrying the same command unchanged is unlikely to help.";
case "internal":
return "Retry once, then capture the QA snapshot and escalate if the same failure repeats.";
default:
return "";
}
}
function buildTechnicalDetail(error: BackendError) {
const detail = error.detail && error.detail !== error.message ? error.detail : null;
const summary = detail ? `${error.message} (${detail})` : error.message;
return `Backend detail: ${ensureTrailingPeriod(summary)}`;
}
function describeContext(context: BackendErrorContext) {
switch (context.operation) {
case "connection_test":
return `testing ${context.connectionName} on ${context.databaseLabel}`;
case "key_browse":
return `loading keys from ${context.connectionName} / ${context.databaseLabel}`;
case "value_read":
return `reading ${context.keyName} on ${context.connectionName} / ${context.databaseLabel}`;
case "value_write":
return `saving ${context.keyName} on ${context.connectionName} / ${context.databaseLabel}`;
case "ttl_update":
return `updating TTL for ${context.keyName} on ${context.connectionName} / ${context.databaseLabel}`;
case "ttl_remove":
return `removing TTL from ${context.keyName} on ${context.connectionName} / ${context.databaseLabel}`;
case "inspector_refresh":
return `refreshing ${context.keyName} on ${context.connectionName} / ${context.databaseLabel}`;
case "command_execution":
return `running ${context.commandName.toUpperCase()} on ${context.connectionName} / ${context.databaseLabel}`;
default:
return "handling a desktop Redis request";
}
}
function ensureTrailingPeriod(value: string) {
return /[.!?]$/.test(value) ? value : `${value}.`;
}