feat(desktop): add mutation review guardrails

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Senior Frontend Engineer
2026-03-31 08:47:14 +00:00
parent bf6f8bc0bf
commit 35040ef0c9
7 changed files with 1084 additions and 125 deletions

View File

@@ -37,6 +37,7 @@ pnpm run desktop:dev
pnpm run desktop:build pnpm run desktop:build
pnpm run desktop:tauri:dev pnpm run desktop:tauri:dev
pnpm run desktop:tauri:build pnpm run desktop:tauri:build
pnpm run desktop:tauri:build:all
``` ```
说明: 说明:
@@ -45,9 +46,10 @@ pnpm run desktop:tauri:build
- 仓库通过 `.npmrc` 固定安装 devDependencies避免宿主机的 `NODE_ENV=production` 破坏桌面构建链路。 - 仓库通过 `.npmrc` 固定安装 devDependencies避免宿主机的 `NODE_ENV=production` 破坏桌面构建链路。
- `cargo test -p redis-core` 默认验证共享后端核心,包括 mock Redis 握手与命令执行测试,避免在缺少桌面系统库时阻断后端迭代。 - `cargo test -p redis-core` 默认验证共享后端核心,包括 mock Redis 握手与命令执行测试,避免在缺少桌面系统库时阻断后端迭代。
- `pnpm --filter @redis-gui/desktop test` 运行桌面前端的最小单测基线;当前覆盖共享错误映射层,避免后续功能迭代把后端错误码重新退回成零散文案拼接。 - `pnpm --filter @redis-gui/desktop test` 运行桌面前端的最小单测基线;当前覆盖共享错误映射层,避免后续功能迭代把后端错误码重新退回成零散文案拼接。
- `pnpm run desktop:dev` 启动浏览器态工作台壳层,用于检查布局、交互和 demo fallback此模式下没有 Tauri 命令桥。 - `pnpm run desktop:dev` 启动浏览器态工作台壳层,用于检查布局、交互和 demo fallback此模式下没有 Tauri 命令桥,但会在当前会话内保留 mock string 保存和 TTL 调整结果,便于做壳层级交互验收
- `pnpm run desktop:tauri:dev` 启动带 Tauri 后端桥的桌面工作台;连接测试和命令执行会调用现有 `test_redis_connection``execute_redis_command` 命令,因此需要完整的 Tauri Linux 运行依赖。 - `pnpm run desktop:tauri:dev` 启动带 Tauri 后端桥的桌面工作台;连接测试和命令执行会调用现有 `test_redis_connection``execute_redis_command` 命令,因此需要完整的 Tauri Linux 运行依赖。
- `pnpm run desktop:tauri:build` 会先执行桌面前端 build再进入 Tauri/Rust 的真实打包流程;当前 Linux `.deb``.rpm` 产物位于仓库根 `target/release/bundle/` - `pnpm run desktop:tauri:build` 是当前推荐的稳定 Linux 打包入口,只生成已验证可出的 `.deb``.rpm` 产物;产物位于仓库根 `target/release/bundle/`
- `pnpm run desktop:tauri:build:all` 保留完整 bundle 调查路径;当前会在 AppImage 的 `linuxdeploy` 阶段失败,因此不应作为默认 QA 打包命令。
- Linux 上进入 Tauri 打包前,可先用 `pkg-config --modversion gtk+-3.0 webkit2gtk-4.1 libsoup-3.0` 做原生依赖预检查;任一缺失都会阻断桌面包产物生成。 - Linux 上进入 Tauri 打包前,可先用 `pkg-config --modversion gtk+-3.0 webkit2gtk-4.1 libsoup-3.0` 做原生依赖预检查;任一缺失都会阻断桌面包产物生成。
## Local Redis Smoke Fixture ## Local Redis Smoke Fixture
@@ -79,9 +81,15 @@ pnpm run desktop:tauri:dev
## Current Frontend Scope ## Current Frontend Scope
- 首版可见界面现在已接入真实的桌面 browse / inspect / string save / TTL update 路径,但仍保持危险操作和完整产品化流程在后续阶段演进。 - 首版可见界面现在已接入真实的桌面 browse / inspect / string save / TTL update 路径,但仍保持危险操作和完整产品化流程在后续阶段演进。
- 当前前端消费 `backend_bootstrap``test_redis_connection``browse_redis_keys``read_redis_value``write_redis_value``execute_redis_command`;浏览器态仍保持明确的 demo fallback。 - 当前前端消费 `backend_bootstrap``test_redis_connection``browse_redis_keys``read_redis_value``create_redis_value``write_redis_value``update_redis_key_ttl``execute_redis_command`;浏览器态仍保持明确的 demo fallback。
- 当前仓库还包含一个 string-only 的 `Add key` 可见入口,走 `create_redis_value` 合约;该能力已在仓库中实现,但阶段验收仍需按 `docs/redis-gui-v1-product-requirements.md``docs/qa/acceptance-matrix.md` 单独判断,不应自动算入当前基线签收。
- 当前前端还通过共享错误映射层统一消费 `redis-core` 的稳定错误码使连接测试、浏览、读取、保存、TTL 和命令执行失败都落到一致的操作提示上。 - 当前前端还通过共享错误映射层统一消费 `redis-core` 的稳定错误码使连接测试、浏览、读取、保存、TTL 和命令执行失败都落到一致的操作提示上。
- `inspect_redis_key``update_redis_key_ttl` 已在桌面桥可用;当前 UI 已暴露 TTL 设置与移除控制,但删除执行仍保持未开放状态。 - `inspect_redis_key``update_redis_key_ttl` 已在桌面桥可用;当前 UI 已暴露 TTL 设置与移除控制,但删除执行仍保持未开放状态。
- 当前 phase 的 string 保存、TTL 设置和 TTL 移除在执行前都会进入 scoped review重复展示 connection / DB / key 语境与 current-vs-next 变化,并阻止 no-op 提交直接触达 live bridge。
- inspector 现在对 `hash/list/set/zset/stream` 提供结构化只读面板,不再要求操作者从原始 JSON 文本中手动解析 typed value。 - inspector 现在对 `hash/list/set/zset/stream` 提供结构化只读面板,不再要求操作者从原始 JSON 文本中手动解析 typed value。
- inspector 现在还会明确显示当前 inspect 状态、value surface 语义、下一步操作路径和 key facts降低 string / structured / missing / demo fallback 场景下的误读成本。
- DB switcher 现在默认覆盖 `db0``db15`,并会在当前作用域落到更高 DB 时保留对应选项,避免 UI 退回到四个固定 DB 的弱验证面。
- 连接测试、key browse、value inspector 和命令托盘在 Tauri 桌面运行时走真实后端桥;浏览器态 `pnpm run desktop:dev` 会明确回退到 demo 数据,不伪装成真实 Redis 行为。 - 连接测试、key browse、value inspector 和命令托盘在 Tauri 桌面运行时走真实后端桥;浏览器态 `pnpm run desktop:dev` 会明确回退到 demo 数据,不伪装成真实 Redis 行为。
- 浏览器态现在还会在单次会话内同步 mock string 保存、TTL 设置和 TTL 移除的可见状态,保证壳层演示与 UI 反馈一致,但这仍不构成任何 live Redis 证据。
- utility rail 现在额外提供本地 smoke assist 面板,可直接切换到 `127.0.0.1:6380 / db0` 的 fixture 路径、聚焦 `smoke:*` 键,并预填安全 smoke 命令。
- 具体 UI/UX 边界与 QA 验收入口见 `docs/frontend-workspace-baseline.md``docs/qa/acceptance-matrix.md` - 具体 UI/UX 边界与 QA 验收入口见 `docs/frontend-workspace-baseline.md``docs/qa/acceptance-matrix.md`

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
import { describe, expect, it } from "vitest";
import {
buildMutationGuardDialogContent,
canRemoveTtl,
hasMeaningfulStringChange,
hasMeaningfulTtlChange,
} from "./mutation-guardrails";
describe("mutation guardrails", () => {
it("summarizes live string replacement with current and next payload previews", () => {
expect(
buildMutationGuardDialogContent({
kind: "value_write",
runtimeMode: "live",
keyName: "cache:homepage",
databaseLabel: "db0",
currentValue: "hello",
nextValue: "hello world",
}),
).toEqual({
title: "Replace string value for cache:homepage?",
description: "Review the scoped string save for db0 before continuing.",
currentLabel: "Current value",
currentValue: '5 chars, 1 line, preview "hello"',
nextLabel: "Next value",
nextValue: '11 chars, 1 line, preview "hello world"',
warning: "This replaces the full live string value and keeps the current TTL boundary.",
confirmLabel: "Save live string",
});
});
it("summarizes TTL update in browser demo mode", () => {
expect(
buildMutationGuardDialogContent({
kind: "ttl_update",
runtimeMode: "demo",
keyName: "session:42",
databaseLabel: "db5",
currentTtl: "persistent",
nextTtl: "1m",
}),
).toEqual({
title: "Apply TTL to session:42?",
description: "Review the scoped TTL change for db5 before continuing.",
currentLabel: "Current TTL",
currentValue: "persistent",
nextLabel: "Next TTL",
nextValue: "1m",
warning: "This only updates the session-local browser demo state.",
confirmLabel: "Set demo TTL",
});
});
it("summarizes live TTL removal", () => {
expect(
buildMutationGuardDialogContent({
kind: "ttl_remove",
runtimeMode: "live",
keyName: "smoke:string:ttl",
databaseLabel: "db0",
currentTtl: "48s",
}),
).toEqual({
title: "Remove TTL from smoke:string:ttl?",
description: "Review the scoped TTL removal for db0 before continuing.",
currentLabel: "Current TTL",
currentValue: "48s",
nextLabel: "Next TTL",
nextValue: "persistent",
warning: "This removes the live expiry and makes the key persistent.",
confirmLabel: "Remove live TTL",
});
});
it("detects no-op string saves", () => {
expect(hasMeaningfulStringChange("hello", "hello")).toBe(false);
expect(hasMeaningfulStringChange("hello", "hello world")).toBe(true);
});
it("detects exact live TTL no-ops and formatted demo TTL no-ops", () => {
expect(
hasMeaningfulTtlChange({ kind: "expires_in_millis", value: 60_000 }, 60_000),
).toBe(false);
expect(hasMeaningfulTtlChange({ kind: "display", value: "1m" }, 60_000)).toBe(false);
expect(hasMeaningfulTtlChange({ kind: "display", value: "1m" }, 120_000)).toBe(true);
});
it("blocks TTL removal when the key is already persistent", () => {
expect(canRemoveTtl({ kind: "persistent" })).toBe(false);
expect(canRemoveTtl({ kind: "display", value: "persistent" })).toBe(false);
expect(canRemoveTtl({ kind: "expires_in_millis", value: 10_000 })).toBe(true);
});
});

