Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/web/src/features/transcript/TranscriptTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ export const TranscriptTimeline = memo(function TranscriptTimeline({
// restored to a mid-scroll anchor mask with the plain transcript background
// instead (never wrong content). Removal is layout-driven (rAF poll of the
// list's content), not a timer.
const REVEAL_STABILITY_FRAMES = 8;
// Readiness already requires stable content geometry, correct tail alignment,
// and a row inside the viewport. Four consecutive ready frames absorb one
// delayed list remeasure without holding the warm copy for eight display cycles.
const REVEAL_STABILITY_FRAMES = 4;
const [coldMount, setColdMount] = useState(true);
useEffect(() => {
let frame = 0;
Expand Down
1 change: 1 addition & 0 deletions apps/web/test/interaction-motion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ describe("session refresh renders without flicker", () => {
it("cold list mounts are masked by a warm overlay removed on layout, not a timer", () => {
expect(timeline).toContain("coldMount");
expect(timeline).toContain("legend-list-content-container");
expect(timeline).toContain("REVEAL_STABILITY_FRAMES = 4");
expect(timeline).not.toMatch(/setTimeout\([^)]*coldMount/i);
});

Expand Down
9 changes: 5 additions & 4 deletions docs/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ need to be distinguished. Raw samples are retained; the comparison uses the medi
p95.

The UI report keeps the whole-test duration and also separates the browser work into navigation,
connection after page load, transcript-shell visibility, real-list reveal, and tail paint. The tail
paint sample is taken after the real list replaces its warm loading copy and two browser animation
frames have elapsed. These numbers show where a slowdown occurs; they are not a claim about a
physical display's pixel response time.
connection after page load, transcript-shell visibility, tail alignment, real-list reveal, and tail
paint. The timeline requires four consecutive ready frames after its content geometry and tail
alignment settle before replacing the warm loading copy. The tail paint sample is taken after the
real list replaces that copy and two more browser animation frames have elapsed. These numbers show
where a slowdown occurs; they are not a claim about a physical display's pixel response time.

`ui.mount-bounded-10k` stops after the original mount assertions, before the paint-only wait and
phase-file write. `ui.playwright-scenario-instrumented` records the full instrumented test duration
Expand Down
25 changes: 25 additions & 0 deletions e2e/remote-app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ test("@soak mounts the bounded tail of a 10k history on a phone viewport", async
await page.evaluate(() => {
const phases: {
transcriptVisibleAt?: number;
tailAlignedAt?: number;
realListVisibleAt?: number;
tailPaintedAt?: number;
} = {};
Expand All @@ -500,6 +501,25 @@ test("@soak mounts the bounded tail of a 10k history on a phone viewport", async
}
const realList = transcript?.querySelector(".legend-list-content-container") ?? null;
const overlay = transcript?.querySelector("[data-cold-mount-overlay]") ?? null;
const scroller = [...(transcript?.querySelectorAll<HTMLElement>("div") ?? [])].find(
(element) => {
const { overflowY } = getComputedStyle(element);
return overflowY === "auto" || overflowY === "scroll";
},
);
const transcriptRect = transcript?.getBoundingClientRect();
const rows = [...(transcript?.querySelectorAll<HTMLElement>("[data-transcript-row]") ?? [])];
const rowsInView = transcriptRect !== undefined && rows.some((row) => {
const rect = row.getBoundingClientRect();
return rect.bottom > transcriptRect.top && rect.top < transcriptRect.bottom;
});
if (scroller !== undefined && transcriptRect !== undefined) {
const maxScroll = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
const aligned = Math.abs(scroller.scrollTop - maxScroll) <= 1;
if (phases.tailAlignedAt === undefined && aligned && rowsInView) {
phases.tailAlignedAt = performance.now();
}
}
Comment thread
wolfiesch marked this conversation as resolved.
Outdated
if (
phases.realListVisibleAt === undefined
&& isVisible(transcript)
Expand Down Expand Up @@ -538,13 +558,15 @@ test("@soak mounts the bounded tail of a 10k history on a phone viewport", async
window as typeof window & {
__t4BrowserPaintPhases?: {
transcriptVisibleAt?: number;
tailAlignedAt?: number;
realListVisibleAt?: number;
tailPaintedAt?: number;
};
}
).__t4BrowserPaintPhases;
return phases !== undefined
&& Number.isFinite(phases.transcriptVisibleAt)
&& Number.isFinite(phases.tailAlignedAt)
&& Number.isFinite(phases.realListVisibleAt)
&& Number.isFinite(phases.tailPaintedAt)
? phases
Expand All @@ -557,6 +579,7 @@ test("@soak mounts the bounded tail of a 10k history on a phone viewport", async
await phasesHandle.dispose();
if (
phases.transcriptVisibleAt === undefined
|| phases.tailAlignedAt === undefined
|| phases.realListVisibleAt === undefined
|| phases.tailPaintedAt === undefined
) {
Expand All @@ -571,6 +594,8 @@ test("@soak mounts the bounded tail of a 10k history on a phone viewport", async
navigationDomContentLoaded: navigationTiming.domContentLoaded,
connectedAfterDomContentLoaded: connectedAt - navigationTiming.domContentLoaded,
sessionClickToTranscriptVisible: phases.transcriptVisibleAt - sessionClickStartedAt,
sessionClickToTailAligned: phases.tailAlignedAt - sessionClickStartedAt,
tailAlignedToRealListVisible: phases.realListVisibleAt - phases.tailAlignedAt,
sessionClickToRealListVisible: phases.realListVisibleAt - sessionClickStartedAt,
sessionClickToTailPainted: phases.tailPaintedAt - sessionClickStartedAt,
})}\n`,
Expand Down
12 changes: 12 additions & 0 deletions scripts/perf/ui.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const phaseSamples = {
navigationDomContentLoaded: [],
connectedAfterDomContentLoaded: [],
sessionClickToTranscriptVisible: [],
sessionClickToTailAligned: [],
tailAlignedToRealListVisible: [],
sessionClickToRealListVisible: [],
sessionClickToTailPainted: [],
};
Expand Down Expand Up @@ -122,6 +124,16 @@ writeReport(
direction: "lower",
...summarize(phaseSamples.sessionClickToTranscriptVisible),
},
{
name: "browser.session-click-to-tail-aligned",
direction: "lower",
...summarize(phaseSamples.sessionClickToTailAligned),
},
{
name: "browser.tail-aligned-to-real-list-visible",
direction: "lower",
...summarize(phaseSamples.tailAlignedToRealListVisible),
},
{
name: "browser.session-click-to-real-list-visible",
direction: "lower",
Expand Down
Loading