AURORA-1836: фиксированная высота строк вместо measureElement #222

Open
aep wants to merge 2 commits from feat/211-aurora-1836-row-measure into test
Owner

Closes #211

Jira: AURORA-1836

Что сделано

  • Новый хелпер createFixedRowVirtualizerOptions({ rowHeight, overscan, extra }) (RealtimeTable/utils/virtualizerOptions.js): estimateSize и measureElement возвращают константу, элемент не читается (нет offsetHeight/getBoundingClientRect, нет синхронного layout).
  • Лист (RealtimeTable.jsx), свод 1 (SummaryTable.jsx), свод 2 (Svod2Page.jsx) переведены на хелпер.
  • Высота строки задана явно: строке (muiTableBodyRowProps.style.height) и ячейке (height + overflow: hidden, box-sizing: border-box), чтобы раскладка и виртуализатор не расходились.
  • Мёртвая BASE_TABLE_CONFIG.muiTableRowProps (80 px / 5rem, MRT v3 её не читает) удалена; TABLE_ROW_HEIGHT теперь '3rem'.
  • Overscan строк свода 2: 8 -> 3. Overscan строк листа не менялся (AURORA-1837).
  • Фактическая высота сохранена, не 80 px.

Тесты

  • node --test web/src/components/RealtimeTable/utils/*.test.js: 13 passed, 0 failed (включая virtualizerOptions.test.js с Proxy, бросающим на любое чтение свойства элемента).
  • npm run build проходит. biome check по изменённым файлам: новых замечаний нет (в RealtimeTable.jsx, SummaryTable.jsx, Svod2Page.jsx остались прежние format/organizeImports/lint, репозиторий biome-ом не отформатирован).
  • Бэк не менялся, pytest не запускался.

Проверить руками

  • Лист FORM_2/AHR: нет наездов строк и щелей в начале, середине и конце прокрутки.
  • Последняя строка видна целиком.
  • Сворачивание и разворачивание групп.
  • Масштаб sizeMult 0.8 / 1 / 1.5.
  • Поиск: переход к совпадению (scrollToIndex) попадает в нужную строку.
  • Окна шириной около 1366, 1600, 1920 и более 1920 px: высота зависит от rem (см. R-1), строки не должны наезжать.
  • Свод 1 (с раскрытием строк) и свод 2 (с фильтрами, плашки «Вид сметы»/«ССП»): без наездов и щелей, последняя строка видна.

Замер до/после

Будет добавлен после ночного прогона на стенде perf/.
Сценарии: PERF_RUN_ID=<id> PERF_TARGETS='FORM_2/AHR/*' PERF_ROLES=rf PERF_AXES=y PERF_REPEATS=3 PERF_TRACE=0 npx playwright test scenarios/scroll.spec.ts; своды: PERF_TARGETS='svod/svod1_MAIN,svod/svod2_GO,svod/projects_summaries' PERF_ROLES=dfip PERF_MAX_STEPS=200. Ожидание: в CPU-профиле нет offsetHeight; FORM_2/AHR не хуже -10 % от 89 мс.

Rulings I made

  • R-1: высота строки в rem, а не в px. Фактическая высота листа равна 3rem (minHeight/maxHeight ячейки), а html { font-size: .83vw } при ширине <= 1920 делает rem зависимым от окна: «~40 px» из отчёта perf соответствует окну около 1600 px (3 x 13,3 = 39,8), а на 1920 строка 47,8 px. Постоянный px привёл бы к наездам. Поэтому строка/ячейка заданы в rem, а виртуализатор получает функцию getRootFontSizePx() * 3 (по window.innerWidth, без чтения layout). Цена ошибки: расхождение фо��мулы с App.css даёт наезды/щели; при смене ширины окна ResizeObserver строк снова вызывает measureElement, значение пересчитывается.
  • R-2: высота свода 1 = 2.25rem, свода 2 = 2.75rem. Выведены из стилей (py 0.5rem x 2 + line-height 1.43 x 0.8125rem + 1 px граница ~ 2.2rem; у свода 2 добавлена плашка 0.25rem x 2 + 1.35 x 0.8125rem + рамка ~ 2.7rem), вживую не замерены. Прежние 37 px были лишь оценкой. Цена ошибки: небольшое изменение плотности строк; значение правится константой ROW_HEIGHT_REM.
  • R-3: хелпер принимает rowHeight числом или функцией и extra для прочих опций (scrollPaddingStart/End листа). Цена ошибки: нет.
  • R-4: height/overflow: hidden добавлены только в тело (getTableBodyCellProps), а не в общий getTableHeadCellStyles, чтобы не обрезать элементы заголовка.

🤖 Generated with Claude Code

Closes #211 Jira: AURORA-1836 ## Что сделано - Новый хелпер `createFixedRowVirtualizerOptions({ rowHeight, overscan, extra })` (`RealtimeTable/utils/virtualizerOptions.js`): `estimateSize` и `measureElement` возвращают константу, элемент не читается (нет `offsetHeight`/`getBoundingClientRect`, нет синхронного layout). - Лист (`RealtimeTable.jsx`), свод 1 (`SummaryTable.jsx`), свод 2 (`Svod2Page.jsx`) переведены на хелпер. - Высота строки задана явно: строке (`muiTableBodyRowProps.style.height`) и ячейке (`height` + `overflow: hidden`, `box-sizing: border-box`), чтобы раскладка и виртуализатор не расходились. - Мёртвая `BASE_TABLE_CONFIG.muiTableRowProps` (80 px / 5rem, MRT v3 её не читает) удалена; `TABLE_ROW_HEIGHT` теперь `'3rem'`. - Overscan строк свода 2: 8 -> 3. Overscan строк листа не менялся (AURORA-1837). - Фактическая высота сохранена, не 80 px. ## Тесты - `node --test web/src/components/RealtimeTable/utils/*.test.js`: 13 passed, 0 failed (включая `virtualizerOptions.test.js` с Proxy, бросающим на любое чтение свойства элемента). - `npm run build` проходит. `biome check` по изменённым файлам: новых замечаний нет (в `RealtimeTable.jsx`, `SummaryTable.jsx`, `Svod2Page.jsx` остались прежние format/organizeImports/lint, репозиторий biome-ом не отформатирован). - Бэк не менялся, pytest не запускался. ## Проверить руками - Лист FORM_2/AHR: нет наездов строк и щелей в начале, середине и конце прокрутки. - Последняя строка видна целиком. - Сворачивание и разворачивание групп. - Масштаб sizeMult 0.8 / 1 / 1.5. - Поиск: переход к совпадению (scrollToIndex) попадает в нужную строку. - Окна шириной около 1366, 1600, 1920 и более 1920 px: высота зависит от rem (см. R-1), строки не должны наезжать. - Свод 1 (с раскрытием строк) и свод 2 (с фильтрами, плашки «Вид сметы»/«ССП»): без наездов и щелей, последняя строка видна. ## Замер до/после Будет добавлен после ночного прогона на стенде perf/. Сценарии: `PERF_RUN_ID=<id> PERF_TARGETS='FORM_2/AHR/*' PERF_ROLES=rf PERF_AXES=y PERF_REPEATS=3 PERF_TRACE=0 npx playwright test scenarios/scroll.spec.ts`; своды: `PERF_TARGETS='svod/svod1_MAIN,svod/svod2_GO,svod/projects_summaries' PERF_ROLES=dfip PERF_MAX_STEPS=200`. Ожидание: в CPU-профиле нет `offsetHeight`; FORM_2/AHR не хуже -10 % от 89 мс. ## Rulings I made - R-1: высота строки в rem, а не в px. Фактическая высота листа равна 3rem (minHeight/maxHeight ячейки), а `html { font-size: .83vw }` при ширине <= 1920 делает rem зависимым от окна: «~40 px» из отчёта perf соответствует окну около 1600 px (3 x 13,3 = 39,8), а на 1920 строка 47,8 px. Постоянный px привёл бы к наездам. Поэтому строка/ячейка заданы в rem, а виртуализатор получает функцию `getRootFontSizePx() * 3` (по `window.innerWidth`, без чтения layout). Цена ошибки: расхождение фо��мулы с App.css даёт наезды/щели; при смене ширины окна ResizeObserver строк снова вызывает `measureElement`, значение пересчитывается. - R-2: высота свода 1 = 2.25rem, свода 2 = 2.75rem. Выведены из стилей (py 0.5rem x 2 + line-height 1.43 x 0.8125rem + 1 px граница ~ 2.2rem; у свода 2 добавлена плашка 0.25rem x 2 + 1.35 x 0.8125rem + рамка ~ 2.7rem), вживую не замерены. Прежние 37 px были лишь оценкой. Цена ошибки: небольшое изменение плотности строк; значение правится константой `ROW_HEIGHT_REM`. - R-3: хелпер принимает `rowHeight` числом или функцией и `extra` для прочих опций (`scrollPaddingStart/End` листа). Цена ошибки: нет. - R-4: `height`/`overflow: hidden` добавлены только в тело (`getTableBodyCellProps`), а не в общий `getTableHeadCellStyles`, чтобы не обрезать элементы заголовка. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 2 commits 2026-10-28 17:10:54 +03:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Высота строки и ячейки задана явно в rem, виртуализатор не читает offsetHeight.
Мёртвая muiTableRowProps удалена. Overscan строк свода 2: 8 -> 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Ночной замер на стенде perf/ (29.10, масштаб 1, D0 без grow, CPU 1x, arm-ka-* остановлены; Apple M4)

Сборки: «до» — test 2a28748; «после» — этот PR 12a315b. API и БД одни.
Команды: скролл PERF_TARGETS='FORM_2/AHR/*' PERF_ROLES=rf PERF_AXES=y PERF_REPEATS=3 PERF_TRACE=0 scroll.spec.ts; своды — PERF_ROLES=dfip PERF_TRACE=0 --grep-invert filter без PERF_MAX_STEPS (svod1 3 повтора; свод 2 GO полный: «до» 2 повтора, «после» 1 повтор — время ночи); CPU-профиль — hotspots.spec.ts PERF_HOT_MODE=cpu (:8082, source maps) → cpuprofile_top.py.

main_thread_ms_per_step, медиана до после Δ
FORM_2/AHR, y 89.1 [88.9–89.9] 78.7 [76.6–80.0] −11.7 %
свод 1 MAIN 139.5 116.0 −17 %
свод 1 FORM_1 133.9 110.1 −18 %
сводные проектов 93.9 74.1 −21 %
свод 2 GO (полный, 2046–2074 шага) 180.7 (2 повт.) 149.6 (1 повт.) −17 %

CPU-профиль: get offsetHeight до — 13,8 % FORM_2/AHR и 8,4 % свод 1 MAIN; после — нет ни в одном профиле (FORM_2/AHR, свод 1 MAIN, свод 2 GO); остался только measureElement — virtualizerOptions.js:16 с 0,0 %.

Вердикт по критерию: «нет offsetHeight в CPU-профиле» — выполнен; «FORM_2/AHR ≥ −10 % от 89 мс» — выполнен (−11,7 %, 78.7 мс).

Высота строк (getBoundingClientRect().height, 24–32 строки, все одинаковые):

экран 1366 до → после 1600 до → после 1920 до → после rem после
лист FORM_2/AHR 34.00 → 34.00 39.83 → 39.83 47.80 → 47.80 3rem
свод 1 MAIN 25.48 → 25.50 29.69 → 29.88 35.42 → 35.84 2.25rem
свод 2 GO 32.41 → 31.17 37.44 → 36.52 44.33 → 43.81 2.75rem
Лист — без изменений; свод 1 — +0,2…0,4 px; свод 2 стал ниже на 0,5–1,2 px на строку (было ≈2,82rem фактически, стало 2,75rem) — факт для визуальной проверки, решение за человеком. Наложений/щелей в первых строках нет (высоты всех строк равны).

Скриншоты 1600×900: до — night/base/shots/base-{sheet,svod1,svod2}-1600-*.png, после — night/1836/shots/p1836-*.png; высоты — night/base/shots/base-rowh-rowh.json, night/1836/shots/p1836-rowh-rowh.json; каталог /private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/. Попиксельная сверка — в итоговом комментарии.

Оговорка методики (O-2): замеры «после» сняты после coauthor-прогона на test, который изменил ~130 числовых ячеек FORM_2/AHR (размер ответа 4 291 895 → 4 301 701 байт); структура и число строк те же, на время рендера это не влияет в пределах шума.

🤖 Generated with Claude Code

## Ночной замер на стенде perf/ (29.10, масштаб 1, D0 без grow, CPU 1x, arm-ka-* остановлены; Apple M4) **Сборки:** «до» — `test` 2a28748; «после» — этот PR 12a315b. API и БД одни. **Команды:** скролл `PERF_TARGETS='FORM_2/AHR/*' PERF_ROLES=rf PERF_AXES=y PERF_REPEATS=3 PERF_TRACE=0 scroll.spec.ts`; своды — `PERF_ROLES=dfip PERF_TRACE=0 --grep-invert filter` без `PERF_MAX_STEPS` (svod1 3 повтора; свод 2 GO полный: «до» 2 повтора, «после» **1 повтор** — время ночи); CPU-профиль — `hotspots.spec.ts PERF_HOT_MODE=cpu` (:8082, source maps) → `cpuprofile_top.py`. | main_thread_ms_per_step, медиана | до | после | Δ | |---|---|---|---| | FORM_2/AHR, y | 89.1 [88.9–89.9] | **78.7** [76.6–80.0] | **−11.7 %** | | свод 1 MAIN | 139.5 | 116.0 | −17 % | | свод 1 FORM_1 | 133.9 | 110.1 | −18 % | | сводные проектов | 93.9 | 74.1 | −21 % | | свод 2 GO (полный, 2046–2074 шага) | 180.7 (2 повт.) | 149.6 (1 повт.) | −17 % | CPU-профиль: `get offsetHeight` до — 13,8 % FORM_2/AHR и 8,4 % свод 1 MAIN; **после — нет ни в одном профиле** (FORM_2/AHR, свод 1 MAIN, свод 2 GO); остался только `measureElement — virtualizerOptions.js:16` с 0,0 %. **Вердикт по критерию:** «нет `offsetHeight` в CPU-профиле» — **выполнен**; «FORM_2/AHR ≥ −10 % от 89 мс» — **выполнен** (−11,7 %, 78.7 мс). Высота строк (`getBoundingClientRect().height`, 24–32 строки, все одинаковые): | экран | 1366 до → после | 1600 до → после | 1920 до → после | rem после | |---|---|---|---|---| | лист FORM_2/AHR | 34.00 → 34.00 | 39.83 → 39.83 | 47.80 → 47.80 | 3rem | | свод 1 MAIN | 25.48 → 25.50 | 29.69 → 29.88 | 35.42 → 35.84 | 2.25rem | | свод 2 GO | 32.41 → **31.17** | 37.44 → **36.52** | 44.33 → **43.81** | 2.75rem | Лист — без изменений; свод 1 — +0,2…0,4 px; **свод 2 стал ниже на 0,5–1,2 px на строку** (было ≈2,82rem фактически, стало 2,75rem) — факт для визуальной проверки, решение за человеком. Наложений/щелей в первых строках нет (высоты всех строк равны). Скриншоты 1600×900: до — `night/base/shots/base-{sheet,svod1,svod2}-1600-*.png`, после — `night/1836/shots/p1836-*.png`; высоты — `night/base/shots/base-rowh-rowh.json`, `night/1836/shots/p1836-rowh-rowh.json`; каталог `/private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/`. Попиксельная сверка — в итоговом комментарии. Оговорка методики (O-2): замеры «после» сняты после coauthor-прогона на `test`, который изменил ~130 числовых ячеек FORM_2/AHR (размер ответа 4 291 895 → 4 301 701 байт); структура и число строк те же, на время рендера это не влияет в пределах шума. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Ночной замер — часть 2 (итог)

Сборки как в части 1 (до — test, после — 12a315b). Добавлено: попиксельная сверка и combo.

Сверка скриншотов 1600×900 (base против p1836), доля отличающихся пикселей:

экран % что на картинках
лист FORM_2/AHR, 7 позиций 2,4 – 12,0 текст строк «двоится» со сдвигом, растущим вниз по окну; высоты строк в rowh те же (34.00 / 39.83 / 47.80)
свод 1 (3), сводные 6,6 – 9,6; 6,5 то же + сдвиг 0,02–0,4 px на строку (25.48 → 25.50 …)
свод 2 (6 позиций) 5,2 – 8,7 строки ниже на 0,5–1,2 px (32.41 → 31.17 при 1366; 37.44 → 36.52 при 1600; 44.33 → 43.81 при 1920)
Гипотеза про сдвиг (не измерялась): в «до» позиции строк считались по округлённой offsetHeight (≈ 40 при реальных 39.83), в «после» — по 3rem. Выделенных расхождений состава колонок/текста нет. Высота свода 2 — по-прежнему решение человека.

combo (все PR волны 1): строки в combo те же, что у 1836 (combo-rowh-rowh.json и p1836-rowh-rowh.json равны: лист 34.00 / 39.83 / 47.80, свод 1 25.50 / 29.88 / 35.84, свод 2 31.17 / 36.52 / 43.81); offsetHeight нет ни в одном из 4 CPU-профилей combo.
Лист y: base 89.1 → 1836 78.7 → combo 46.0; свод 1 MAIN 139.5 → 116.0 → 83.6; свод 2 GO 180.7 → 149.6 → 32.6 (1 повтор).

Вердикт без изменений: «нет offsetHeight» и «FORM_2/AHR ≥ −10 %» — выполнен.
Файлы: /private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/imgdiff.txt, imgdiff/diff-p1836-*.png, 1836/shots/, combo/shots/.

## Ночной замер — часть 2 (итог) Сборки как в части 1 (до — `test`, после — 12a315b). Добавлено: попиксельная сверка и combo. **Сверка скриншотов 1600×900** (base против p1836), доля отличающихся пикселей: | экран | % | что на картинках | |---|---|---| | лист FORM_2/AHR, 7 позиций | 2,4 – 12,0 | текст строк «двоится» со сдвигом, растущим вниз по окну; высоты строк в `rowh` те же (34.00 / 39.83 / 47.80) | | свод 1 (3), сводные | 6,6 – 9,6; 6,5 | то же + сдвиг 0,02–0,4 px на строку (25.48 → 25.50 …) | | свод 2 (6 позиций) | 5,2 – 8,7 | строки ниже на 0,5–1,2 px (32.41 → 31.17 при 1366; 37.44 → 36.52 при 1600; 44.33 → 43.81 при 1920) | Гипотеза про сдвиг (не измерялась): в «до» позиции строк считались по округлённой `offsetHeight` (≈ 40 при реальных 39.83), в «после» — по 3rem. Выделенных расхождений состава колонок/текста нет. Высота свода 2 — по-прежнему решение человека. **combo** (все PR волны 1): строки в combo те же, что у 1836 (`combo-rowh-rowh.json` и `p1836-rowh-rowh.json` равны: лист 34.00 / 39.83 / 47.80, свод 1 25.50 / 29.88 / 35.84, свод 2 31.17 / 36.52 / 43.81); `offsetHeight` нет ни в одном из 4 CPU-профилей combo. Лист y: base 89.1 → 1836 78.7 → combo 46.0; свод 1 MAIN 139.5 → 116.0 → 83.6; свод 2 GO 180.7 → 149.6 → 32.6 (1 повтор). **Вердикт без изменений:** «нет offsetHeight» и «FORM_2/AHR ≥ −10 %» — **выполнен**. Файлы: `/private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/imgdiff.txt`, `imgdiff/diff-p1836-*.png`, `1836/shots/`, `combo/shots/`.
This pull request can be merged automatically.
You are not authorized to merge this pull request.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/211-aurora-1836-row-measure:feat/211-aurora-1836-row-measure
git checkout feat/211-aurora-1836-row-measure
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/DFiP_Budget_planing#222
No description provided.