Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/design-handoff/IMPLEMENTATION-STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ tests), so when a project gains or loses a frame, update this table by hand.
|---|---|---|
| `Familiar Analytics.dc.html` | `src/components/familiar-analytics-content.tsx` | `7316804273` (#4277) — dock + stage workbench |
| `Chat.dc.html` (session, list) | chat session chrome | `59527634e7` (#3983) |
| `Chat.dc.html` 2a (spine, minimap) | thread instruments | `6cc5fcb913` (#4046) |
| `Chat.dc.html` 2a (spine) | left turn spine (`src/components/chat-thread-instruments.tsx`). **Not adopted:** the frame's right-edge thread minimap — permanently removed in `cave-5m5hv`, along with its model, stylesheet and the `cave:chat:thread-instruments` preference that gated it. The right-side instrument is the `Coven Cave - Chat Session` run rail, which is automatic. | `6cc5fcb913` (#4046), `cave-5m5hv` |
| `Chat.dc.html` 2b (bands) | new-session launcher | `3e5b9c450d` (`cave-iwopz`) |
| `Composer.dc.html` | shared chat composer (`src/components/chat-view.tsx`, `src/components/composer-actions-menu.tsx`, `src/styles/cave-composer.css`) — edge-mounted Tools opens the existing action cascade, a linked Task edge opens the real board card, the lower action row keeps voice/enhance/send behavior, and its context meter uses the latest settled response’s measured token usage. **Not adopted:** the handoff’s Plan / Explore / Build modes, because Cave exposes no matching durable runtime state; attachment and completion surfaces remain the existing fully functional implementations. | `cave-9v9jr` |
| `Chat Session - Prototype.dc.html` | Sessions list (`src/components/chat-list.tsx`, `src/lib/chat-session-status.ts`, `src/lib/chat-session-activity.ts`, `src/lib/chat-session-sort.ts`, `src/styles/chat-list.css`), the new-session hero (`src/styles/home-dashboard.css`), transcript gap divider (`src/lib/chat-turn-gap.ts`) and the accessible "N earlier turns" fold (`src/lib/chat-transcript-fold.ts`, `src/components/chat-view.tsx`, `src/styles/cave-chat/session-chrome.css`). The frame's session chrome — serif title row, slim mono context row, spine, reader — had already landed through `Chat.dc.html` (session/2a) and `Reader.dc.html` 3a. **Not adopted:** the frame's per-row step counts, tool-call counts and last-message previews — the daemon's `SessionRow` carries none of them, and inventing them would be a row that lies; the fold's prose-dimming hint (the first visible turns below a closed fold sit slightly dimmed) was deliberately dropped in `cave-u5lq7` for contrast, then landed later via `cave-4akqc` with hover/focus restore and a reduced-transparency kill-switch, so the affordance never costs the reader legibility. | `cave-n3jg2`, `a21bc785e` (`cave-u5lq7`); prose-dimming hint `cave-4akqc` |
Expand Down
2 changes: 1 addition & 1 deletion scripts/run-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1609,7 +1609,7 @@ export const SUITES = {
"src/lib/harness-version.test.ts",
"src/lib/hermes-shim.test.ts",
"src/lib/hermes-responses-stream.test.ts",
"src/lib/thread-instruments-visibility.test.ts",
"src/lib/retired-chat-preferences.test.ts",
"src/lib/openclaw-bin.test.ts",
"src/lib/openclaw-bridge.test.ts",
"src/lib/openclaw-compatibility.test.ts",
Expand Down
49 changes: 42 additions & 7 deletions src/components/chat-run-rail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { readFileSync } from "node:fs";
const component = readFileSync(new URL("./chat-run-rail.tsx", import.meta.url), "utf8");
const css = readFileSync(new URL("../styles/cave-chat/run-rail.css", import.meta.url), "utf8");
const chatView = readFileSync(new URL("./chat-view.tsx", import.meta.url), "utf8");
const header = readFileSync(new URL("./chat-session-header.tsx", import.meta.url), "utf8");
const menuModel = readFileSync(new URL("../lib/chat-session-menu-model.ts", import.meta.url), "utf8");
const facade = readFileSync(new URL("../styles/cave-chat.css", import.meta.url), "utf8");

// ── mounted and styled, or it is dead code ──────────────────────────────────
Expand All @@ -18,15 +20,48 @@ assert.match(
/<ChatActivityMap\s+turns=\{activePath\}/,
"the activity map derives from the SAME activePath the transcript renders",
);
assert.match(
chatView,
/activePath\.length > 0 && activityMapVisible \? \(\s*\n\s*<ChatActivityMap/,
"the activity map follows its session-header visibility toggle",

// ── AUTOMATIC: the rail is the right-side instrument, with nothing to switch ─
// (cave-5m5hv) The rail replaced the thread minimap. "The minimap is gone" is
// also satisfied by rendering nothing at all, so these assert the positive: the
// rail mounts whenever there is a transcript, and no preference stands between
// a reader and it.
{
const mount = chatView.match(/\{([^{}]*?)\?\s*\(\s*\n\s*<ChatActivityMap/);
assert.ok(mount, "the rail's mount condition is readable");
assert.equal(
mount[1].trim(),
"activePath.length > 0",
"the ONLY condition on the rail is that a transcript exists — no preference, no flag",
);
}
// Comments are stripped first, the same discipline the `order: 1` guard below
// uses: all three files now EXPLAIN in prose that the toggle is retired, and an
// unanchored search matches the explanation. Scan the code, not the paragraph.
const code = (src: string) => src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^[ \t]*\/\/.*$/gm, "");
for (const revived of ["activityMapVisible", "useActivityMapVisible", "thread-instruments-visibility"]) {
assert.ok(
!code(chatView).includes(revived) && !code(header).includes(revived),
`${revived} is retired: re-introducing it gives a reader a way to switch the rail back off`,
);
}
assert.ok(
!code(menuModel).includes("activity-map"),
"the session kebab carries no activity-map toggle — the rail is not optional",
);
assert.doesNotMatch(
// …and the stripper is not doing the work on its own.
assert.ok(code(menuModel).includes('"thinking"'), "the menu model still declares its real items");
assert.ok(
!code(chatView).includes("ChatThreadMinimap"),
"chat no longer mounts the retired thread minimap",
);
// The LEFT turn spine is deliberately retained and mounted; its own pins live
// in lib/chat-thread-instruments.test.ts. Pinned here too because this file is
// the one that previously asserted it was gone.
assert.match(
chatView,
/ChatThreadMinimap|ChatThreadSpine/,
"chat no longer mounts the retired thread spine or minimap",
/<ChatThreadSpine\s+turns=\{activePath\}/,
"the left turn spine survives the minimap's removal and is still mounted",
);
assert.match(
component,
Expand Down
11 changes: 2 additions & 9 deletions src/components/chat-session-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { chatProjectById } from "@/lib/chat-projects";
import { archiveAction, sessionMenuSections, voiceAction, type SessionMenuItemId } from "@/lib/chat-session-menu-model";
import { Icon } from "@/lib/icon";
import { useShowThinking } from "@/lib/reasoning-visibility";
import { useActivityMapVisible } from "@/lib/thread-instruments-visibility";
import type { Familiar, SessionRow } from "@/lib/types";
import { FamiliarIcon } from "@/components/familiar-icon";
import { ProjectPickerPopover } from "@/components/project-picker";
Expand Down Expand Up @@ -58,7 +57,6 @@ export function SessionOverflowMenu({
const [open, setOpen] = useState(false);
const [projectPickerOpen, setProjectPickerOpen] = useState(false);
const [showThinking, setShowThinking] = useShowThinking();
const [activityMapVisible, setActivityMapVisible] = useActivityMapVisible();
const triggerRef = useRef<HTMLButtonElement | null>(null);
const menuRef = useRef<HTMLDivElement | null>(null);
const keyboardOpenRequested = useRef(false);
Expand All @@ -72,7 +70,6 @@ export function SessionOverflowMenu({
projectRoot: activeProject?.root ?? null,
hasTurns,
showThinking,
activityMapVisible,
reflectAvailable: Boolean(onReflect),
reflecting,
});
Expand Down Expand Up @@ -159,10 +156,6 @@ export function SessionOverflowMenu({
setShowThinking(!showThinking);
close();
},
"activity-map": () => {
setActivityMapVisible(!activityMapVisible);
close();
},
reflect: () => {
close();
onReflect?.();
Expand Down Expand Up @@ -211,8 +204,8 @@ export function SessionOverflowMenu({
key={item.id}
icon={item.icon}
checked={item.checked}
// Only `thinking` and `activity-map` carry `checked` here and
// both are independent on/off toggles, not one of a set.
// Only `thinking` carries `checked` here, and it is an
// independent on/off toggle, not one of a set.
checkedRole="checkbox"
disabled={item.disabled}
title={item.title}
Expand Down
Loading
Loading