···88## [0.23.5] — 2026-04-06
991010### Fixed
1111+- Frozen column background now survives scrolling: compositing layer + inline !important prevent WebKit paint drops and CSS override (#441)
1212+- Version badge no longer overlapped by sheet UI elements (z-index 1→50) (#441)
1313+- Frozen column transparent background in dark mode when scrolling (#440)
1114- Sheets: spill system no longer silently overwrites user-entered data in cells that were previously spill targets (#431)
1215- Sheets: frozen columns/rows now always have an opaque background in dark mode, preventing scrolled content from showing through (#432)
1316···247250- Fix E2E test flakiness: replace page reload with addInitScript, add waitForURL before waitForSelector (#305)
248251249252### Changed
253253+- QA batch 20: continued edge case coverage expansion (#439)
254254+- QA batch 19: theming, automations, range-highlight, export edge cases (#438)
255255+- QA batch 18: formulas & pivot-table edge cases (#437)
256256+- QA batch 17: more untested pure-logic modules (#436)
250257- No unit tests for server API endpoints or WebSocket relay (#413)
251258- No unit tests for forms builder, conditional logic, or response pipeline (#412)
252259- CSS: dark mode colors use oklch() which is not supported in Firefox <113 or Safari <15.4 - no fallback colors defined (#408)
+8-2
src/css/app.css
···26332633.sheet-grid td.frozen-corner {
26342634 position: sticky;
26352635 background: var(--color-bg, #fff);
26362636+ /* Force own compositing layer — prevents WebKit/Blink from dropping the
26372637+ background paint of sticky cells during fast scrolling (oklch + custom
26382638+ properties exacerbate the bug). */
26392639+ -webkit-backface-visibility: hidden;
26402640+ backface-visibility: hidden;
26362641}
2637264226382643[data-theme="dark"] .sheet-grid td.frozen-row,
···73227327 font-style: italic;
73237328}
7324732973257325-/* Version badge */
73307330+/* Version badge — z-index must exceed sticky cells (max 5) and sheet UI
73317331+ but stay below modals/dialogs (1000+). */
73267332.version-badge {
73277333 position: fixed;
73287334 bottom: 0.5rem;
···73327338 color: var(--color-text-faint, oklch(0.6 0 0));
73337339 opacity: 0.5;
73347340 pointer-events: none;
73357335- z-index: 1;
73417341+ z-index: 50;
73367342 user-select: none;
73377343}
73387344
+17-8
src/sheets/main.ts
···593593 const cfStyleStr = buildCfStyle(cfResult);
594594595595 // Background on td so inset box-shadow grid lines paint on top.
596596- // Frozen cells MUST have an opaque background to occlude scrolled content behind them.
596596+ // Frozen cells use !important so CSS rules (.in-range etc.) can't replace with semi-transparent bg.
597597+ const isFrozenCell = c <= freezeC || r <= freezeR;
597598 const cellBg = getCellBgColor(cellData, cfStyleStr);
598599 if (cellBg) {
599599- tdStyle += 'background:' + cellBg + ';';
600600- } else if (c <= freezeC || r <= freezeR) {
601601- tdStyle += 'background:var(--color-bg);';
600600+ tdStyle += 'background:' + cellBg + (isFrozenCell ? ' !important' : '') + ';';
601601+ } else if (isFrozenCell) {
602602+ tdStyle += 'background:var(--color-bg) !important;';
602603 }
603604604605 const styleAttr = tdStyle ? ' style="' + tdStyle + '"' : '';
···23872388 displayDiv.style.cssText = 'padding:0;overflow:hidden;' + getCellStyle(cellData, '');
23882389 }
23892390 // Background on td for sparklines too
23902390- // Frozen cells MUST keep an opaque inline background to occlude scrolled content
23912391+ // Frozen cells use !important so CSS rules like .in-range can't override with semi-transparent bg
23912392 const bg = getCellBgColor(cellData, '');
23922392- td.style.background = bg || (isFrozen ? 'var(--color-bg)' : '');
23932393+ if (isFrozen) {
23942394+ td.style.setProperty('background', bg || 'var(--color-bg)', 'important');
23952395+ } else {
23962396+ td.style.background = bg || '';
23972397+ }
23932398 hasSparklines = true;
23942399 } else {
23952400 // Remove sparkline canvas if value is no longer a sparkline
···24002405 const cfStyleStr = buildCfStyle(cfResult);
24012406 displayDiv.style.cssText = getCellStyle(cellData, cfStyleStr);
24022407 // Background on td so inset box-shadow grid lines paint on top
24032403- // Frozen cells MUST keep an opaque inline background to occlude scrolled content
24082408+ // Frozen cells use !important so CSS rules like .in-range can't override with semi-transparent bg
24042409 const bg = getCellBgColor(cellData, cfStyleStr);
24052405- td.style.background = bg || (isFrozen ? 'var(--color-bg)' : '');
24102410+ if (isFrozen) {
24112411+ td.style.setProperty('background', bg || 'var(--color-bg)', 'important');
24122412+ } else {
24132413+ td.style.background = bg || '';
24142414+ }
24062415 // Update wrap class
24072416 if (cellData?.s?.wrap) displayDiv.classList.add('cell-wrap');
24082417 else displayDiv.classList.remove('cell-wrap');