View File

@@ -0,0 +1,178 @@
export type MutationGuardRuntimeMode = "live" | "demo";
export type ComparableTtlState =
| { kind: "persistent" }
| { kind: "missing" }
| { kind: "expires_in_millis"; value: number }
| { kind: "display"; value: string };
export type MutationGuardDialogInput =
| {
kind: "value_write";
runtimeMode: MutationGuardRuntimeMode;
keyName: string;
databaseLabel: string;
currentValue: string;
nextValue: string;
}
| {
kind: "ttl_update";
runtimeMode: MutationGuardRuntimeMode;
keyName: string;
databaseLabel: string;
currentTtl: string;
nextTtl: string;
}
| {
kind: "ttl_remove";
runtimeMode: MutationGuardRuntimeMode;
keyName: string;
databaseLabel: string;
currentTtl: string;
};
export type MutationGuardDialogContent = {
title: string;
description: string;
currentLabel: string;
currentValue: string;
nextLabel: string;
nextValue: string;
warning: string;
confirmLabel: string;
};
export function buildMutationGuardDialogContent(
input: MutationGuardDialogInput,
): MutationGuardDialogContent {
switch (input.kind) {
case "value_write":
return {
title: `Replace string value for ${input.keyName}?`,
description: `Review the scoped string save for ${input.databaseLabel} before continuing.`,
currentLabel: "Current value",
currentValue: describeStringPayload(input.currentValue),
nextLabel: "Next value",
nextValue: describeStringPayload(input.nextValue),
warning:
input.runtimeMode === "live"
? "This replaces the full live string value and keeps the current TTL boundary."
: "This only updates the session-local browser demo state.",
confirmLabel:
input.runtimeMode === "live" ? "Save live string" : "Save demo string",
};
case "ttl_update":
return {
title: `Apply TTL to ${input.keyName}?`,
description: `Review the scoped TTL change for ${input.databaseLabel} before continuing.`,
currentLabel: "Current TTL",
currentValue: input.currentTtl,
nextLabel: "Next TTL",
nextValue: input.nextTtl,
warning:
input.runtimeMode === "live"
? "This changes the live expiry for the selected key in Redis."
: "This only updates the session-local browser demo state.",
confirmLabel: input.runtimeMode === "live" ? "Set live TTL" : "Set demo TTL",
};
case "ttl_remove":
return {
title: `Remove TTL from ${input.keyName}?`,
description: `Review the scoped TTL removal for ${input.databaseLabel} before continuing.`,
currentLabel: "Current TTL",
currentValue: input.currentTtl,
nextLabel: "Next TTL",
nextValue: "persistent",
warning:
input.runtimeMode === "live"
? "This removes the live expiry and makes the key persistent."
: "This only updates the session-local browser demo state.",
confirmLabel:
input.runtimeMode === "live" ? "Remove live TTL" : "Remove demo TTL",
};
default:
return {
title: "Review mutation",
description: "Review the scoped mutation before continuing.",
currentLabel: "Current",
currentValue: "n/a",
nextLabel: "Next",
nextValue: "n/a",
warning: "",
confirmLabel: "Confirm",
};
}
}
export function hasMeaningfulStringChange(currentValue: string, nextValue: string) {
return currentValue !== nextValue;
}
export function hasMeaningfulTtlChange(
currentTtl: ComparableTtlState,
nextTtlMillis: number,
) {
switch (currentTtl.kind) {
case "expires_in_millis":
return currentTtl.value !== nextTtlMillis;
case "display":
return currentTtl.value !== formatDurationFromMillis(nextTtlMillis);
case "persistent":
case "missing":
default:
return true;
}
}
export function canRemoveTtl(currentTtl: ComparableTtlState) {
switch (currentTtl.kind) {
case "persistent":
return false;
case "display":
return currentTtl.value !== "persistent";
case "expires_in_millis":
case "missing":
default:
return true;
}
}
function describeStringPayload(value: string) {
const lines = value.split(/\r?\n/).length;
const compactPreview = value.replace(/\s+/g, " ").trim();
const preview = compactPreview ? truncateText(compactPreview, 56) : "(empty string)";
return `${value.length} chars, ${lines} line${lines === 1 ? "" : "s"}, preview ${JSON.stringify(preview)}`;
}
function truncateText(value: string, maxLength: number) {
if (value.length <= maxLength) {
return value;
}
return `${value.slice(0, maxLength - 3)}...`;
}
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`;
}

View File

@@ -47,6 +47,7 @@ Use a four-zone workspace:
- New connection form entry point - New connection form entry point
- Active DB switcher - Active DB switcher
- Safety posture and backend boundary summary - Safety posture and backend boundary summary
- Local smoke fixture assist for repeatable QA runs
### Browser pane ### Browser pane
@@ -84,29 +85,45 @@ Use a four-zone workspace:
- `apps/desktop` now runs on React and Tailwind-based styling. - `apps/desktop` now runs on React and Tailwind-based styling.
- The shell now consumes the existing Tauri bridge for `backend_bootstrap`, `test_redis_connection`, `browse_redis_keys`, `read_redis_value`, `write_redis_value`, and `execute_redis_command`. - The shell now consumes the existing Tauri bridge for `backend_bootstrap`, `test_redis_connection`, `browse_redis_keys`, `read_redis_value`, `write_redis_value`, and `execute_redis_command`.
- The inspector now consumes `update_redis_key_ttl` for live TTL set and persist actions in desktop runtime. - The inspector now consumes `update_redis_key_ttl` for live TTL set and persist actions in desktop runtime.
- The repository now also contains a visible string-only `Add key` dialog wired to `create_redis_value`, but that capability remains a separately tracked next-phase surface rather than part of this baseline milestone's required acceptance.
- shadcn-style UI primitives are used locally for buttons, inputs, textareas, badges, and dialogs. - shadcn-style UI primitives are used locally for buttons, inputs, textareas, badges, and dialogs.
- Browser-only dev mode uses explicit demo fallback so the visible shell can still be reviewed without a Tauri runtime. - Browser-only dev mode uses explicit demo fallback so the visible shell can still be reviewed without a Tauri runtime.
- Tauri desktop mode now allows live connection tests, live key browsing, live value inspection, string save for supported keys, live TTL updates, and live command execution while keeping browser dev mode on explicit demo fallback. - Tauri desktop mode now allows live connection tests, live key browsing, live value inspection, string save for supported keys, live TTL updates, and live command execution while keeping browser dev mode on explicit demo fallback.
- Non-string live values now render in structured read-only panels for `hash`, `list`, `set`, `zset`, and `stream`, so operators can scan typed payloads without reading raw JSON dumps. - Non-string live values now render in structured read-only panels for `hash`, `list`, `set`, `zset`, and `stream`, so operators can scan typed payloads without reading raw JSON dumps.
- The visible shell now validates draft-connection name, host, port, and DB input before creation, and prevents duplicate profile names inside the local shell state. - The visible shell now validates draft-connection name, host, port, and DB input before creation, and prevents duplicate profile names inside the local shell state.
- The DB switcher now exposes a predictable default operator range from `db0` through `db15` and keeps an out-of-range active DB visible instead of depending on a four-item hardcoded list.
- Zero-result key searches now place the inspector into an explicit empty state instead of leaving a stale key visible. - Zero-result key searches now place the inspector into an explicit empty state instead of leaving a stale key visible.
- Frontend error handling now routes stable backend error codes through a shared operator-facing notice layer so live failures render with consistent copy and banner tone across connection, browse, inspect, write, TTL, and command flows. - Frontend error handling now routes stable backend error codes through a shared operator-facing notice layer so live failures render with consistent copy and banner tone across connection, browse, inspect, write, TTL, and command flows.
- The inspector now also renders a dedicated readability layer with explicit inspect status, value-surface semantics, next-step guidance, and key facts so string, structured, missing, and demo-fallback states are easier to interpret.
- Current-phase writable actions now require a scoped review step before string save, TTL set, or TTL removal proceeds, and no-op submissions are blocked before they reach the live bridge or demo fallback.
- The utility rail now exposes a bounded local smoke assist surface with fixture targeting, `smoke:*` focus, safe command presets, and the seeded key list from the local smoke runbook.
- Browser-only demo mode now keeps session-local mock string-save and TTL changes visible in the browser, inspector, and mock command responses so shell review does not claim success while leaving stale UI state behind.
## QA Acceptance For This Milestone ## QA Acceptance For This Milestone
- The shell renders four distinct zones: rail, browser, inspector, command tray. - The shell renders four distinct zones: rail, browser, inspector, command tray.
- The header keeps app version, runtime mode, and build mode visible for smoke validation. - The header keeps app version, runtime mode, and build mode visible for smoke validation.
- The utility rail exposes a copyable QA snapshot with runtime, connection, selection, and latest command context. - The utility rail exposes a copyable QA snapshot with runtime, connection, selection, and latest command context.
- The utility rail exposes the seeded local smoke fixture path and safe smoke shortcuts without requiring manual re-entry from the runbook.
- The active connection and active DB stay visible without opening a modal. - The active connection and active DB stay visible without opening a modal.
- The DB switcher exposes `db0` through `db15` by default and still keeps the current DB visible when the active scope falls outside that range.
- Search filters the browser list and shows a no-results state. - Search filters the browser list and shows a no-results state.
- Search now exposes explicit mode feedback so operators can see whether the current input is being treated as a contains search or a raw Redis pattern.
- Search now exposes a bounded clear action in the browser header so operators can return to normal browse state without manually deleting the entire input.
- When search returns zero keys, the inspector switches to an empty state instead of showing the last selected key. - When search returns zero keys, the inspector switches to an empty state instead of showing the last selected key.
- Empty browser and inspector states now distinguish between an empty DB and a filtered no-match result instead of using one generic no-results sentence.
- The connection action distinguishes desktop live testing from browser demo fallback. - The connection action distinguishes desktop live testing from browser demo fallback.
- Invalid draft connections cannot be created from the visible shell. - Invalid draft connections cannot be created from the visible shell.
- Live backend failures are shown through a consistent banner treatment instead of per-surface ad hoc copy. - Live backend failures are shown through a consistent banner treatment instead of per-surface ad hoc copy.
- Non-string keys are explicitly read-only. - Non-string keys are explicitly read-only.
- Non-string keys render as structured typed panels instead of raw JSON text blobs. - Non-string keys render as structured typed panels instead of raw JSON text blobs.
- The inspector keeps current status, value semantics, action guidance, and key facts visible without requiring operators to infer meaning from badges or raw payload shape alone.
- String keys show a save affordance near the value surface. - String keys show a save affordance near the value surface.
- TTL controls stay beside the inspector metadata and expose both set and remove flows. - TTL controls stay beside the inspector metadata and expose both set and remove flows.
- String save and TTL mutations now require a second review step that repeats scoped current-vs-next state before continuing.
- Failed save and TTL attempts keep the current draft value or TTL input in place so operators can adjust and retry without rebuilding context.
- Browser demo mode keeps mock string-save and TTL changes visible for the current session while still labeling the runtime as non-live.
- Current baseline sign-off does not require the visible `Add key` surface to pass or fail; keep any Add Key evidence separate unless PM explicitly promotes it into the active milestone.
- Delete requires a second confirmation step that includes key name and DB context. - Delete requires a second confirmation step that includes key name and DB context.
- The command tray visibly blocks dangerous administrative commands. - The command tray visibly blocks dangerous administrative commands.
- Command history marks whether each result came from the live desktop bridge or the demo shell. - Command history marks whether each result came from the live desktop bridge or the demo shell.
@@ -123,7 +140,7 @@ pnpm run desktop:dev
Notes: Notes:
- `pnpm run desktop:dev` is the browser shell path. It supports layout review and demo fallback only. - `pnpm run desktop:dev` is the browser shell path. It supports layout review and session-local demo fallback only; mock save and TTL actions update visible UI state, but no Tauri or Redis mutation occurs.
- `pnpm run desktop:tauri:dev` is the desktop integration path. Use it when QA or backend needs to validate the existing command bridge against a live Redis target. - `pnpm run desktop:tauri:dev` is the desktop integration path. Use it when QA or backend needs to validate the existing command bridge against a live Redis target.
- `pnpm --filter @redis-gui/desktop test` should stay green before handing frontend foundation changes to QA or other feature teams. - `pnpm --filter @redis-gui/desktop test` should stay green before handing frontend foundation changes to QA or other feature teams.
- Use `pnpm run desktop:build` for a production bundle check before handing the shell to QA or integration work. - Use `pnpm run desktop:build` for a production bundle check before handing the shell to QA or integration work.

View File

@@ -1,38 +1,50 @@
# Redis GUI V1 Acceptance Matrix # Redis GUI V1 Acceptance Matrix
Date: 2026-03-27 Date: 2026-03-31
Owner: QA Engineer Owner: QA Engineer
## Evidence Collected On 2026-03-27 ## Evidence Collected On 2026-03-31
- A fresh `cargo test -p redis-core` rerun passes in this session with 16 tests green. - `cargo test -p redis-core` passes in this session with 20 tests green.
- `pnpm install --lockfile-only` passes at repository root after adding `pnpm-workspace.yaml`. - `pnpm --filter @redis-gui/desktop test` passes in this session with 11 files and 44 tests green.
- `pnpm run desktop:build` passes and emits `apps/desktop/dist/`. - `pnpm run desktop:build` passes and emits `apps/desktop/dist/`.
- `pnpm run desktop:tauri:build` now completes Linux `.deb` and `.rpm` artifact generation under `target/release/bundle/`, then fails later in the AppImage stage with `io: Connection reset by peer (os error 104)`. - `pnpm run desktop:dev` starts Vite on `http://127.0.0.1:1420/`, and the served HTML points to the desktop shell entrypoint.
- `pnpm run desktop:tauri:build` now completes Linux `.deb` and `.rpm` artifact generation under `target/release/bundle/`.
- `pnpm run desktop:tauri:build:all` still fails later in the AppImage stage because `linuxdeploy` aborts in the current environment.
- `dpkg-deb -I target/release/bundle/deb/Redis GUI Foundation_0.1.0_amd64.deb` confirms Debian package metadata and runtime dependencies on `libwebkit2gtk-4.1-0` and `libgtk-3-0`.
- Direct launch of `target/release/desktop-shell` in this heartbeat fails before window creation with `Failed to initialize GTK`, because the current session has no `DISPLAY`, `WAYLAND_DISPLAY`, or `xvfb-run`.
- `./scripts/redis-fixture/start.sh`, `seed.sh`, and `stop.sh` pass in this session, and the seeded fixture exposes 7 `smoke:*` keys with expected string, TTL, hash, list, set, zset, and stream evidence.
- `./scripts/redis-fixture/seed.sh` also fails correctly when the fixture container is absent, with an explicit instruction to run `start.sh` first.
- The repository still contains no `Dockerfile`, `docker-compose.yml`, `compose.yaml`, or equivalent compose asset; Docker-backed verification currently depends on the documented fixture scripts plus a reachable local Docker daemon.
- The repository now contains a Rust workspace, a React workspace UI baseline, Tauri commands for connection testing and command execution, and shared frontend scope documentation. - The repository now contains a Rust workspace, a React workspace UI baseline, Tauri commands for connection testing and command execution, and shared frontend scope documentation.
- Browser dev mode still uses demo data for shell review, while Tauri desktop mode can now exercise live connection-test, key browse, value read/write, and command-execution bridges. - Browser dev mode still uses demo data for shell review, while Tauri desktop mode can now exercise live connection-test, key browse, value read/write, and command-execution bridges.
- The checked-out desktop shell now also exposes a visible string-only `Add key` flow through `create_redis_value`, but PM phase notes still treat that capability as next-phase inventory rather than a required current-baseline acceptance item.
- Browser dev mode now also keeps mock string-save and TTL changes visible for the current session, reducing shell-review drift between action banners and visible UI state without changing the live acceptance boundary.
- The desktop DB switcher now exposes a default `db0` through `db15` range and keeps an active out-of-range DB visible in the selector.
- A repeatable local Redis fixture and smoke runbook now exist in `scripts/redis-fixture/` and `docs/qa/local-desktop-smoke.md`. - A repeatable local Redis fixture and smoke runbook now exist in `scripts/redis-fixture/` and `docs/qa/local-desktop-smoke.md`.
- `docs/frontend-workspace-baseline.md` and `docs/redis-gui-v1-product-requirements.md` now both exist in-repo.
## Matrix ## Matrix
| Scope | Happy Path Acceptance | Failure Path Acceptance | Required Evidence | Current Status | | Scope | Happy Path Acceptance | Failure Path Acceptance | Required Evidence | Current Status |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Linux app smoke | App installs or launches from a documented command, opens main window, connects to test Redis, exits cleanly | Missing Redis, bad host, bad password, unsupported DB or network loss surface actionable error and app stays stable | Launch steps, versioned artifact or dev command, screenshots, QA run log | Partially blocked: documented fixture and smoke path now exist, but a full Linux launch-and-connect evidence set is still pending | | Linux app smoke | App installs or launches from a documented command, opens main window, connects to test Redis, exits cleanly | Missing Redis, bad host, bad password, unsupported DB or network loss surface actionable error and app stays stable | Launch steps, versioned artifact or dev command, screenshots, QA run log | Partially blocked: documented fixture, package artifacts, and smoke path now exist, but this heartbeat had no GUI-capable display session, so real Tauri window launch-and-connect evidence is still pending |
| macOS app smoke | Same as Linux, using signed or documented macOS artifact | Same failure paths, plus first-run permission or gatekeeper guidance if relevant | Build artifact name, install steps, screenshots, QA run log | Blocked: no validated macOS build or artifact evidence | | macOS app smoke | Same as Linux, using signed or documented macOS artifact | Same failure paths, plus first-run permission or gatekeeper guidance if relevant | Build artifact name, install steps, screenshots, QA run log | Blocked: no validated macOS build or artifact evidence |
| Windows app smoke | Same as Linux, using installer or portable package | Same failure paths, plus Windows-specific install and uninstall validation | Build artifact name, install steps, screenshots, QA run log | Blocked: no validated Windows build or artifact evidence | | Windows app smoke | Same as Linux, using installer or portable package | Same failure paths, plus Windows-specific install and uninstall validation | Build artifact name, install steps, screenshots, QA run log | Blocked: no validated Windows build or artifact evidence |
| Desktop workspace shell | Rail, browser, inspector, and command tray render together with active connection, build identity, runtime mode, and a copyable QA snapshot visible; command history labels live vs demo results | Empty search state clears the inspector selection, read-only non-string value state, destructive confirmation, and browser-mode demo fallback stay stable and explicit | `npm run desktop:dev` or `npm run desktop:build`, `docs/frontend-workspace-baseline.md`, screenshots or copied QA snapshot text | Pass for shell-level evidence; not a substitute for Redis feature acceptance | | Desktop workspace shell | Rail, browser, inspector, and command tray render together with active connection, build identity, runtime mode, a copyable QA snapshot, local smoke assist, visible inspector summary/key facts surfaces, and scoped mutation review for current-phase write actions | Empty search state clears the inspector selection, search mode feedback and clear action stay visible and predictable, read-only non-string value state, inspector status/action guidance stays aligned to live vs demo vs missing cases, destructive confirmation, no-op write attempts are blocked before execution, and browser-mode demo fallback stays explicit while mock save and TTL actions update session-local UI state | `npm run desktop:dev` or `npm run desktop:build`, `docs/frontend-workspace-baseline.md`, screenshots or copied QA snapshot text | Pass for shell-level evidence; not a substitute for Redis feature acceptance |
| Connection management | Create a validated draft profile, keep DB context visible, and run the existing live connection-test bridge from Tauri desktop mode | Invalid host, bad port, bad password, duplicate name, timeout, and unreachable server are handled with consistent operator-facing messaging | Tauri run log, screenshots, repeatable steps, Redis target | Partially blocked: the shell now validates draft name/host/port/DB locally, maps backend error codes into shared notices, and calls the real connection-test command, but profile persistence and full evidence capture are not complete | | Connection management | Create a validated draft profile, keep DB context visible, switch among a predictable default DB range, and run the existing live connection-test bridge from Tauri desktop mode | Invalid host, bad port, bad password, duplicate name, timeout, and unreachable server are handled with consistent operator-facing messaging | Tauri run log, screenshots, repeatable steps, Redis target | Partially blocked: the shell now validates draft name/host/port/DB locally, exposes `db0` through `db15` while preserving active out-of-range DB context, maps backend error codes into shared notices, and calls the real connection-test command, but profile persistence and full evidence capture are not complete |
| Key browsing | Browse DBs and keys, paginate or virtualize large lists, view metadata | Empty DB, deleted key during browse, permission error, and connection drop do not crash UI, and live failures reuse the shared error banner treatment | Seed dataset, result screenshots, observed limits | Partially blocked: desktop UI now calls the live browse bridge with `SCAN` pagination and metadata, but fixture-backed Tauri screenshots and measured limits are still pending | | Key browsing | Browse DBs and keys, paginate or virtualize large lists, view metadata | Empty DB, deleted key during browse, permission error, and connection drop do not crash UI, and live failures reuse the shared error banner treatment | Seed dataset, result screenshots, observed limits | Partially blocked: desktop UI now calls the live browse bridge with `SCAN` pagination and metadata, but fixture-backed Tauri screenshots and measured limits are still pending |
| Search | Search by key name or pattern and return matching set within documented limits | No matches, invalid pattern, and very large result sets are handled predictably | Seed dataset, search queries, measured behavior | Partially blocked: desktop UI now maps search to live browse requests, but fixture-backed evidence and measured limits are still pending | | Search | Search by key name or pattern and return matching set within documented limits; visible shell makes it clear whether the current input is treated as a contains search or a raw Redis pattern | No matches, invalid pattern, empty DB state, and very large result sets are handled predictably, and operators can clear search back to browse state without ambiguity | Seed dataset, search queries, measured behavior | Partially blocked: desktop UI now maps search to live browse requests and exposes clearer search-state feedback, but fixture-backed evidence and measured limits are still pending |
| Value editing | Open supported value types, edit, save, and refresh persisted value | Concurrent modification, invalid payload, and permission error preserve user context and surface failure through the shared operator notice layer | Before/after values, screenshots, repeatable steps | Partially blocked: string read/save/refresh is wired through the live desktop bridge, and non-string types now render in structured read-only inspector panels, but fixture-backed evidence and broader editing scope remain pending | | Value editing | Open supported value types, review pending string replacement, confirm save, and refresh persisted value; inspector keeps current state and write semantics explicit while editing | Concurrent modification, invalid payload, missing-key transitions, and permission error preserve user context, while no-op edits are blocked before execution and live failures reuse the shared operator notice layer | Before/after values, screenshots, repeatable steps | Partially blocked: string read/save/refresh is wired through the live desktop bridge, non-string types now render in structured read-only inspector panels, inspector guidance is clearer across live and fallback states, scoped review now clarifies current-vs-next value before save, and browser demo mode keeps session-local save state coherent, but fixture-backed live evidence and broader editing scope remain pending |
| TTL operations | View TTL, set TTL, remove TTL, and verify expiration behavior | Invalid TTL, already expired key, and permission error handled safely with consistent banner feedback | Seed dataset, timestamps, verification steps | Partially blocked: desktop UI now exposes live TTL set and remove controls through the existing contract, but seeded-fixture validation evidence is still missing | | TTL operations | View TTL, review pending TTL change or removal, confirm the mutation, and verify the resulting expiration behavior | Invalid TTL, already persistent/no-op TTL requests, already expired key, and permission error are handled safely with explicit guardrails and consistent banner feedback | Seed dataset, timestamps, verification steps | Partially blocked: desktop UI now exposes live TTL set and remove controls through the existing contract, blocks no-op TTL submissions, and keeps browser demo TTL state coherent, but seeded-fixture live validation evidence is still missing |
| Command console | Execute safe Redis commands in Tauri desktop mode and show result or error output with explicit live/demo labeling | Invalid command, long-running command, and blocked dangerous command are handled predictably, and backend failures reuse the shared operator notice mapping | Command transcript, screenshots, allow or deny rules | Partially blocked: the desktop UI now calls the real command execution bridge, but it is not yet validated against a shared live Redis fixture and dangerous mutations still stay blocked at shell level | | Command console | Execute safe Redis commands in Tauri desktop mode and show result or error output with explicit live/demo labeling | Invalid command, long-running command, and blocked dangerous command are handled predictably, and backend failures reuse the shared operator notice mapping | Command transcript, screenshots, allow or deny rules | Partially blocked: the desktop UI now calls the real command execution bridge, but it is not yet validated against a shared live Redis fixture and dangerous mutations still stay blocked at shell level |
| Dangerous operation confirmation | Delete, flush, overwrite, or destructive commands require explicit confirmation | Cancel path does not mutate data; confirm path mutates only intended scope | Confirmation UX screenshots, before/after data | Blocked: feature not implemented | | Dangerous operation confirmation | Delete, flush, overwrite, or destructive commands require explicit confirmation | Cancel path does not mutate data; confirm path mutates only intended scope | Confirmation UX screenshots, before/after data | Blocked: feature not implemented |
| Installer and packaging | Package metadata, icon, version, and uninstall behavior are correct on each target OS | Corrupt install, upgrade, downgrade, and missing dependency behavior are documented or handled | Artifact checklist, install logs, uninstall evidence | Partially blocked: Linux `.deb` and `.rpm` artifacts now exist, but AppImage failed and install or uninstall evidence is still absent | | Installer and packaging | Package metadata, icon, version, and uninstall behavior are correct on each target OS | Corrupt install, upgrade, downgrade, and missing dependency behavior are documented or handled | Artifact checklist, install logs, uninstall evidence | Partially blocked: Linux `.deb` metadata and contents are inspectable and the standard `.deb`/`.rpm` build path is stable, but AppImage remains blocked and install or uninstall evidence is still absent |
## Current Acceptance Judgment ## Current Acceptance Judgment
- No Redis-backed Redis GUI V1 feature acceptance item can be marked passed on 2026-03-27. - No Redis-backed Redis GUI V1 feature acceptance item can be marked passed on 2026-03-31.
- Current repository state now supports shell-level frontend acceptance and partial backend feature review: the React workspace shell builds, live desktop bridges exist for connection test, browse, value read/write, and command execution, draft-validation and empty-state behavior are explicit, and shared UI scope documentation is present in-repo. - Current repository state now supports shell-level frontend acceptance and partial backend feature review: the React workspace shell builds, live desktop bridges exist for connection test, browse, string create, value read/write, and command execution, draft-validation and empty-state behavior are explicit, and shared UI scope documentation is present in-repo.
- Rust source-level test evidence is now current for `redis-core`, Linux package artifacts exist for `.deb` and `.rpm`, but live Redis screenshots and installation evidence are still required. - The visible `Add key` surface should be tracked as implemented next-phase inventory only; its presence in the repository must not be treated as automatic current-baseline pass criteria or release scope expansion.
- QA can now reuse the documented fixture and smoke path, but feature acceptance and release sign-off remain blocked on end-to-end desktop evidence. - Rust source-level test evidence is current for `redis-core`, the desktop test baseline is current, the browser demo entrypoint is reproducible, Linux package artifacts exist for `.deb` and `.rpm`, Debian metadata is inspectable, and the fixture path is reproducible on both success and one key failure path, but live Redis screenshots or logs and installation evidence are still required.
- QA can now independently execute the documented Linux baseline path, and the phase-level evidence loop is summarized in `docs/qa/current-phase-evidence-loop.md`, but feature acceptance and release sign-off remain blocked on a GUI-capable desktop session for end-to-end evidence, AppImage packaging, cross-platform evidence, and installer install or uninstall checks.

View File

@@ -0,0 +1,37 @@
# HAC-31 Safe Mutation Guardrails
Date: 2026-03-31
Owner: Senior Frontend Engineer
Issue: HAC-31
## Goal
Harden current-phase writable operations without expanding product scope beyond existing string save and TTL controls.
## Scope
- add a scoped review step before string save
- add a scoped review step before TTL set and TTL removal
- block no-op mutation submits before they reach the desktop bridge or demo fallback
- preserve draft value and TTL input when a live mutation fails
## Explicit Non-Goals
- Add Key flow changes
- real delete execution
- non-string editing
- command-surface changes
- visual refresh beyond mutation clarity
## Implementation
- Added `apps/desktop/src/lib/mutation-guardrails.ts` for mutation summaries and no-op detection helpers.
- Updated `apps/desktop/src/App.tsx` so save and TTL actions open a shared review dialog with scoped current-vs-next details.
- Disabled string save when no value change is pending.
- Disabled TTL set when the requested TTL already matches the active TTL state.
- Kept live failure handling on the shared operator notice layer while preserving draft state for retry.
## Verification
- `pnpm --filter @redis-gui/desktop test`
- `pnpm run desktop:build`