Files
learnharness/tests/render-settings.test.tsx
T
amosblomqvist c86430e8bf Wave 7 chunk B: remove the embeddings machinery — the ladder collapses
The owner's wave-7 ruling: embeddings die entirely (git history is the
stash); clustering is birth-session derived (chunk A, 950ddfd), labels
are session names, and the prober's graph tools replace injection.

- settings: GraphSettings collapses to { enabled } — embedding,
  embeddingModel, clustering, labeling all die. Migration story: stale
  record keys are UNREAD and dropped by the sanitizer's reconstruction;
  the next graph-section PUT (replace-whole) persists the collapsed
  shape. The settings-panel GraphSection renders one master row.
- deleted: src/server/embeddings.ts (adapters, resolveEmbeddingPortFor,
  embedNewNodes, embeddingPort, embeddingModelCatalog), the
  GET /api/models/embeddings route, scripts/embed-graph.ts, tests for
  all three, and the /api/models/embeddings picker surfaces in the UI.
- prices: EMBEDDING_PRICES overlay + callCost fallback die. cost-summary:
  the embedding stage fold dies — back to three stages (extraction/
  audit/naming) per the owner ruling; historical stages.embedding spend
  in old run records is simply not displayed.
- ports: EmbeddingPort/EmbeddingResult/EmbeddingTask die. The embeddings
  LEDGER surface (readEmbeddings/writeEmbeddings + EmbeddingsLedger +
  EMBEDDINGS_FILE + graph-deletion's vector pruning) STAYS — inert data,
  kept only so graph-context.ts compiles until Teacher's pass removes
  it; the seam dies in the follow-up interlude.
- chat-handler (approved cross-lane unwiring, wave-6 precedent): the
  extraction pipeline's embedder becomes a documented no-op (nothing
  embedded, nothing spent, ledger untouched) until Teacher's pass
  removes the phase; the graph-context injection dispatch gate, the
  graphContext deps wiring, the setContextRespawn seam, and
  turn-service's triggerGraphContext are gone — injection is dead
  code pending Teacher's file deletions.
- scripts: backfill-graph/import-notes pass the same no-op embedder;
  replay-layout derives clusters from birthSessionPartition (the same
  partition the real settle uses) instead of the embeddings ledger.
- settings-body: parseGraphSettingsBody reads the master switch only —
  old-shape keys in a body are ignored, not rejected; bad shapes 400.
- next.config: the @huggingface tracing-exclusion guards die with the
  last reference.

