Session deletion dialog matches the node deletion dialog: red title, mono name below, checkbox flow
Owner polish round (2026-09-24): the two destructive-confirmation
surfaces must read as ONE design. The node dialog was the good one —
the session dialog now wears its exact look and flow.
components/delete-session-dialog.tsx (full rework):
- Red serif "Delete session?" (was neutral "Delete "title"?") — the
node dialog's destructive title, text-destructive on font-title 2xl.
- The session name below the title in JetBrains Mono at text-lg,
NO code-block chrome — the font already says code. (The node
dialog's name got the same treatment: text-xs bg-muted code block
dies, mono text-lg line lives. Owner chose below-the-title over
parens-in-the-title: long titles wrap safely below, never inside
the 25px headline.)
- TWO OPTION BUTTONS -> CHECKBOX FLOW: the locked two-button design
(preserve-graph / delete-nodes) collapses to ONE unchecked-by-default
checkbox "Also remove its graph traces" (accent-primary, the settings
panel's native-checkbox idiom) carrying the graph-touching option's
consequence copy verbatim. Unchecked = preserve-graph; checked =
delete-nodes. The unchecked promise ("everything it added to the
graph stays unless you check below") lives in the intro line now.
- Bottom row = the node dialog's exact pair: quiet Cancel + destructive
red Delete (border + text, fill on hover), "Deleting…" on the Delete
button while busy. Try-again re-derives the mode from the checkbox
(retry IS Delete pressed again; the checkbox is disabled while busy).
- DeleteSessionBody exported pure (the settings/node dialog body
pattern) for SSR pins; TRACES_COPY exported for the copy pin.
DELETE_OPTIONS dies with the two-button design.
Tests: session-context.test.tsx's two-option pins rewritten for the
node-dialog shape (red title, mono name no-chrome, unchecked checkbox
default, red Delete pair, old testids absent); delete-node-dialog
pins the chrome-less text-lg name. 905/905. Live-verified on :3001:
both dialogs render 25px red serif titles, 16px mono names, matching
red Delete buttons; Esc closes both, nothing deleted in verification.
This commit is contained in:
@@ -5,7 +5,7 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { RenameRow, SessionRow } from "../components/sidebar.tsx";
|
||||
import { DELETE_OPTIONS, DeleteSessionDialog } from "../components/delete-session-dialog.tsx";
|
||||
import { DeleteSessionBody, DeleteSessionDialog, TRACES_COPY } from "../components/delete-session-dialog.tsx";
|
||||
import type { ChatListItem } from "../src/core/index.ts";
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ describe("RenameRow — the inline rename surface", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("DeleteSessionDialog — the locked two-option copy (portals no-op in SSR; pin the pure seam)", () => {
|
||||
describe("DeleteSessionDialog — the node-dialog shape (owner flow rework 2026-09-24; portals no-op in SSR; pin the pure body)", () => {
|
||||
it("closed (chat=null) renders nothing", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<DeleteSessionDialog chat={null} onClose={() => {}} onDeleted={() => {}} />,
|
||||
@@ -59,18 +59,68 @@ describe("DeleteSessionDialog — the locked two-option copy (portals no-op in S
|
||||
assert.equal(html, "");
|
||||
});
|
||||
|
||||
it("exactly two options, both modes covered, consequences in plain language", () => {
|
||||
assert.equal(DELETE_OPTIONS.length, 2);
|
||||
const modes = DELETE_OPTIONS.map((o) => o.mode).sort();
|
||||
assert.deepEqual(modes, ["delete-nodes", "preserve-graph"]);
|
||||
it("red serif title 'Delete session?', the session name below in mono, sized up, no code-block chrome", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<DeleteSessionBody
|
||||
chat={{ id: "c1", title: "Orbital mechanics" }}
|
||||
traces={false}
|
||||
busy={false}
|
||||
failure={null}
|
||||
onTraces={() => {}}
|
||||
onClose={() => {}}
|
||||
onRun={() => {}}
|
||||
/>,
|
||||
);
|
||||
assert.match(html, /Delete session\?/);
|
||||
assert.match(html, /text-destructive[^"]*">Delete session\?</); // the red title
|
||||
// the name: mono, text-lg (sized up), NO bg-muted code-block chrome
|
||||
const name = html.match(/data-testid="session-name-code"[^>]*>/)?.[0] ?? "";
|
||||
assert.match(name, /font-mono text-lg/);
|
||||
assert.ok(!name.includes("bg-muted"), "no code-block chrome");
|
||||
assert.match(html, /data-testid="session-name-code"[^>]*>Orbital mechanics</);
|
||||
});
|
||||
|
||||
it("the graph-touching option says so; the session-only option promises the graph stays", () => {
|
||||
const preserve = DELETE_OPTIONS.find((o) => o.mode === "preserve-graph");
|
||||
const nodes = DELETE_OPTIONS.find((o) => o.mode === "delete-nodes");
|
||||
assert.ok(preserve?.body.includes("graph stays"));
|
||||
assert.ok(nodes?.body.includes("leave the graph"));
|
||||
// shared-topics survival is stated on the destructive option
|
||||
assert.ok(nodes?.body.includes("survive"));
|
||||
it("one unchecked-by-default checkbox for the graph traces — the graph-touching consequence says so", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<DeleteSessionBody
|
||||
chat={{ id: "c1", title: "Orbital mechanics" }}
|
||||
traces={false}
|
||||
busy={false}
|
||||
failure={null}
|
||||
onTraces={() => {}}
|
||||
onClose={() => {}}
|
||||
onRun={() => {}}
|
||||
/>,
|
||||
);
|
||||
// unchecked by default (preserve-graph) — React SSR omits the
|
||||
// attribute when false, so pin its ABSENCE on the rendered input
|
||||
assert.ok(!/"checked"/.test(html), "unchecked by default");
|
||||
// plain-language consequences on the checkbox body
|
||||
assert.ok(html.includes(TRACES_COPY.title));
|
||||
assert.ok(html.includes("leave the graph too"));
|
||||
assert.ok(html.includes("survive"));
|
||||
// the old two-button design is gone
|
||||
assert.ok(!html.includes("delete-option-preserve"));
|
||||
assert.ok(!html.includes("delete-option-nodes"));
|
||||
});
|
||||
|
||||
it("bottom row matches the node dialog: quiet Cancel + destructive-red Delete", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<DeleteSessionBody
|
||||
chat={{ id: "c1", title: "Orbital mechanics" }}
|
||||
traces={true}
|
||||
busy={true}
|
||||
failure={null}
|
||||
onTraces={() => {}}
|
||||
onClose={() => {}}
|
||||
onRun={() => {}}
|
||||
/>,
|
||||
);
|
||||
assert.match(html, /data-testid="delete-cancel"[^>]*>Cancel</);
|
||||
const del = html.match(/data-testid="delete-confirm"[^>]*>/)?.[0] ?? "";
|
||||
assert.match(del, /border-destructive\/50/);
|
||||
assert.match(html, /data-testid="delete-confirm"[^>]*>Deleting…</); // busy label on Delete
|
||||
// the checkbox survives a busy render (disabled, not unmounted)
|
||||
assert.match(html, /data-testid="delete-traces-checkbox"[^>]*disabled=""/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user