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
+39
View File
@@ -96,6 +96,45 @@ function advance(
return phase;
}
/**
* WHERE inside the flipping message teaching begins (owner round 8,
* 2026-10-05 — the stacked-dividers bug): plan→teach flips at the first
* tagged quiz/explain CALL, but the plan PRESENTATION often lives in the
* same assistant message — an earlier provider STEP than the quiz call.
* A message-level seam therefore drew the Teaching divider before the
* plan presentation too (Planning and Teaching stacked back-to-back).
* This returns the part index of the STEP BOUNDARY governing the flip
* call: -1 when the call sits in the message's FIRST step (the divider
* belongs before the whole message), else the index of the step-start
* part whose generation contains the call — the divider renders there,
* between the plan presentation and the teaching block.
*/
export function teachStepIndex(
parts: ReadonlyArray<{ type: string; state?: string; input?: unknown }>,
): number {
let stepStarts = 0; // how many step-start parts seen so far
let latest = -1; // index of the latest step-start
for (let i = 0; i < parts.length; i++) {
const part = parts[i]!;
if (part.type === "step-start") {
stepStarts++;
latest = i;
continue;
}
if (
(part.type === "tool-quiz" || part.type === "tool-explain") &&
taggedNodeId(part.input)
) {
// The call's governing step: 0–1 step-starts seen = the message's
// FIRST step → -1 (the divider belongs before the whole message);
// a later step → that step-start's index (the divider renders at
// the boundary, replacing the plain hairline there).
return stepStarts <= 1 ? -1 : latest;
}
}
return -1;
}
/**
* The phase at EVERY message index, in one forward fold — the transcript
* dividers render one per boundary, so phaseAt3(m, i) per index would rescan