feat(web,server): unify nav naming, panel width and the update entry (#130)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yaowei ZhengandClaude Opus 5 authored and GitHub committed 2026-07-30 17:25:50 +08:00
1 parent 97852f43b8
commit 385d506e77
32 files changed
+1135 -557

No files matched your search

+20 -19
View File
@@ -1,10 +1,11 @@
/**
* advancePanelTaskScope unit tests: the subagents panel's TASK-SCOPED visibility rules —
* entering a session and every new Task close the panel by default; the current task's first
* live spawn auto-opens it (one attempt per task, so a manual close afterwards is respected
* until the next boundary); a taskCount decrease is a defensive re-baseline, never a boundary.
* The chat page feeds observations per render (session id + taskStartCount + live-spawn flag)
* and applies the returned action under its own layout guards.
* advancePanelTaskScope unit tests: the subagents panel's AUTO-OPEN rule — the current task's
* first live spawn opens the panel once, re-armed at every boundary (Session switch or new
* Task) so a manual close is respected until the next one; boundaries themselves never close
* the panel (an open panel survives a switch, like the Files panel); a taskCount decrease is a
* defensive re-baseline that consumes the attempt instead of arming it. The chat page feeds
* observations per render (session id + taskStartCount + live-spawn flag) and applies the
* returned action under its own layout guards.
*/
import { describe, expect, it } from "vitest";
import {
@@ -18,36 +19,35 @@ const obs = (sessionId: string | null, taskCount: number, liveSpawn = false) =>
liveSpawn,
});
describe("advancePanelTaskScope (task-scoped panel visibility)", () => {
it("entering a session closes by default; a mid-run entry with a live spawn auto-opens instead", () => {
describe("advancePanelTaskScope (subagents panel auto-open)", () => {
it("entering a session does nothing on its own; a mid-run entry with a live spawn auto-opens", () => {
const s = createPanelTaskScope();
expect(advancePanelTaskScope(s, obs("A", 3))).toBe("close");
expect(advancePanelTaskScope(s, obs("A", 3))).toBeNull();
const mid = createPanelTaskScope();
expect(advancePanelTaskScope(mid, obs("A", 3, true))).toBe("autoOpen");
});
it("a new Task closes the panel and RE-ARMS the auto-open; the task's own spawn then opens it once", () => {
it("a new Task RE-ARMS the auto-open without closing; the task's own spawn then opens it once", () => {
const s = createPanelTaskScope();
advancePanelTaskScope(s, obs("A", 1)); // session entry
expect(advancePanelTaskScope(s, obs("A", 1, true))).toBe("autoOpen"); // task 1 spawns
expect(advancePanelTaskScope(s, obs("A", 1, true))).toBeNull(); // once per task
expect(advancePanelTaskScope(s, obs("A", 2))).toBe("close"); // task 2 boundary
expect(advancePanelTaskScope(s, obs("A", 2))).toBeNull(); // the boundary fires once
expect(advancePanelTaskScope(s, obs("A", 2))).toBeNull(); // task 2 boundary: no close
expect(advancePanelTaskScope(s, obs("A", 2, true))).toBe("autoOpen"); // re-armed for task 2
// Consumed again: a manual close mid-task stays respected until the next boundary.
expect(advancePanelTaskScope(s, obs("A", 2, true))).toBeNull();
});
it("a boundary arriving together with the new task's spawn opens rather than closing (batched commit)", () => {
it("a boundary arriving together with the new task's spawn opens in the same observation", () => {
const s = createPanelTaskScope();
advancePanelTaskScope(s, obs("A", 1));
expect(advancePanelTaskScope(s, obs("A", 2, true))).toBe("autoOpen");
});
it("a session switch resets the per-task guard", () => {
it("a session switch resets the per-task guard but leaves visibility alone", () => {
const s = createPanelTaskScope();
expect(advancePanelTaskScope(s, obs("A", 1, true))).toBe("autoOpen"); // consumed for A's task 1
expect(advancePanelTaskScope(s, obs("B", 1))).toBe("close"); // B is entered closed
expect(advancePanelTaskScope(s, obs("B", 1))).toBeNull(); // B inherits the open state
expect(advancePanelTaskScope(s, obs("B", 1, true))).toBe("autoOpen"); // B's task 1 arms fresh
});
@@ -58,12 +58,13 @@ describe("advancePanelTaskScope (task-scoped panel visibility)", () => {
expect(advancePanelTaskScope(s, obs("A", 2))).toBeNull();
});
it("a taskCount decrease re-baselines silently: no boundary, no surprise reopen; the next real boundary works", () => {
it("a taskCount decrease re-baselines silently: no surprise reopen, and the next real boundary still arms", () => {
const s = createPanelTaskScope();
advancePanelTaskScope(s, obs("A", 5));
// A resync swapped in a smaller model while a spawn runs: neither close nor auto-open —
// reopening a panel the user closed mid-task would be a surprise.
// A resync swapped in a smaller model while a spawn runs: no auto-open — reopening a panel
// the user closed mid-task would be a surprise.
expect(advancePanelTaskScope(s, obs("A", 3, true))).toBeNull();
expect(advancePanelTaskScope(s, obs("A", 4))).toBe("close");
expect(advancePanelTaskScope(s, obs("A", 4))).toBeNull(); // boundary itself is silent
expect(advancePanelTaskScope(s, obs("A", 4, true))).toBe("autoOpen"); // but it re-armed
});
});