Skip to content

Commit 8e892f7

Browse files
author
Laurent Guitton
committed
perf(desktop): measure virtualized commit row heights dynamically
Use the virtualizer's measureElement to read each row's real DOM height instead of assuming a fixed 72px, so rows with ref badges, AI reasons, or wrapped messages no longer overlap or leave gaps. 🪄 Commit via GitWand
1 parent c1612e5 commit 8e892f7

1 file changed

Lines changed: 32 additions & 8 deletions

File tree

apps/desktop/src/components/CommitLog.vue

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,12 @@ const rows = computed<Row[]>(() => {
6868
return items;
6969
});
7070
71-
// Row heights — must match the CSS values (.log-section-label padding
72-
// + font + border = ~24px, .log-commit-row = 72px). The estimate is
73-
// per-index so section labels don't inherit the commit-row height
74-
// (visible regression: legends "unpushed" / "pushed" rendered at 72px).
75-
const COMMIT_ROW_H = 72;
71+
// Estimated row heights used as initial guess before DOM measurement.
72+
// The virtualizer measures actual rendered heights via measureElement, so
73+
// these values only affect the initial scroll-height estimate and the
74+
// position of items before they are painted for the first time.
75+
// Keeping them close to reality avoids visible layout jumps on first render.
76+
const COMMIT_ROW_H = 56; // ~2 text lines + meta row + padding, no refs
7677
const SECTION_ROW_H = 24;
7778
7879
const virtualizer = useVirtualizer({
@@ -82,7 +83,11 @@ const virtualizer = useVirtualizer({
8283
const row = rows.value[index];
8384
return row && row.type !== "commit" ? SECTION_ROW_H : COMMIT_ROW_H;
8485
},
85-
overscan: 5,
86+
// Measure each item's actual DOM height after paint so rows with
87+
// ref badges, AI reason lines, or wrapped messages display at their
88+
// real height instead of the fixed 72px estimate.
89+
measureElement: (el) => (el as HTMLElement).offsetHeight,
90+
overscan: 8,
8691
});
8792
8893
// NOTE: the watcher that syncs `rows.value.length` into the virtualizer
@@ -111,12 +116,20 @@ function sectionLabelText(row: Row): string {
111116
}
112117
113118
function vrStyle(vr: { start: number; size: number }) {
119+
// height: vr.size keeps items from overlapping during the estimate phase.
120+
// measureElement is attached to the INNER content elements (commit-item /
121+
// log-section-label), not this wrapper — so offsetHeight reads the true
122+
// content height, not the explicitly-set wrapper height. On the next render
123+
// vr.size is corrected to the measured value and the wrapper matches the
124+
// content exactly. overflow: visible lets content show even if it briefly
125+
// exceeds the estimate before the first measurement lands.
114126
return {
115127
position: "absolute" as const,
116128
top: 0,
117129
left: 0,
118130
width: "100%",
119131
height: vr.size + "px",
132+
overflow: "visible" as const,
120133
transform: "translateY(" + vr.start + "px)",
121134
};
122135
}
@@ -445,9 +458,18 @@ function authorColor(name: string): string {
445458

446459
<div ref="scrollContainerRef" class="log-list" v-else-if="displayedEntries.length > 0" @scroll.passive="onLogScroll">
447460
<div :style="{ height: virtualizer.getTotalSize() + 'px', position: 'relative', width: '100%' }">
448-
<div v-for="vr in virtualizer.getVirtualItems()" :key="'' + vr.key" :style="vrStyle(vr)">
461+
<div
462+
v-for="vr in virtualizer.getVirtualItems()"
463+
:key="'' + vr.key"
464+
:style="vrStyle(vr)"
465+
>
449466
<template v-if="isSectionRow(rows[vr.index])">
450-
<div class="log-section-label" :class="sectionLabelClass(rows[vr.index])">
467+
<div
468+
class="log-section-label"
469+
:class="sectionLabelClass(rows[vr.index])"
470+
:data-index="vr.index"
471+
:ref="(el) => { if (el) virtualizer.measureElement(el as Element); }"
472+
>
451473
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true">
452474
<path d="M8 13V3M5 6l3-3 3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
453475
</svg>
@@ -457,6 +479,8 @@ function authorColor(name: string): string {
457479
<template v-else>
458480
<div
459481
class="commit-item"
482+
:data-index="vr.index"
483+
:ref="(el) => { if (el) virtualizer.measureElement(el as Element); }"
460484
:class="{
461485
'commit-item--selected': selectedHash === c(vr.index).hashFull,
462486
'commit-item--unpushed': isUnpushed(c(vr.index)),

0 commit comments

Comments
 (0)