Compare commits
1 Commits
cmp-26-wor
...
cmp-22-db-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04da063abf |
@@ -70,6 +70,7 @@ import {
|
|||||||
updateDemoKeyTtl,
|
updateDemoKeyTtl,
|
||||||
updateDemoStringValue,
|
updateDemoStringValue,
|
||||||
} from "./lib/demo-workspace";
|
} from "./lib/demo-workspace";
|
||||||
|
import { buildDatabaseSwitcherLabels } from "./lib/database-switcher";
|
||||||
import { cn } from "./lib/utils";
|
import { cn } from "./lib/utils";
|
||||||
|
|
||||||
declare const __APP_VERSION__: string;
|
declare const __APP_VERSION__: string;
|
||||||
@@ -357,8 +358,6 @@ const initialDemoKeys: KeyRecord[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const databases = ["db0", "db1", "db2", "db5"];
|
|
||||||
|
|
||||||
const fallbackBootstrap: BackendBootstrap = {
|
const fallbackBootstrap: BackendBootstrap = {
|
||||||
app_name: "Redis GUI Foundation",
|
app_name: "Redis GUI Foundation",
|
||||||
surface: "desktop",
|
surface: "desktop",
|
||||||
@@ -396,7 +395,7 @@ function App() {
|
|||||||
const [connections, setConnections] = useState(initialConnections);
|
const [connections, setConnections] = useState(initialConnections);
|
||||||
const [demoKeys, setDemoKeys] = useState(initialDemoKeys);
|
const [demoKeys, setDemoKeys] = useState(initialDemoKeys);
|
||||||
const [activeConnectionId, setActiveConnectionId] = useState(initialConnections[0].id);
|
const [activeConnectionId, setActiveConnectionId] = useState(initialConnections[0].id);
|
||||||
const [activeDb, setActiveDb] = useState(databases[0]);
|
const [activeDb, setActiveDb] = useState("db0");
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [selectedKeyId, setSelectedKeyId] = useState<string | null>(initialDemoKeys[1].id);
|
const [selectedKeyId, setSelectedKeyId] = useState<string | null>(initialDemoKeys[1].id);
|
||||||
const [draftValue, setDraftValue] = useState(initialDemoKeys[1].value);
|
const [draftValue, setDraftValue] = useState(initialDemoKeys[1].value);
|
||||||
@@ -443,6 +442,7 @@ function App() {
|
|||||||
const activeConnection =
|
const activeConnection =
|
||||||
connections.find((connection) => connection.id === activeConnectionId) ??
|
connections.find((connection) => connection.id === activeConnectionId) ??
|
||||||
connections[0];
|
connections[0];
|
||||||
|
const databaseOptions = buildDatabaseSwitcherLabels(parseDatabaseLabel(activeDb));
|
||||||
|
|
||||||
const filteredDemoKeys = filterWorkspaceKeys(demoKeys, deferredSearch);
|
const filteredDemoKeys = filterWorkspaceKeys(demoKeys, deferredSearch);
|
||||||
const filteredKeys = tauriAvailable ? liveKeys : filteredDemoKeys;
|
const filteredKeys = tauriAvailable ? liveKeys : filteredDemoKeys;
|
||||||
@@ -1583,7 +1583,7 @@ function App() {
|
|||||||
<section>
|
<section>
|
||||||
<SectionEyebrow icon={<Database className="h-4 w-4" />} label="Database switcher" />
|
<SectionEyebrow icon={<Database className="h-4 w-4" />} label="Database switcher" />
|
||||||
<div className="mt-3 grid gap-2">
|
<div className="mt-3 grid gap-2">
|
||||||
{databases.map((database) => {
|
{databaseOptions.map((database) => {
|
||||||
const active = database === activeDb;
|
const active = database === activeDb;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
74
apps/desktop/src/lib/database-switcher.test.ts
Normal file
74
apps/desktop/src/lib/database-switcher.test.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { buildDatabaseSwitcherLabels } from "./database-switcher";
|
||||||
|
|
||||||
|
describe("database switcher helper", () => {
|
||||||
|
it("returns the default operator range from db0 through db15", () => {
|
||||||
|
expect(buildDatabaseSwitcherLabels(0)).toEqual([
|
||||||
|
"db0",
|
||||||
|
"db1",
|
||||||
|
"db2",
|
||||||
|
"db3",
|
||||||
|
"db4",
|
||||||
|
"db5",
|
||||||
|
"db6",
|
||||||
|
"db7",
|
||||||
|
"db8",
|
||||||
|
"db9",
|
||||||
|
"db10",
|
||||||
|
"db11",
|
||||||
|
"db12",
|
||||||
|
"db13",
|
||||||
|
"db14",
|
||||||
|
"db15",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps an active out-of-range database visible and numerically ordered", () => {
|
||||||
|
expect(buildDatabaseSwitcherLabels(23)).toEqual([
|
||||||
|
"db0",
|
||||||
|
"db1",
|
||||||
|
"db2",
|
||||||
|
"db3",
|
||||||
|
"db4",
|
||||||
|
"db5",
|
||||||
|
"db6",
|
||||||
|
"db7",
|
||||||
|
"db8",
|
||||||
|
"db9",
|
||||||
|
"db10",
|
||||||
|
"db11",
|
||||||
|
"db12",
|
||||||
|
"db13",
|
||||||
|
"db14",
|
||||||
|
"db15",
|
||||||
|
"db23",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes negative or fractional inputs back to safe labels", () => {
|
||||||
|
expect(buildDatabaseSwitcherLabels(-3)).toEqual([
|
||||||
|
"db0",
|
||||||
|
"db1",
|
||||||
|
"db2",
|
||||||
|
"db3",
|
||||||
|
"db4",
|
||||||
|
"db5",
|
||||||
|
"db6",
|
||||||
|
"db7",
|
||||||
|
"db8",
|
||||||
|
"db9",
|
||||||
|
"db10",
|
||||||
|
"db11",
|
||||||
|
"db12",
|
||||||
|
"db13",
|
||||||
|
"db14",
|
||||||
|
"db15",
|
||||||
|
]);
|
||||||
|
expect(buildDatabaseSwitcherLabels(4.8, 2)).toEqual([
|
||||||
|
"db0",
|
||||||
|
"db1",
|
||||||
|
"db2",
|
||||||
|
"db4",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
29
apps/desktop/src/lib/database-switcher.ts
Normal file
29
apps/desktop/src/lib/database-switcher.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export function buildDatabaseSwitcherLabels(
|
||||||
|
activeDatabase: number,
|
||||||
|
maxDefaultDatabase = 15,
|
||||||
|
) {
|
||||||
|
const normalizedDefaultMax = Math.max(0, Math.floor(maxDefaultDatabase));
|
||||||
|
const normalizedActive = Math.max(0, Math.floor(activeDatabase));
|
||||||
|
const labels = new Set<string>();
|
||||||
|
|
||||||
|
for (let database = 0; database <= normalizedDefaultMax; database += 1) {
|
||||||
|
labels.add(formatDatabaseLabel(database));
|
||||||
|
}
|
||||||
|
|
||||||
|
labels.add(formatDatabaseLabel(normalizedActive));
|
||||||
|
|
||||||
|
return [...labels].sort(compareDatabaseLabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareDatabaseLabels(left: string, right: string) {
|
||||||
|
return parseDatabaseLabel(left) - parseDatabaseLabel(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDatabaseLabel(value: string) {
|
||||||
|
const parsed = Number.parseInt(value.replace(/^db/i, ""), 10);
|
||||||
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDatabaseLabel(database: number) {
|
||||||
|
return `db${database}`;
|
||||||
|
}
|
||||||
32
plans/2026-03-31-hac-22-db-switcher-hardening.md
Normal file
32
plans/2026-03-31-hac-22-db-switcher-hardening.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# HAC-22 DB Switcher Hardening
|
||||||
|
|
||||||
|
Date: 2026-03-31 UTC
|
||||||
|
Owner: Senior Frontend Engineer
|
||||||
|
Issue: HAC-22
|
||||||
|
Scope: `desktop-v1` user-visible frontend hardening only
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- `apps/desktop` already ships a visible DB switcher, but the current UI only renders four fixed choices: `db0`, `db1`, `db2`, and `db5`.
|
||||||
|
- Current product scope already includes one active standalone Redis connection, explicit DB context, and DB-scoped browse / inspect / command flows.
|
||||||
|
- The hardcoded four-item list is weaker than the current product boundary and makes shell-level verification less representative for operators who need to move across common DB indexes.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
- Do not expand product scope or alter backend contracts.
|
||||||
|
- Keep the existing rail placement and interaction model for the DB switcher.
|
||||||
|
- Widen the visible DB switcher to a predictable default operator range and ensure the currently active DB remains selectable even when it falls outside the default range.
|
||||||
|
|
||||||
|
## Implemented Slice
|
||||||
|
|
||||||
|
1. Added a shared frontend helper that returns DB labels for the switcher.
|
||||||
|
2. Defaulted the visible range to `db0` through `db15`.
|
||||||
|
3. Preserved the currently active DB when it falls outside that range and kept the options numerically ordered.
|
||||||
|
4. Covered the helper with focused Vitest cases.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
- The visible DB switcher no longer depends on a four-item hardcoded list.
|
||||||
|
- Operators can select common DB indexes from `db0` through `db15` without editing connection drafts.
|
||||||
|
- An active DB outside the default range still appears in the switcher and stays selectable.
|
||||||
|
- `pnpm --filter @redis-gui/desktop test` and `pnpm run desktop:build` remain green.
|
||||||
Reference in New Issue
Block a user