ui: round 8 — fuzzy session search, stacked doors, All right, teach divider at the step seam

- SESSION SEARCH goes fuzzy on the SAME fuzzyScore the model pickers
  use (de0c286's util — one scoring across every search surface):
  ranked by score instead of raw substring; empty query keeps order.
- SIDEBAR: New Teaching / New Practice STACK (full-width rows); the
  kind filter reads Teaching · Practice · All — the real modes first,
  All as the escape hatch, mirroring the stack above. Home stays in the
  LH band (owner-confirmed).
- TEACH DIVIDER, the stacked-dividers bug: the plan presentation and
  the first tagged quiz call usually share ONE assistant message, so
  the message-level seam drew Planning+Teaching back-to-back. New pure
  teachStepIndex(parts) finds the STEP boundary governing the flip
  call; the divider renders there (replacing that boundary's plain
  hairline — a labeled rule supersedes it), prepending before the whole
  message only when the flip is in its first step. The fold (chip,
  sidebar state) is untouched.
- JOBS SUITE, flake round 3: the sweep scans the SHARED jobs dir, so
  parallel test FILES' running records respawn into this file's
  counters — the assertions go jurisdictional (own-job ids collected;
  foreign respawns are not this test's business). 1331/1331.
This commit is contained in:
amosblomqvist
2026-10-05 06:08:51 +03:00
parent 6b58d87d9e
commit 450e0f78f0
5 changed files with 155 additions and 26 deletions
+48
View File
@@ -13,6 +13,7 @@ import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { renderToStaticMarkup } from "react-dom/server";
import { PhaseChip, PhaseDivider, planNodeCount } from "../components/phase-divider.tsx";
import { teachStepIndex } from "../src/core/phases.ts";
describe("PhaseChip (header)", () => {
it("renders the learner-facing label — no colored dot (phase color retired)", () => {
@@ -79,6 +80,53 @@ describe("PhaseDivider (transcript boundary)", () => {
});
});
describe("teachStepIndex (WHERE teaching begins inside the flipping message — owner round 8)", () => {
const call = { type: "tool-quiz", input: { nodeId: "n1", question: "q?" } };
it("the flip call in the message's FIRST step → -1 (divider prepends before the message)", () => {
assert.equal(teachStepIndex([{ type: "step-start" }, { type: "text" }, call]), -1);
});
it("a later step: the boundary's step-start index — between plan presentation and teaching", () => {
// The stacked-dividers bug shape: plan_init + plan text, then a NEW
// step that poses the first tagged quiz. The seam is the SECOND
// step-start (index 3), not before the message.
const parts = [
{ type: "step-start" },
{ type: "tool-plan_init", state: "output-available" },
{ type: "text", text: "Here is your plan." },
{ type: "step-start" },
{ type: "text", text: "Let's begin." },
call,
];
assert.equal(teachStepIndex(parts), 3);
});
it("no step-start parts at all (defensive): first step → -1", () => {
assert.equal(teachStepIndex([{ type: "text" }, call]), -1);
});
it("an untagged quiz call is not the flip — scan continues to the tagged one", () => {
const parts = [
{ type: "step-start" },
{ type: "tool-quiz", input: { question: "probe quiz — no nodeId" } },
{ type: "step-start" },
call,
];
assert.equal(teachStepIndex(parts), 2);
});
it("explain calls carry nodeId too — same seam", () => {
const parts = [
{ type: "step-start" },
{ type: "text" },
{ type: "step-start" },
{ type: "tool-explain", input: { nodeId: "n2", topic: "t" } },
];
assert.equal(teachStepIndex(parts), 2);
});
});
describe("planNodeCount (the seam message's tree)", () => {
it("counts the first output-available plan tree in the message", () => {
const count = planNodeCount({