Files
learnharness/tests/reveal-arrival.test.ts
amosblomqvist e9b9f52a7e fix: question dock flicker — arrival state at render, growth-stamped fold mute
Two owner-reported symptoms, one mechanism (verified frame-by-frame on a
zero-model-call fixture): the position machine read its OWN growth-driven
scroll lag as the learner leaving.

1. Probe arrival at the bottom: the form opened, the reserve grew, the
   smooth catch-up lagged (d peaked ~405px > COLLAPSE_DIST 96) → phantom
   fold → bar shrink morph → re-expand at bottom. FORM→BAR→FORM, ~600ms.
   Fix: the at-bottom arrival now runs under the SAME fold suppression
   the teach reveal uses (suppressFoldDuringReveal, extracted) — the
   arrival's open and its scroll-to-bottom are the same motion class.

2. Probe arrival scrolled up: one stale-open frame painted before the
   after-paint effect collapsed it. Fix: probeArrivalExpanded — the
   open/closed state decided DURING RENDER (open iff at bottom), like
   the teach seam; the effect keeps only the reveal scroll.

3. Dictation stop flush into the pending question: the field grows on
   its own render (capped 192px), the reserve lands on the NEXT
   DockOverlay render (~1s later live), and the spacer's growth spiked
   d≈152 → fold → bar → re-expand. Fix: the offer stamps the geometry
   mute when the reserve GROWS while pending — the spacer is the
   phantom-scroll source, so the mute covers exactly its scroll events.
   Shrinks never stamp (the resize round's stuck-collapsed regression).

Live-verified on :3001 (job-settle fixture, abort-channel settle, no
model calls): A at-bottom arrival FORM-only; B scrolled-up arrival
straight to BAR with zero FORM frames and no scroll-down; C dictation
flush FORM-only through the +152px reserve growth.
2026-09-26 23:53:43 +03:00

62 lines
2.6 KiB
TypeScript

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { probeArrivalExpanded, revealArrival, shouldStampGeometryMute } from "../components/chat.tsx";
/**
* Teach-question arrival states (owner ruling, scroll-honesty round
* 2026-09-25). Applied DURING RENDER before the first paint — the bug was
* the stale expanded=true left by the previous question painting one
* frame of the open question: the dock measured itself tall, reserved that
* height permanently (fold immunity refused the give-back), and the
* transcript scrolled as if the collapsed question had opened.
*/
/**
* The geometry-mute's growth rule (flicker round, 2026-09-26): reserve
* GROWTH while a question is pending is the dock stretching itself (the
* dictation flush landing many dictated lines at once, reproduced d≈152 >
* COLLAPSE_DIST) — the spacer's scroll catch-up must not read as the
* learner leaving. Shrinks are the fold's own give-back and never stamp.
*/
describe("shouldStampGeometryMute — when a block resize is our own stretch", () => {
it("growth while a question is pending stamps (dictation flush, arrival stretch)", () => {
assert.equal(shouldStampGeometryMute(545, 393, true), true);
});
it("sub-pixel growth does not stamp (rounding noise)", () => {
assert.equal(shouldStampGeometryMute(394, 393, true), false);
});
it("shrink never stamps — the fold's own morph must not self-mute the re-expand", () => {
assert.equal(shouldStampGeometryMute(40, 545, true), false);
});
it("no growth stamps without a pending question (composer life)", () => {
assert.equal(shouldStampGeometryMute(545, 393, false), false);
});
});
describe("revealArrival — how a teach question lands", () => {
it("fresh arrival after the composer: collapsed behind the reveal button", () => {
assert.deepEqual(revealArrival(false), { expanded: false, revealed: false });
});
it("queued behind another question: open — the reading already happened", () => {
assert.deepEqual(revealArrival(true), { expanded: true, revealed: true });
});
});
/**
* Probe-question arrival state (flicker round, 2026-09-26): open iff the
* learner is following at the bottom; scrolled up in history → arrive as
* the collapsed bar, decided DURING RENDER (the old after-paint effect
* painted one stale-open frame first).
*/
describe("probeArrivalExpanded — how a probe question lands", () => {
it("at the bottom following along: open", () => {
assert.equal(probeArrivalExpanded(true), true);
});
it("scrolled up in history: arrive already-collapsed (owner ruling)", () => {
assert.equal(probeArrivalExpanded(false), false);
});
});