feat: consolidate database management work

Add catalog-owned logical relationships and runtime snapshots, extend the database-management UI and validation coverage, and document the updated operational workflow.

Keep active sensitive-generation status in a tooltip and indicator, and update the layout E2E to follow the history action in its new database-scoped location.
This commit is contained in:
Codex
2026-09-01 14:46:55 +02:00
parent f586152636
commit 076c9742c5
73 changed files with 6966 additions and 610 deletions
+193 -90
View File
@@ -17,8 +17,9 @@ import { StopConfirmDialog } from "./StopConfirmDialog";
import { SteerInput, ComposerFooter } from "./SteerInput";
import { WorkflowBar } from "./WorkflowBar";
import { DatabaseManagementPage } from "./DatabaseManagementPage";
import { Pencil, ArrowLeft, ArrowRight, Trash2 } from "lucide-react";
import { Button } from "../components/ui/button";
import { Pencil, ArrowLeft, ArrowRight, ChevronDown, Trash2 } from "lucide-react";
import { Accordion } from "@base-ui/react/accordion";
import { Button, buttonVariants } from "../components/ui/button";
import { Checkbox } from "../components/ui/checkbox";
import { Toaster } from "../components/ui/sonner";
import { toast } from "sonner";
@@ -34,7 +35,7 @@ import type { SessionScope, SessionSummary } from "../api/types";
import { useAuthGeneration, useAuthUser } from "../auth/authState";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { CSSProperties } from "react";
import type { CSSProperties, KeyboardEvent } from "react";
import { captureAuthOperation, isAuthOperationCurrent, StaleAuthOperationError, type AuthOperationGuard } from "../auth/authOperation";
interface AppShellProps {
@@ -43,6 +44,14 @@ interface AppShellProps {
type ActiveSurface = "core" | "database-management";
type ActiveManagementPanel = "workspace" | "pi" | null;
const SESSION_SCOPES: readonly SessionScope[] = ["mine", "all"];
function sessionScopeTabClass(selected: boolean): string {
const base = "relative -mb-px flex h-8 w-full cursor-pointer items-center justify-center rounded-t-md border border-b-2 px-2 text-xs font-semibold tracking-[0.005em] outline-none focus-visible:z-10 focus-visible:ring-3 focus-visible:ring-[oklch(var(--nav-active-border)/0.28)]";
return selected
? `${base} border-[oklch(var(--nav-active-border))] bg-[oklch(var(--nav-active))] text-[oklch(var(--nav-active-foreground))] hover:bg-[oklch(var(--nav-active-hover))]`
: `${base} border-border bg-card text-muted-foreground hover:border-muted-foreground/45 hover:bg-muted/55 hover:text-foreground`;
}
type DatabaseManagementPresentation = "legacy" | "fleet";
@@ -129,8 +138,13 @@ export function AppShell({ canLogout }: AppShellProps) {
const [creatingSession, setCreatingSession] = useState(false);
const [awaitingQuestion, setAwaitingQuestion] = useState(false);
const [sessionScope, setSessionScope] = useState<SessionScope>("mine");
const sessionScopeTabRefs = useRef<Record<SessionScope, HTMLButtonElement | null>>({
mine: null,
all: null,
});
const principal = authenticatedUser;
const permissions = authenticatedUser?.permissions ?? [];
const isAdmin = authenticatedUser?.isAdmin === true;
const canReadAllSessions = permissions.includes("session.read_all");
const canManageWorkspace = permissions.includes("workspace.manage");
const canManageWorkspaceSecrets = permissions.includes("workspace.secrets.manage");
@@ -164,6 +178,7 @@ export function AppShell({ canLogout }: AppShellProps) {
databaseNavigationRef.current = state;
}, []);
const [activeManagementPanel, setActiveManagementPanel] = useState<ActiveManagementPanel>(null);
const [adminNavigationValue, setAdminNavigationValue] = useState<string[]>([]);
const [activeOpen, setActiveOpen] = useState(true);
const [archiveOpen, setArchiveOpen] = useState(false);
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
@@ -236,6 +251,22 @@ export function AppShell({ canLogout }: AppShellProps) {
setSelectedSessionIds(selected ? new Set(sessions.map((session) => session.id)) : new Set());
}
function handleSessionScopeTabKeyDown(
event: KeyboardEvent<HTMLButtonElement>,
current: SessionScope,
) {
const currentIndex = SESSION_SCOPES.indexOf(current);
let target: SessionScope | undefined;
if (event.key === "ArrowRight") target = SESSION_SCOPES[(currentIndex + 1) % SESSION_SCOPES.length];
else if (event.key === "ArrowLeft") target = SESSION_SCOPES[(currentIndex - 1 + SESSION_SCOPES.length) % SESSION_SCOPES.length];
else if (event.key === "Home") target = SESSION_SCOPES[0];
else if (event.key === "End") target = SESSION_SCOPES.at(-1);
if (!target) return;
event.preventDefault();
setSessionScope(target);
sessionScopeTabRefs.current[target]?.focus();
}
function canLeaveDatabaseManagement(): boolean {
if (activeSurface !== "database-management") return true;
if (databaseNavigationRef.current.busy) {
@@ -627,6 +658,12 @@ export function AppShell({ canLogout }: AppShellProps) {
await logoutUser();
}
const currentNavigation = activeSurface === "database-management"
? "database"
: activeManagementPanel ?? "core";
const adminNavigationOpen = adminNavigationValue.includes("administration");
const managementNavigationCurrent = currentNavigation !== "core";
return (
<div
ref={containerRef}
@@ -808,115 +845,180 @@ export function AppShell({ canLogout }: AppShellProps) {
<div className="flex flex-col gap-2 px-4 pb-3">
<Button
variant="default"
variant={currentNavigation === "core" ? "navigationActive" : "outline"}
size="sm"
className="w-full"
aria-current={currentNavigation === "core" ? "page" : undefined}
data-navigation-state={currentNavigation === "core" ? "current" : "available"}
onClick={startNewSession}
>
New session
</Button>
<Button
variant="outline"
size="sm"
className="w-full"
onClick={() => {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setActiveManagementPanel("workspace");
}}
>
Workspace management
</Button>
{canManageDatabase && (
<Button
variant={activeSurface === "database-management" ? "secondary" : "outline"}
size="sm"
className="w-full"
aria-current={activeSurface === "database-management" ? "page" : undefined}
onClick={() => {
setActiveManagementPanel(null);
setActiveSurface("database-management");
}}
{isAdmin && (
<Accordion.Root
value={adminNavigationValue}
onValueChange={setAdminNavigationValue}
className="rounded-lg border border-border/80 bg-card/45 p-1.5"
>
Database management
</Button>
)}
{canManagePi && (
<Button
variant="outline"
size="sm"
className="w-full"
onClick={() => {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setActiveManagementPanel("pi");
}}
>
Pi management
</Button>
<Accordion.Item value="administration">
<Accordion.Header className="m-0">
<Accordion.Trigger
className={buttonVariants({
variant: !adminNavigationOpen && managementNavigationCurrent ? "navigationActive" : "outline",
size: "sm",
className: "w-full justify-between px-2.5",
})}
data-navigation-state={!adminNavigationOpen && managementNavigationCurrent ? "current" : "available"}
>
<span>Administration</span>
<ChevronDown
aria-hidden="true"
data-icon="inline-end"
className={`transition-transform duration-150 motion-reduce:transition-none ${adminNavigationOpen ? "rotate-180" : ""}`}
/>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel className="grid gap-1.5 px-0.5 pt-1.5">
<Button
variant={currentNavigation === "database" ? "navigationActive" : "outline"}
size="sm"
className="w-full"
aria-current={currentNavigation === "database" ? "page" : undefined}
data-navigation-state={currentNavigation === "database" ? "current" : canManageDatabase ? "available" : "unavailable"}
disabled={!canManageDatabase}
title={canManageDatabase ? undefined : "Database management permission is required"}
onClick={() => {
setActiveManagementPanel(null);
setActiveSurface("database-management");
}}
>
Database management
</Button>
<div role="separator" aria-orientation="horizontal" className="mx-1 h-px bg-border/90" />
<Button
variant={currentNavigation === "workspace" ? "navigationActive" : "outline"}
size="sm"
className="w-full"
aria-current={currentNavigation === "workspace" ? "page" : undefined}
aria-expanded={activeManagementPanel === "workspace"}
data-navigation-state={currentNavigation === "workspace" ? "current" : "available"}
onClick={() => {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setActiveManagementPanel("workspace");
}}
>
Workspace management
</Button>
<Button
variant={currentNavigation === "pi" ? "navigationActive" : "outline"}
size="sm"
className="w-full"
aria-current={currentNavigation === "pi" ? "page" : undefined}
aria-expanded={activeManagementPanel === "pi"}
data-navigation-state={currentNavigation === "pi" ? "current" : canManagePi ? "available" : "unavailable"}
disabled={!canManagePi}
title={canManagePi ? undefined : "Pi management permission is required"}
onClick={() => {
if (!canLeaveDatabaseManagement()) return;
setActiveSurface("core");
setActiveManagementPanel("pi");
}}
>
Pi management
</Button>
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>
)}
</div>
{canReadAllSessions && (
<div className="px-4 pb-3">
<div className="grid grid-cols-2 gap-1 rounded-lg bg-muted p-1" aria-label="Session scope">
<Button
variant={sessionScope === "mine" ? "secondary" : "ghost"}
size="xs"
aria-pressed={sessionScope === "mine"}
<div className="px-4">
<div
role="tablist"
aria-label="Session scope"
aria-orientation="horizontal"
className="grid grid-cols-2 border-b border-border"
>
<button
ref={(node) => { sessionScopeTabRefs.current.mine = node; }}
id="session-scope-mine-tab"
type="button"
role="tab"
aria-selected={sessionScope === "mine"}
aria-controls="session-scope-panel"
tabIndex={sessionScope === "mine" ? 0 : -1}
data-tab-state={sessionScope === "mine" ? "active" : "inactive"}
className={sessionScopeTabClass(sessionScope === "mine")}
onClick={() => setSessionScope("mine")}
onKeyDown={(event) => handleSessionScopeTabKeyDown(event, "mine")}
>
My sessions
</Button>
<Button
variant={showingAllSessions ? "secondary" : "ghost"}
size="xs"
aria-pressed={showingAllSessions}
</button>
<button
ref={(node) => { sessionScopeTabRefs.current.all = node; }}
id="session-scope-all-tab"
type="button"
role="tab"
aria-selected={showingAllSessions}
aria-controls="session-scope-panel"
tabIndex={showingAllSessions ? 0 : -1}
data-tab-state={showingAllSessions ? "active" : "inactive"}
className={sessionScopeTabClass(showingAllSessions)}
onClick={() => setSessionScope("all")}
onKeyDown={(event) => handleSessionScopeTabKeyDown(event, "all")}
>
All sessions
</Button>
</button>
</div>
{showingAllSessions && (
<p className="mt-2 text-xs font-medium text-amber-700 dark:text-amber-400">
Administrator view: all sessions
</p>
)}
</div>
)}
{/* L1 — rail title */}
<div className="px-4 pb-1.5 pt-1">
<span className="thot-label text-[0.8rem] font-bold tracking-[0.18em] text-primary">
Sessions
</span>
</div>
<div className="flex items-center justify-between px-4 pb-2">
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-muted-foreground hover:text-foreground">
<Checkbox
checked={allSessionsSelected}
aria-label="Select all sessions"
disabled={sessions.length === 0}
onCheckedChange={(selected) => toggleAllSessions(selected === true)}
/>
<span>Select all</span>
</label>
{selectedSessions.length > 0 && (
<Button
variant="destructive"
size="xs"
aria-label={`Delete ${selectedSessions.length} selected sessions`}
onClick={() => {
requestDelete(selectedSessions);
}}
>
<Trash2 />
Delete ({selectedSessions.length})
</Button>
<div
id={canReadAllSessions ? "session-scope-panel" : undefined}
role={canReadAllSessions ? "tabpanel" : undefined}
aria-labelledby={canReadAllSessions ? `session-scope-${sessionScope}-tab` : undefined}
className="flex min-h-0 flex-1 flex-col"
>
{canReadAllSessions && showingAllSessions && (
<p className="mx-4 mb-2 mt-2 text-xs font-medium text-amber-700 dark:text-amber-400">
Administrator view: all sessions
</p>
)}
</div>
<div className="flex-1 overflow-y-auto px-2 pb-4">
{/* L1 — rail title */}
<div className="px-4 pb-1.5 pt-1">
<span className="thot-label text-[0.8rem] font-bold tracking-[0.18em] text-primary">
Sessions
</span>
</div>
<div className="flex items-center justify-between px-4 pb-2">
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-muted-foreground hover:text-foreground">
<Checkbox
checked={allSessionsSelected}
aria-label="Select all sessions"
disabled={sessions.length === 0}
onCheckedChange={(selected) => toggleAllSessions(selected === true)}
/>
<span>Select all</span>
</label>
{selectedSessions.length > 0 && (
<Button
variant="destructive"
size="xs"
aria-label={`Delete ${selectedSessions.length} selected sessions`}
onClick={() => {
requestDelete(selectedSessions);
}}
>
<Trash2 />
Delete ({selectedSessions.length})
</Button>
)}
</div>
<div className="flex-1 overflow-y-auto px-2 pb-4">
{/* L2 — section toggle */}
<button
type="button"
@@ -1002,6 +1104,7 @@ export function AppShell({ canLogout }: AppShellProps) {
showOwner={showingAllSessions}
/>
)}
</div>
</div>
</aside>
)}