feat(desktop): clarify inspector readability path

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Senior Frontend Engineer
2026-03-31 08:23:13 +00:00
parent bf6f8bc0bf
commit 969744733f
8 changed files with 679 additions and 27 deletions

View File

@@ -57,6 +57,10 @@ import {
buildStructuredInspectorSections,
type StructuredInspectorSection,
} from "./lib/structured-inspector";
import {
buildInspectorReadabilitySummary,
type InspectorFact,
} from "./lib/inspector-readability";
import {
appendWorkspaceKeyRecords,
filterWorkspaceKeys,
@@ -475,14 +479,25 @@ function App() {
!isInspectorLoading &&
structuredInspectorSections.length > 0 &&
!isEditable;
const supportNote = tauriAvailable
? buildLiveSupportNote(selectedValueRecord, selectedKey)
: (selectedKey?.summary ??
`Search currently returns zero keys in ${activeDb}. This is an intentional empty state, not a backend failure.`);
const ttlWriteSupported = tauriAvailable
? selectedValueRecord?.ttl_write_supported === true
: selectedKey !== null;
const inspectorReadability = buildInspectorReadabilitySummary({
databaseLabel: activeDb,
runtimeMode: tauriAvailable ? "live" : "demo",
selectedKey:
selectedKey === null
? null
: {
name: selectedKey.name,
type: selectedKey.type,
ttl: selectedKey.ttl,
},
record: selectedValueRecord,
isLoading: isInspectorLoading,
isEditable,
ttlWriteSupported,
});
const localSmokeFixtureActive = isLocalSmokeFixtureConnection(activeConnection);
const localSmokeFilterActive = search.trim() === localSmokeFixtureSearchText;
const ttlDraftError = ttlDraft.trim() ? validateTtlDraft(ttlDraft) : null;
@@ -1843,6 +1858,7 @@ function App() {
<div className="flex items-center gap-2">
<Badge variant="neutral">{selectedKey.type}</Badge>
<Badge variant="neutral">TTL {selectedKey.ttl}</Badge>
<Badge variant="accent">{inspectorReadability.status}</Badge>
</div>
) : null}
</div>
@@ -1852,16 +1868,19 @@ function App() {
<div className="flex items-center justify-between gap-3">
<p className="text-sm font-medium text-[var(--ink-0)]">Value surface</p>
<span className="font-mono text-xs text-[var(--ink-muted)]">
{selectedKey
? isInspectorLoading
? "loading"
: isEditable
? "editable"
: "read only"
: "awaiting selection"}
{inspectorReadability.status}
</span>
</div>
<div className="mt-3 rounded-[20px] border border-[var(--line-soft)] bg-[var(--surface-muted)] px-4 py-3">
<p className="text-sm font-medium text-[var(--ink-0)]">
{inspectorReadability.surfaceTitle}
</p>
<p className="mt-1 text-sm text-[var(--ink-soft)]">
{inspectorReadability.surfaceDescription}
</p>
</div>
{selectedKey ? (
showStructuredInspector ? (
<StructuredInspectorPanels sections={structuredInspectorSections} />
@@ -1939,17 +1958,29 @@ function App() {
</DialogContent>
</Dialog>
</div>
<div className="mt-3 rounded-[20px] border border-dashed border-[var(--line-soft)] bg-[var(--surface-muted)] px-4 py-3">
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-[var(--ink-muted)]">
{inspectorReadability.actionTitle}
</p>
<p className="mt-2 text-sm text-[var(--ink-soft)]">
{inspectorReadability.actionDescription}
</p>
</div>
</div>
<div className="space-y-4">
<InfoCard
title="Key metadata"
<InspectorSummaryCard
title="Inspect summary"
icon={<CircleAlert className="h-4 w-4" />}
surfaceDescription={inspectorReadability.surfaceDescription}
actionTitle={inspectorReadability.actionTitle}
actionDescription={inspectorReadability.actionDescription}
/>
<InspectorFactsCard
title="Key facts"
icon={<TimerReset className="h-4 w-4" />}
lines={[
`Active DB: ${activeDb}`,
`TTL: ${selectedKey?.ttl ?? "no active key"}`,
`Editor support: ${selectedKey ? (isEditable ? "string write enabled" : "inspection only") : "awaiting matching selection"}`,
]}
facts={inspectorReadability.facts}
/>
<div className="rounded-[24px] border border-[var(--line-soft)] bg-[var(--surface-base)] p-4">
<div className="flex items-center gap-2">
@@ -2014,11 +2045,6 @@ function App() {
</div>
</div>
</div>
<InfoCard
title="Support note"
icon={<CircleAlert className="h-4 w-4" />}
lines={[supportNote]}
/>
</div>
</div>
</div>
@@ -2150,6 +2176,59 @@ function InfoCard({
);
}
function InspectorSummaryCard({
title,
icon,
surfaceDescription,
actionTitle,
actionDescription,
}: {
title: string;
icon: ReactElement;
surfaceDescription: string;
actionTitle: string;
actionDescription: string;
}) {
return (
<div className="rounded-[24px] border border-[var(--line-soft)] bg-[var(--surface-base)] p-4">
<div className="flex items-center gap-2">
<span className="text-[var(--accent-strong)]">{icon}</span>
<p className="text-sm font-medium text-[var(--ink-0)]">{title}</p>
</div>
<div className="mt-3 space-y-4">
<InfoLine label="Current path" value={surfaceDescription} />
<InfoLine label={actionTitle} value={actionDescription} />
</div>
</div>
);
}
function InspectorFactsCard({
title,
icon,
facts,
}: {
title: string;
icon: ReactElement;
facts: InspectorFact[];
}) {
return (
<div className="rounded-[24px] border border-[var(--line-soft)] bg-[var(--surface-base)] p-4">
<div className="flex items-center gap-2">
<span className="text-[var(--accent-strong)]">{icon}</span>
<p className="text-sm font-medium text-[var(--ink-0)]">{title}</p>
</div>
<div className="mt-3 grid gap-3 sm:grid-cols-2">
{facts.map((fact) => (
<InfoLine key={fact.label} label={fact.label} value={fact.value} />
))}
</div>
</div>
);
}
function StructuredInspectorPanels({
sections,
}: {

View File

@@ -0,0 +1,183 @@
import { describe, expect, it } from "vitest";
import { buildInspectorReadabilitySummary } from "./inspector-readability";
describe("inspector readability summary", () => {
it("guides the operator when no key is selected", () => {
expect(
buildInspectorReadabilitySummary({
databaseLabel: "db4",
runtimeMode: "live",
selectedKey: null,
record: null,
isLoading: false,
isEditable: false,
ttlWriteSupported: false,
}),
).toEqual(
expect.objectContaining({
status: "awaiting selection",
surfaceTitle: "No key selected",
actionTitle: "Next step",
facts: expect.arrayContaining([
{ label: "Scope", value: "db4" },
{ label: "Type", value: "unknown" },
]),
}),
);
});
it("describes editable live strings with explicit write semantics", () => {
expect(
buildInspectorReadabilitySummary({
databaseLabel: "db0",
runtimeMode: "live",
selectedKey: {
name: "cache:homepage",
type: "string",
ttl: "48s",
},
record: {
metadata: {
key_type: "string",
ttl: { kind: "expires_in_millis", value: 48_000 },
},
data: {
kind: "string",
value: { kind: "string", value: "payload" },
},
},
isLoading: false,
isEditable: true,
ttlWriteSupported: true,
}),
).toEqual({
status: "editable string",
surfaceTitle: "String value",
surfaceDescription:
"The full live string payload is loaded in the inspector. Saving replaces the entire string value and preserves the active TTL boundary.",
actionTitle: "Operator path",
actionDescription:
"Review the full payload, make bounded edits, and use Save string value when you are ready to replace the current value.",
facts: [
{ label: "Scope", value: "db0" },
{ label: "Type", value: "string" },
{ label: "TTL state", value: "48s" },
{ label: "Value shape", value: "string payload" },
{ label: "Write path", value: "replace full string" },
{ label: "TTL control", value: "available" },
],
});
});
it("describes structured live values as readable inspection-only surfaces", () => {
expect(
buildInspectorReadabilitySummary({
databaseLabel: "db0",
runtimeMode: "live",
selectedKey: {
name: "session:42",
type: "hash",
ttl: "13m",
},
record: {
metadata: {
key_type: "hash",
ttl: { kind: "expires_in_millis", value: 780_000 },
},
data: {
kind: "hash",
entries: [
{
field: { kind: "string", value: "region" },
value: { kind: "string", value: "eu-west-1" },
},
{
field: { kind: "string", value: "role" },
value: { kind: "string", value: "editor" },
},
],
},
},
isLoading: false,
isEditable: false,
ttlWriteSupported: true,
}),
).toEqual(
expect.objectContaining({
status: "structured read only",
surfaceTitle: "Hash fields",
facts: [
{ label: "Scope", value: "db0" },
{ label: "Type", value: "hash" },
{ label: "TTL state", value: "13m" },
{ label: "Value shape", value: "2 fields" },
{ label: "Write path", value: "inspection only" },
{ label: "TTL control", value: "available" },
],
}),
);
});
it("keeps demo mode explicit so shell review is not confused with live evidence", () => {
expect(
buildInspectorReadabilitySummary({
databaseLabel: "db0",
runtimeMode: "demo",
selectedKey: {
name: "cache:homepage",
type: "string",
ttl: "48s",
},
record: null,
isLoading: false,
isEditable: true,
ttlWriteSupported: true,
}),
).toEqual(
expect.objectContaining({
status: "demo editable",
surfaceTitle: "String value",
facts: expect.arrayContaining([
{ label: "Write path", value: "session-local string save" },
{ label: "TTL control", value: "available" },
]),
}),
);
});
it("makes stale browse-to-read gaps explicit when the key disappears", () => {
expect(
buildInspectorReadabilitySummary({
databaseLabel: "db2",
runtimeMode: "live",
selectedKey: {
name: "jobs:failed",
type: "list",
ttl: "persistent",
},
record: {
metadata: {
key_type: "list",
ttl: { kind: "missing" },
},
data: {
kind: "missing",
},
},
isLoading: false,
isEditable: false,
ttlWriteSupported: false,
}),
).toEqual(
expect.objectContaining({
status: "missing key",
surfaceTitle: "Key disappeared after browse",
facts: expect.arrayContaining([
{ label: "Scope", value: "db2" },
{ label: "TTL state", value: "missing" },
{ label: "Write path", value: "not available" },
]),
}),
);
});
});

View File

@@ -0,0 +1,340 @@
type RedisKeyTtl =
| { kind: "persistent" }
| { kind: "missing" }
| { kind: "expires_in_millis"; value: number };
type RedisValueContent =
| { kind: "string"; value: string }
| { kind: "binary"; bytes: number[] };
type RedisFieldValueEntry = {
field: RedisValueContent;
value: RedisValueContent;
};
type RedisSortedSetEntry = {
member: RedisValueContent;
score: string;
};
type RedisStreamEntry = {
id: string;
fields: RedisFieldValueEntry[];
};
type RedisValueData =
| { kind: "missing" }
| { kind: "string"; value: RedisValueContent }
| { kind: "hash"; entries: RedisFieldValueEntry[] }
| { kind: "list"; items: RedisValueContent[] }
| { kind: "set"; members: RedisValueContent[] }
| { kind: "sorted_set"; entries: RedisSortedSetEntry[] }
| { kind: "stream"; entries: RedisStreamEntry[] };
type RedisValueRecord = {
metadata: {
key_type: string;
ttl: RedisKeyTtl;
};
data: RedisValueData;
};
type SelectedInspectorKey = {
name: string;
type: string;
ttl: string;
};
export type InspectorFact = {
label: string;
value: string;
};
export type InspectorReadabilitySummary = {
status: string;
surfaceTitle: string;
surfaceDescription: string;
actionTitle: string;
actionDescription: string;
facts: InspectorFact[];
};
export type InspectorReadabilityContext = {
databaseLabel: string;
runtimeMode: "live" | "demo";
selectedKey: SelectedInspectorKey | null;
record: RedisValueRecord | null;
isLoading: boolean;
isEditable: boolean;
ttlWriteSupported: boolean;
};
export function buildInspectorReadabilitySummary(
context: InspectorReadabilityContext,
): InspectorReadabilitySummary {
const keyType = resolveKeyType(context);
const ttlState = resolveTtlState(context);
const valueShape = describeValueShape(context);
const writePath = describeWritePath(context);
const ttlPath = context.selectedKey
? context.ttlWriteSupported
? "available"
: "not available"
: "awaiting selection";
const facts: InspectorFact[] = [
{ label: "Scope", value: context.databaseLabel },
{ label: "Type", value: formatRedisKeyTypeLabel(keyType) },
{ label: "TTL state", value: ttlState },
{ label: "Value shape", value: valueShape },
{ label: "Write path", value: writePath },
{ label: "TTL control", value: ttlPath },
];
if (!context.selectedKey) {
return {
status: "awaiting selection",
surfaceTitle: "No key selected",
surfaceDescription:
"Pick a key from the browser to load its metadata, value shape, and TTL state into the inspector.",
actionTitle: "Next step",
actionDescription:
"If the browser is empty, clear or adjust the current search so the inspector has a live target again.",
facts,
};
}
if (context.runtimeMode === "demo") {
return {
status: context.isEditable ? "demo editable" : "demo read only",
surfaceTitle: buildSurfaceTitle(context.selectedKey.type, false),
surfaceDescription: context.isEditable
? "Browser dev mode is showing a session-local string payload. Layout review is safe here, but no live Redis mutation occurs."
: `${formatRedisKeyTypeLabel(context.selectedKey.type)} payloads stay on the demo inspection path in browser mode so the UI can be reviewed without pretending a live backend exists.`,
actionTitle: "Operator path",
actionDescription: context.isEditable
? "Use Save string value only for session-local shell review. Switch to the desktop runtime before treating the result as Redis evidence."
: "Review the current structure here, then use the desktop runtime when you need live Redis data or verification.",
facts,
};
}
if (context.isLoading) {
return {
status: "loading",
surfaceTitle: `Loading ${formatRedisKeyTypeLabel(keyType)} value`,
surfaceDescription:
"The desktop bridge is reading the current key so the inspector can show live metadata, value shape, and write capability together.",
actionTitle: "Operator path",
actionDescription:
"Wait for the read to finish before editing or relying on the TTL state shown in the inspector.",
facts,
};
}
if (!context.record) {
return {
status: "metadata only",
surfaceTitle: `${formatRedisKeyTypeLabel(keyType)} metadata ready`,
surfaceDescription:
"The browser still has metadata for the selected key, but the live value surface is not currently loaded in the inspector.",
actionTitle: "Operator path",
actionDescription:
"Use Refresh metadata to retry the live read, or return to the browser if the key may have changed upstream.",
facts,
};
}
if (context.record.data.kind === "missing") {
return {
status: "missing key",
surfaceTitle: "Key disappeared after browse",
surfaceDescription:
"The key was visible during browse but no longer existed when the inspector requested the live value.",
actionTitle: "Operator path",
actionDescription:
"Refresh metadata or reload the browser list to clear the stale selection before taking further action.",
facts,
};
}
if (context.isEditable) {
return {
status: "editable string",
surfaceTitle: "String value",
surfaceDescription:
"The full live string payload is loaded in the inspector. Saving replaces the entire string value and preserves the active TTL boundary.",
actionTitle: "Operator path",
actionDescription:
"Review the full payload, make bounded edits, and use Save string value when you are ready to replace the current value.",
facts,
};
}
return {
status: "structured read only",
surfaceTitle: buildSurfaceTitle(context.record.data.kind, true),
surfaceDescription: buildStructuredDescription(context.record.data.kind),
actionTitle: "Operator path",
actionDescription:
"Use the structured panels to scan the live payload quickly. Mutations for this key type remain outside the current desktop contract.",
facts,
};
}
function resolveKeyType(context: InspectorReadabilityContext) {
return context.record?.metadata.key_type ?? context.selectedKey?.type ?? "unknown";
}
function resolveTtlState(context: InspectorReadabilityContext) {
if (context.record) {
return formatRedisKeyTtl(context.record.metadata.ttl);
}
return context.selectedKey?.ttl ?? "awaiting selection";
}
function describeValueShape(context: InspectorReadabilityContext) {
if (!context.selectedKey) {
return "no active key";
}
if (context.runtimeMode === "demo") {
return context.isEditable
? "session-local string payload"
: `${formatRedisKeyTypeLabel(context.selectedKey.type)} demo preview`;
}
if (context.isLoading) {
return "live value request in flight";
}
if (!context.record) {
return "metadata loaded, value unavailable";
}
switch (context.record.data.kind) {
case "missing":
return "key missing";
case "string":
return "string payload";
case "hash":
return `${context.record.data.entries.length} field${context.record.data.entries.length === 1 ? "" : "s"}`;
case "list":
return `${context.record.data.items.length} item${context.record.data.items.length === 1 ? "" : "s"}`;
case "set":
return `${context.record.data.members.length} member${context.record.data.members.length === 1 ? "" : "s"}`;
case "sorted_set":
return `${context.record.data.entries.length} scored member${context.record.data.entries.length === 1 ? "" : "s"}`;
case "stream":
return `${context.record.data.entries.length} entr${context.record.data.entries.length === 1 ? "y" : "ies"}`;
default:
return "unknown";
}
}
function describeWritePath(context: InspectorReadabilityContext) {
if (!context.selectedKey) {
return "awaiting selection";
}
if (context.runtimeMode === "demo") {
return context.isEditable ? "session-local string save" : "inspection only";
}
if (context.isLoading) {
return "pending live capability";
}
if (!context.record) {
return "value unavailable";
}
if (context.record.data.kind === "missing") {
return "not available";
}
return context.isEditable ? "replace full string" : "inspection only";
}
function buildSurfaceTitle(kind: string, liveStructured: boolean) {
switch (kind) {
case "hash":
return "Hash fields";
case "list":
return "List items";
case "set":
return liveStructured ? "Set members" : "Set preview";
case "sorted_set":
case "zset":
return "Sorted set members";
case "stream":
return "Stream entries";
case "string":
return "String value";
default:
return "Value surface";
}
}
function buildStructuredDescription(kind: string) {
switch (kind) {
case "hash":
return "Live HGETALL output is rendered as field/value rows so operators do not need to parse raw JSON during inspection.";
case "list":
return "Live LRANGE output is rendered in list order so operators can scan indexed items without leaving the inspector.";
case "set":
return "Live SMEMBERS output is rendered as a structured member list so the inspector stays readable even when the payload is dense.";
case "sorted_set":
return "Live ZRANGE output is rendered as member and score pairs so ordering semantics stay visible in the inspector.";
case "stream":
return "Live XRANGE output is rendered as entry rows with inline field payloads so stream structure is visible at a glance.";
default:
return "The live value is available on a structured read-only path in the current desktop contract.";
}
}
function formatRedisKeyTtl(ttl: RedisKeyTtl) {
switch (ttl.kind) {
case "persistent":
return "persistent";
case "missing":
return "missing";
case "expires_in_millis":
return formatDurationFromMillis(ttl.value);
default:
return "unknown";
}
}
function formatDurationFromMillis(value: number) {
if (value < 1_000) {
return `${value}ms`;
}
const totalSeconds = Math.floor(value / 1_000);
if (totalSeconds < 60) {
return `${totalSeconds}s`;
}
const totalMinutes = Math.floor(totalSeconds / 60);
if (totalMinutes < 60) {
return `${totalMinutes}m`;
}
const totalHours = Math.floor(totalMinutes / 60);
if (totalHours < 24) {
return `${totalHours}h`;
}
const totalDays = Math.floor(totalHours / 24);
return `${totalDays}d`;
}
function formatRedisKeyTypeLabel(value: string) {
switch (value) {
case "zset":
return "sorted set";
default:
return value || "unknown";
}
}