Agent prompts section: re-read on write, dedupe, owner-ruled layout
The prompts area's five owner-reported symptoms, root causes found in code and verified live before fixing: 1. STATE ECHO — the toggle's truth (SegmentInfo.enabled) derives from the settings record's agent switches, but the segment view was read ONCE per section mount: the switch write landed (PUT /api/settings/agent) yet the toggle painted its old state until a remount re-fetched. Root-cause fix in the settings-saved family: the section now re-reads /api/settings/prompts when the 'lh:settings-saved' signal lands for the agent section — the reader the signal law was missing. Toggle flips verify at ~40ms, no navigation. 2. DUPLICATION — the server contract returns the shared segments in BOTH per-agent lists; the UI rendered both lists back to back, so every shared chunk appeared twice (18 rows, 5 dupes). sectionedSegments (pure, exported, pinned) dedupes: the agents' own chunks plus the shared chunks ONCE. 3. ORDERING — Teacher first, then Prober, then Shared last (owner-ruled), each section headed, chunk order within a section preserved from assembly order (the server lists ARE assembly order: base, shared sorted, own numeric-prefix sorted). 4. GRAYING — the whole-row dim died; only the TOGGLE dims now when there is no override file (a wrapper span on the control; the file name is always full-contrast). 5. DESCRIPTION LOGIC — the 'No changes yet' line died. No description when there's no override (the empty state is clean: toggle + name); 'Your version is saved' (PROMPT_SAVED_HINT, pinned) appears ONLY when an override exists. The redundant 'Shared base — appears in both agents' editor note died too — the Shared section header carries 'Used by both agents'. Also: saveSegment/deleteSegment reuse the readPrompts callback (keeps last-good on a failed re-read; the mount effect owns the loading state). Live-verified on :3001: sections in the ruled order with 13 unique rows, clean-row and overridden-row states per the rulings, toggle flip instant (view + record agree), save/delete round-trips, and a shared-segment save + toggle through the single Shared control. Open finding (NOT fixed here — needs a ruling): an ON switch whose override file is later deleted orphans the record, and the agent-section write validation 400s every subsequent write (an ON switch must have an existing file) while the UI's disabled toggle cannot reach the OFF escape hatch the validation anticipates. Repro: Save + switch on, Delete the override, flip any tool. Server validation + row semantics are Platform's / owner's call.
This commit is contained in:
@@ -9,11 +9,13 @@ import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { appearanceVars, derivedPalette, resolveAppearance, staleVarNames } from "../components/settings/appearance.tsx";
|
||||
import {
|
||||
PROMPT_SAVED_HINT,
|
||||
composePromptSwitch,
|
||||
composeSearchEngine,
|
||||
composeSlotModel,
|
||||
composeTool,
|
||||
researchToolUnavailable,
|
||||
sectionedSegments,
|
||||
} from "../components/settings/agent-section.tsx";
|
||||
import { nextOverrides } from "../components/settings/keyboard-section.tsx";
|
||||
import { classifyKeys, keyStatusLine, type ProviderKeyInfo } from "../components/settings/keys-section.tsx";
|
||||
@@ -339,3 +341,50 @@ describe("researchToolUnavailable — the row grays on the registry's own signal
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("sectionedSegments — the prompts overhaul (2026-09-26)", () => {
|
||||
// The server contract: BOTH per-agent lists include the shared
|
||||
// segments (same paths) — the view must list them ONCE.
|
||||
const view = {
|
||||
prober: [
|
||||
{ path: "system-prompt.md", sourceText: "b", overrideText: null, enabled: false, shared: true },
|
||||
{ path: "shared/10-personality.md", sourceText: "s1", overrideText: "custom", enabled: true, shared: true },
|
||||
{ path: "prober/10-probe-doctrine.md", sourceText: "p1", overrideText: null, enabled: false, shared: false },
|
||||
],
|
||||
teacher: [
|
||||
{ path: "system-prompt.md", sourceText: "b", overrideText: null, enabled: false, shared: true },
|
||||
{ path: "shared/10-personality.md", sourceText: "s1", overrideText: "custom", enabled: true, shared: true },
|
||||
{ path: "teacher/05-method.md", sourceText: "t1", overrideText: null, enabled: false, shared: false },
|
||||
{ path: "teacher/10-planning.md", sourceText: "t2", overrideText: null, enabled: false, shared: false },
|
||||
],
|
||||
};
|
||||
|
||||
it("the owner's ruled order: Teacher, Prober, Shared — Shared last", () => {
|
||||
const ids = sectionedSegments(view).map((s) => s.id);
|
||||
assert.deepEqual(ids, ["teacher", "prober", "shared"]);
|
||||
});
|
||||
|
||||
it("shared chunks appear exactly ONCE across all sections (no duplication)", () => {
|
||||
const all = sectionedSegments(view).flatMap((s) => s.segments.map((seg) => seg.path));
|
||||
assert.equal(all.filter((p) => p === "system-prompt.md").length, 1);
|
||||
assert.equal(all.filter((p) => p === "shared/10-personality.md").length, 1);
|
||||
// every path unique — no chunk listed twice
|
||||
assert.equal(new Set(all).size, all.length);
|
||||
});
|
||||
|
||||
it("the Shared section holds the shared chunks in assembly order; the agents' sections hold their own", () => {
|
||||
const [teacher, prober, shared] = sectionedSegments(view);
|
||||
assert.ok(teacher && prober && shared, "all three sections present");
|
||||
assert.deepEqual(teacher.segments.map((s) => s.path), ["teacher/05-method.md", "teacher/10-planning.md"]);
|
||||
assert.deepEqual(prober.segments.map((s) => s.path), ["prober/10-probe-doctrine.md"]);
|
||||
// assembly order: the base file first, then the shared fragments
|
||||
assert.deepEqual(shared.segments.map((s) => s.path), ["system-prompt.md", "shared/10-personality.md"]);
|
||||
// the Shared header carries the one hint that earns its place
|
||||
assert.equal(shared.hint, "Used by both agents");
|
||||
assert.equal(teacher.hint, undefined);
|
||||
});
|
||||
|
||||
it("the indicator copy is pinned (override present = the row earns its line)", () => {
|
||||
assert.equal(PROMPT_SAVED_HINT, "Your version is saved");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user