Cross-lane flags: extraction.ts/graph-context.ts keep stale embed
comments + the embed phase until Teacher's full pass (their parallel
interlude owns those files — zero overlap kept by design);
semantic-clusters.ts + its test stay one more round (extraction.test.ts,
Teacher's file, still imports it — they delete it with the namer cases);
ChatSettings.graphContextTokens + the graph-context-control UI stay
until Teacher+UI.

Gates: tsc clean, eslint clean, 885/885 tests, build compiles.
Live-verified on the port-3004 dev tree: PUT /api/settings/graph with
dead keys collapses on write, bad shapes 400, GET reads {enabled: true},
/api/models/embeddings 404s.
2026-09-23 15:02:55 +03:00

213 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Settings-surface render tests (thin settings wave): SSR of the section
* components pins the THIN markup — toggle rows, the graph ladder's
* graying, the mode control's honest disabled state, the role rows and
* tool rows. Fetches don't run under renderToStaticMarkup (effects never
* fire), so the SSR state IS the loading state — asserted as such.
* Same tsx classic-JSX shim as render-sidebar.test.tsx.
*/
import React from "react";
(globalThis as Record<string, unknown>).React = React;
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { renderToStaticMarkup } from "react-dom/server";
import { TOGGLE_TOOLS, AgentSection } from "../components/settings/agent-section.tsx";
import { AppearanceSection, GraphSection } from "../components/settings/appearance-section.tsx";
import { KeysSection } from "../components/settings/keys-section.tsx";
import { KeyboardSection } from "../components/settings/keyboard-section.tsx";
import type { GraphSettings } from "../src/core/index.ts";
import { SettingsDialogBody, SettingsDialogProvider } from "../components/settings/settings-dialog.tsx";
import { KEYBOARD_ACTIONS } from "../src/core/index.ts";
/** The full opening tag of the element carrying a testid — attribute
* order in React output is not contractual, so assertions run on the
* whole tag instead of ordered regex chains. */
function tagOf(html: string, testid: string): string {
const i = html.indexOf(`data-testid="${testid}"`);
assert.ok(i >= 0, `missing ${testid}`);
return html.slice(html.lastIndexOf("<", i), html.indexOf(">", i) + 1);
}
describe("SettingsDialog (the designed surface, SSR)", () => {
it("provider CLOSED: children only — no dialog, no portal content", () => {
const html = renderToStaticMarkup(
<SettingsDialogProvider>
<p>the app</p>
</SettingsDialogProvider>,
);
assert.ok(html.includes("the app"));
assert.ok(!html.includes("settings-backdrop"));
assert.ok(!html.includes("settings-dialog"));
});
it("the dialog frame: role=dialog + the ✕ + the six-section nav, General active", () => {
const html = renderToStaticMarkup(<SettingsDialogBody onClose={() => {}} />);
assert.ok(html.includes('role="dialog"'));
assert.ok(html.includes('aria-modal="true"'));
assert.ok(html.includes('aria-label="Close settings"'));
for (const id of ["general", "keys", "agent", "appearance", "graph", "keyboard"]) {
assert.ok(html.includes(`data-testid="settings-nav-${id}"`), id);
}
// General is the landing section; the others are not current.
assert.ok(tagOf(html, "settings-nav-general").includes('aria-current="true"'));
assert.ok(tagOf(html, "settings-nav-keyboard").includes('aria-current="false"'));
// Fetches don't run under SSR — the loading state IS the SSR state.
assert.ok(html.includes("Loading settings…"));
// Modal-only doctrine: no link back to /settings anywhere in it.
assert.ok(!html.includes('href="/settings"'));
});
});
describe("AgentSection (thin SSR)", () => {
it("renders the three model SLOT rows (slots-only, fix wave: unpicked reads —, no Default option)", () => {
const html = renderToStaticMarkup(<AgentSection agent={undefined} models={undefined} onSectionChange={() => {}} onModelsChange={() => {}} />);
for (const slot of ["strong", "cheap", "vision"]) {
assert.ok(html.includes(`data-testid="slot-row-${slot}"`), slot);
// The shared model-picker dropdown as the opened menu (owner,
// 2026-09-20), the thin trigger as the collapsed look.
assert.ok(html.includes(`data-testid="slot-model-${slot}"`), `${slot} model trigger`);
assert.ok(html.includes(`data-testid="slot-thinking-${slot}"`), `${slot} thinking trigger`);
// the roster hint rides every row
assert.ok(html.includes("Prober + Teacher") || slot !== "strong", "strong roster");
}
// Fix wave: no Default anywhere — an unpicked slot reads "—"
// (the owner's no-factory-fallback language), and no menu
// carries a Default row (menu-default testid is retired).
assert.equal((html.match(/>Default<\/span>/g) ?? []).length, 0, "no Default trigger");
assert.ok(!html.includes('data-testid="menu-default"'), "no Default row");
assert.equal((html.match(/>—<\/span>/g) ?? []).length, 6, "three slots × two — triggers");
// the per-role rows are GONE (slots-only)
for (const role of ["researcher", "demoBuilder", "visualMaker"]) {
assert.ok(!html.includes(`data-testid="role-row-${role}"`), `${role} retired`);
}
assert.ok(html.includes('data-testid="group-models"'), "Models sub-group");
assert.ok(html.includes('data-testid="group-tools"'), "Tools sub-group");
assert.ok(html.includes('data-testid="group-prompts"'), "System prompts sub-group");
});
it("the search-engine row renders with its live reason (search wave 2026-09-20)", () => {
const html = renderToStaticMarkup(<AgentSection agent={undefined} models={undefined} onSectionChange={() => {}} onModelsChange={() => {}} />);
assert.ok(html.includes('data-testid="search-engine-row"'), "the row");
assert.ok(html.includes('data-testid="search-engine-trigger"'), "the trigger");
// SSR: /api/search never fired → no reason line yet, trigger reads Automatic
assert.ok(html.includes("Automatic"), "unset engine reads Automatic");
// the explicit preference shows the chosen engine in the trigger
const html2 = renderToStaticMarkup(
<AgentSection agent={{ search: { engine: "tavily" } }} models={undefined} onSectionChange={() => {}} onModelsChange={() => {}} />,
);
assert.ok(html2.includes("Tavily"), "pinned engine visible in the trigger");
});
it("renders the owner's FINAL tool list — research, demo, visual, pdf", () => {
assert.deepEqual([...TOGGLE_TOOLS], ["research", "demo", "visual", "pdf"]);
const html = renderToStaticMarkup(<AgentSection agent={undefined} models={undefined} onSectionChange={() => {}} onModelsChange={() => {}} />);
for (const id of TOGGLE_TOOLS) assert.ok(html.includes(`data-testid="tool-row-${id}"`), id);
});
it("tools default ON (absent key = enabled)", () => {
const html = renderToStaticMarkup(<AgentSection agent={undefined} models={undefined} onSectionChange={() => {}} onModelsChange={() => {}} />);
for (const id of TOGGLE_TOOLS) {
assert.ok(tagOf(html, `toggle-tool-${id}`).includes('aria-checked="true"'), `${id} defaults on`);
}
});
it("an overridden SLOT shows its model in the trigger, tools carry the delta", () => {
const html = renderToStaticMarkup(
<AgentSection
agent={{ tools: { demo: false } }}
models={{ strong: { model: { provider: "zai", id: "glm-4.7-flash" } } }}
onSectionChange={() => {}}
onModelsChange={() => {}}
/>,
);
// The trigger shows the stored id even with no catalog loaded (a
// trigger reading "Default" over a live override would lie).
assert.ok(html.includes("Glm 4.7 Flash"), "the override id is visible (prettyModel form)");
assert.ok(tagOf(html, "toggle-tool-demo").includes('aria-checked="false"'), "demo off");
assert.ok(tagOf(html, "toggle-tool-visual").includes('aria-checked="true"'), "visual still on");
});
it("prompts show the loading state under SSR (effects never fire)", () => {
const html = renderToStaticMarkup(<AgentSection agent={undefined} models={undefined} onSectionChange={() => {}} onModelsChange={() => {}} />);
assert.ok(html.includes("Loading prompt segments"));
});
});
describe("GraphSection — the collapsed master switch (wave 7)", () => {
// Wave 7 (owner ruling 2026-09-23): the section is the master switch
// ONLY — the embedding/clustering/labeling rungs and the BYOK model
// picker are gone (clustering is the free birth-session partition,
// labels ARE session names). One row, one toggle.
it("renders the single master-switch row", () => {
const html = renderToStaticMarkup(<GraphSection graph={undefined} onSectionChange={() => {}} />);
assert.ok(html.includes('data-testid="graph-row-enabled"'));
assert.ok(!html.includes("graph-row-embedding"), "the dead rungs render nothing");
assert.ok(!html.includes("Embedding model"), "the picker is gone");
assert.ok(!html.includes("graph-row-clustering"));
assert.ok(!html.includes("graph-row-labeling"));
assert.ok(tagOf(html, "toggle-graph-enabled").includes('aria-checked="true"'), "absent record = factory ON");
});
it("a stored off record shows the switch off", () => {
const html = renderToStaticMarkup(<GraphSection graph={{ enabled: false }} onSectionChange={() => {}} />);
assert.ok(tagOf(html, "toggle-graph-enabled").includes('aria-checked="false"'));
});
});
describe("KeysSection (thin SSR)", () => {
it("SSR = the loading state (effects never fire)", () => {
const html = renderToStaticMarkup(<KeysSection />);
assert.ok(html.includes("Loading keys…"));
});
});
describe("AppearanceSection (thin SSR)", () => {
it("mode is rendered DISABLED — dark-only this wave, no lying control", () => {
const html = renderToStaticMarkup(<AppearanceSection appearance={undefined} onSectionChange={() => {}} />);
assert.ok(tagOf(html, "select-mode").includes("disabled"), "mode select disabled");
assert.ok(html.includes("Light ships with the design wave"));
// The whole row dims, graph-view style (the control never adds its own).
assert.ok(tagOf(html, "row-mode").includes("opacity-40"), "mode row dims whole");
});
it("hex inputs carry the factory placeholders; prose size resolves against the factory", () => {
const html = renderToStaticMarkup(<AppearanceSection appearance={undefined} onSectionChange={() => {}} />);
assert.ok(html.includes(`placeholder="#141414"`));
assert.ok(html.includes(`placeholder="Source Sans 3"`));
assert.ok(html.includes(`placeholder="#c96b4a"`));
assert.ok(html.includes("16px"));
});
it("a set background shows in the input, not the placeholder", () => {
const html = renderToStaticMarkup(
<AppearanceSection appearance={{ background: "#1a1a2e" }} onSectionChange={() => {}} />,
);
assert.ok(html.includes(`value="#1a1a2e"`));
});
});
describe("KeyboardSection (thin SSR)", () => {
it("renders every registry action in its scope group", () => {
const html = renderToStaticMarkup(<KeyboardSection />);
for (const def of KEYBOARD_ACTIONS) {
assert.ok(html.includes(`data-testid="kbd-row-${def.id}"`), def.id);
}
assert.ok(html.includes("kbd-scope-global"));
assert.ok(html.includes("kbd-scope-chat"));
});
it("open-settings is in the registry with the settings chord", () => {
const def = KEYBOARD_ACTIONS.find((a) => a.id === "open-settings");
assert.ok(def, "the action exists");
assert.equal(def?.scope, "global");
assert.equal(def?.binding, "mod+,");
});
it("displayOnly actions list but never offer Rebind", () => {
const html = renderToStaticMarkup(<KeyboardSection />);
assert.ok(html.includes("Owned by a surface — not rebindable"));
assert.ok(!html.includes(`data-testid="kbd-rebind-question-next-row"`), "surface keys stay put");
assert.ok(!html.includes(`data-testid="kbd-rebind-model-menu-pick"`));
});
});