feat(desktop): harden connection session lifecycle

- add a visible connection session summary for ready/testing/demo/retry states
- prevent stale live connection tests from overwriting a newer active DB selection
- include the missing frontend helpers already referenced by App.tsx so the branch builds cleanly

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Senior Frontend Engineer
2026-03-31 09:32:47 +00:00
parent 35040ef0c9
commit 4839165657
13 changed files with 1233 additions and 87 deletions

View File

@@ -3,6 +3,7 @@ import {
startTransition,
useDeferredValue,
useEffect,
useRef,
useState,
type ChangeEvent,
type FormEvent,
@@ -82,6 +83,14 @@ import {
updateDemoStringValue,
} from "./lib/demo-workspace";
import { buildDatabaseSwitcherLabels } from "./lib/database-switcher";
import {
applyConnectionTestFailure,
applyConnectionTestSuccess,
buildConnectionSessionSummary,
formatConnectionCardDetailLabel,
formatConnectionStatusLabel,
markConnectionTesting,
} from "./lib/connection-session";
import {
createDemoKeyRecord,
findDuplicateKeyName,
@@ -493,7 +502,9 @@ function App() {
const [addKeyDialogOpen, setAddKeyDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [pendingMutation, setPendingMutation] = useState<PendingMutationGuard | null>(null);
const [isConnectionTestRunning, setIsConnectionTestRunning] = useState(false);
const [connectionTestState, setConnectionTestState] = useState<{ connectionId: string } | null>(
null,
);
const [isCommandRunning, setIsCommandRunning] = useState(false);
const [isBrowseLoading, setIsBrowseLoading] = useState(false);
const [isInspectorLoading, setIsInspectorLoading] = useState(false);
@@ -528,6 +539,9 @@ function App() {
const activeConnection =
connections.find((connection) => connection.id === activeConnectionId) ??
connections[0];
const activeConnectionIdRef = useRef(activeConnectionId);
const activeConnectionNameRef = useRef(activeConnection.name);
const activeDbRef = useRef(activeDb);
const databaseOptions = buildDatabaseSwitcherLabels(parseDatabaseLabel(activeDb));
const searchState = resolveKeyBrowserSearchState(search);
const deferredSearchState = resolveKeyBrowserSearchState(deferredSearch);
@@ -591,6 +605,20 @@ function App() {
ttlWriteSupported,
});
const localSmokeFixtureActive = isLocalSmokeFixtureConnection(activeConnection);
const isConnectionTestRunning = connectionTestState !== null;
const activeConnectionTestRunning = connectionTestState?.connectionId === activeConnection.id;
const otherTestingConnectionName =
connectionTestState !== null && connectionTestState.connectionId !== activeConnection.id
? connections.find((connection) => connection.id === connectionTestState.connectionId)?.name ??
"another connection"
: null;
const connectionSessionSummary = buildConnectionSessionSummary({
connection: activeConnection,
databaseLabel: activeDb,
tauriAvailable,
activeTestRunning: activeConnectionTestRunning,
otherTestingConnectionName,
});
const localSmokeFilterActive = search.trim() === localSmokeFixtureSearchText;
const ttlDraftError = ttlDraft.trim() ? validateTtlDraft(ttlDraft) : null;
const nextTtlMillis =
@@ -660,6 +688,12 @@ function App() {
bannerMessage: banner.message,
});
useEffect(() => {
activeConnectionIdRef.current = activeConnectionId;
activeConnectionNameRef.current = activeConnection.name;
activeDbRef.current = activeDb;
}, [activeConnectionId, activeConnection.name, activeDb]);
useEffect(() => {
if (!tauriAvailable) {
startTransition(() => {
@@ -1538,23 +1572,14 @@ function App() {
const handleTestConnection = async () => {
const connection = activeConnection;
const request = toRedisConnectionRequest(connection, activeDb);
const databaseLabel = activeDb;
const request = toRedisConnectionRequest(connection, databaseLabel);
startTransition(() => {
setIsConnectionTestRunning(true);
setConnections((current) =>
current.map((item) =>
item.id === connection.id
? {
...item,
status: "checking",
detail: "Live test in progress",
}
: item,
),
);
setConnectionTestState({ connectionId: connection.id });
setConnections((current) => markConnectionTesting(current, connection.id));
showNeutralBanner(
`Testing ${connection.name} against ${activeDb} using the current desktop contract.`,
`Testing ${connection.name} against ${databaseLabel} using the current desktop contract.`,
);
});
@@ -1574,7 +1599,7 @@ function App() {
showNeutralBanner(
`Tauri runtime is unavailable in browser dev mode, so connection testing stayed in demo fallback for ${connection.name}.`,
);
setIsConnectionTestRunning(false);
setConnectionTestState(null);
});
return;
}
@@ -1586,46 +1611,46 @@ function App() {
startTransition(() => {
setConnections((current) =>
current.map((item) =>
item.id === connection.id
? {
...item,
status: "healthy",
detail: result.round_trip_status,
target: {
...item.target,
database: result.selected_database,
username: result.authenticated_as ?? item.target.username,
},
}
: item,
),
applyConnectionTestSuccess(current, connection.id, {
roundTripStatus: result.round_trip_status,
selectedDatabase: result.selected_database,
authenticatedAs: result.authenticated_as,
}),
);
setActiveDb(formatDatabaseLabel(result.selected_database));
showAccentBanner(
`Live Redis test passed for ${connection.name} on ${formatDatabaseLabel(result.selected_database)}. Round-trip status: ${result.round_trip_status}.`,
setConnectionTestState(null);
if (activeConnectionIdRef.current === connection.id) {
setActiveDb(formatDatabaseLabel(result.selected_database));
setSelectedKeyId(null);
showAccentBanner(
`Live Redis test passed for ${connection.name} on ${formatDatabaseLabel(result.selected_database)}. Round-trip status: ${result.round_trip_status}.`,
);
return;
}
showNeutralBanner(
`Live Redis test passed for ${connection.name}; current workspace stayed on ${activeConnectionNameRef.current} / ${activeDbRef.current}.`,
);
setIsConnectionTestRunning(false);
});
} catch (error) {
const errorCode = formatBackendErrorCodeLabel(toBackendError(error).code);
startTransition(() => {
setConnections((current) =>
current.map((item) =>
item.id === connection.id
? {
...item,
status: "error",
detail: formatBackendErrorCodeLabel(toBackendError(error).code),
}
: item,
),
setConnections((current) => applyConnectionTestFailure(current, connection.id, errorCode));
setConnectionTestState(null);
if (activeConnectionIdRef.current === connection.id) {
showBackendErrorBanner(error, {
operation: "connection_test",
connectionName: connection.name,
databaseLabel,
});
return;
}
showNeutralBanner(
`Live Redis test failed for ${connection.name}. The current workspace stayed on ${activeConnectionNameRef.current} / ${activeDbRef.current}; reopen ${connection.name} to retry after reviewing its card status.`,
);
showBackendErrorBanner(error, {
operation: "connection_test",
connectionName: connection.name,
databaseLabel: activeDb,
});
setIsConnectionTestRunning(false);
});
}
};
@@ -1778,7 +1803,7 @@ function App() {
<StatusPill
icon={<Activity className="h-4 w-4" />}
label="Status"
value={formatConnectionStatus(activeConnection)}
value={formatConnectionStatusLabel(activeConnection)}
/>
<StatusPill
icon={<PanelLeft className="h-4 w-4" />}
@@ -1838,7 +1863,7 @@ function App() {
<p className="text-sm font-medium text-[var(--ink-0)]">{connection.name}</p>
<p className="mt-1 text-sm text-[var(--ink-soft)]">{formatEndpoint(connection.target)}</p>
<p className="mt-2 font-mono text-xs uppercase tracking-[0.14em] text-[var(--ink-muted)]">
{formatConnectionCardDetail(connection)}
{formatConnectionCardDetailLabel(connection)}
</p>
</div>
<Badge variant={active ? "accent" : "neutral"}>{connection.environment}</Badge>
@@ -1958,9 +1983,32 @@ function App() {
onClick={handleTestConnection}
disabled={isConnectionTestRunning}
>
{isConnectionTestRunning ? "Testing..." : tauriAvailable ? "Test live" : "Test demo"}
{activeConnectionTestRunning
? "Testing..."
: isConnectionTestRunning
? "Test running elsewhere"
: tauriAvailable
? "Test live"
: "Test demo"}
</Button>
</div>
<div className="mt-3 rounded-[22px] border border-[var(--line-soft)] bg-[var(--surface-base)] px-4 py-3">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-medium text-[var(--ink-0)]">Connection session</p>
<p className="mt-1 text-sm text-[var(--ink-soft)]">
{connectionSessionSummary.summary}
</p>
</div>
<Badge variant={connectionSessionSummary.badgeVariant}>
{connectionSessionSummary.badgeLabel}
</Badge>
</div>
<p className="mt-3 text-sm text-[var(--ink-soft)]">
{connectionSessionSummary.hint}
</p>
</div>
</section>
<section>
@@ -2888,38 +2936,6 @@ function formatEndpoint(target: ConnectionProfile["target"]) {
return `${target.host}:${target.port}`;
}
function formatConnectionStatus(connection: ConnectionProfile) {
if (connection.status === "healthy") {
return `Healthy · ${connection.detail}`;
}
if (connection.status === "checking") {
return "Testing live connection";
}
if (connection.status === "error") {
return `Error · ${connection.detail}`;
}
return connection.detail;
}
function formatConnectionCardDetail(connection: ConnectionProfile) {
if (connection.status === "checking") {
return "live test in progress";
}
if (connection.status === "healthy") {
return `checked ${connection.detail}`;
}
if (connection.status === "error") {
return `failed ${connection.detail}`;
}
return connection.detail;
}
function formatBackendErrorCodeLabel(code: string) {
return code.replace(/_/g, " ");
}