AURORA-1843: свод 2 — догрузка строк страницами, поиск и фильтры через сервер (2/3, фронт) #243

Open
aep wants to merge 14 commits from feat/236-aurora-1843-svod2-server-paging-web into feat/236-aurora-1843-svod2-server-paging
Owner

Refs #236 (часть 2/3; Closes #236 будет в PR 3). Jira AURORA-1843 (эпик AURORA-1834). Стек: PR 1 (#241) → этот PR → PR 3. Spec: openspec/changes/aurora-1843-svod2-server-paging/.

Что сделано (фаза 2 — фронт)

  • Свод 2 получает строки страницами с сервера (view=compact, страница 300 строк), поиск и три колоночных фильтра считает сервер, варианты фильтров приходят из /facets. В памяти вкладки — окно из трёх страниц (LRU 5) + заглушки; та же таблица MRT, отступы виртуализатора (paddingStart/paddingEnd) задают высоту прокрутки по total.
  • Чистая логика: svod2Paging.js (окно, срез, LRU, параметры, редьюсер состояния вкладки с поколениями, shouldRefine, shouldShowFound); хук useSvod2Pages.js ходит в API (один AbortController на поколение, settled для каждого поколения, нет утечек rAF/контроллеров). SummaryApi.getSvod2Page/getSvod2Facets.
  • Единый источник высоты строки окна — измерение первой строки с данными (по умолчанию 37 px), ROW_VIRTUALIZER_OPTIONS, SvodContext, SvodFilters и свод 1 не менялись.
  • Для пользователя поведение прежнее: подписи, условия disabled, подсказка «Нажмите «Загрузить»…», Alert с «Повторить», сброс колоночных фильтров, «Найдено N из M».

Тесты

  • node --test (RealtimeTable utils, SvodPage, Svod2Page): 52/52; npm run build ок; git grep getSummary|invalidateSummary|matchesSearch в Svod2Page пуст.
  • Проверка предпосылки (Task 9): MRT 3.2.1 разворачивает пользовательские rowVirtualizerOptions последними, высота tbody = getTotalSize().
  • biome: одна ошибка формата в Svod2Page.jsx (scaleNumericColumns, <Tab>) — строки не менялись относительно 399234a.

Чек-лист ручной проверки (главный контроллер, на стенде против сборки «до» на тех же данных)

  • GO и RF, admin и dfip, «Все» и выбор организаций.
  • Поиск, три фильтра и их сочетания, варианты с числами, «Найдено», сброс фильтров.
  • Вкладки и положение прокрутки; ползунок в середину и в конец.
  • Быстрый ввод, затем фильтр; ввод поиска во время первой загрузки; возврат к ранее загруженным условиям (A→B→A).
  • Высота строки (R-9): нет наложения строк и пустоты, «Обновить» на глубине сохраняет позицию; зазоры между заглушками.
  • Варианты select-ов обновляются после смены поиска/фильтров.
  • Единицы, «Перейти к задачам», горизонтальная прокрутка, ошибка догрузки.
  • Скриншот��.

Замер до/после — добавит контроллер после прогона на стенде.

Rulings I made

  • R-1 (design #35): Ruling: решения плана #23–#34 одобрены главным контроллером как есть — цена ошибки: нет.
  • R-2 (design #36): Ruling: хелпер legacy_full живёт в api/tests/integration/conftest.py (не в test_svod2_paging_api.py); Task 3 создаёт его там, Task 4 импортирует оттуда — иначе test_svod2_sql.py импортировал бы из тестового модуля. Цена ошибки: перенос функции.
  • R-3 (design #37): Ruling: проверки главного контроллера разделены: PG15.12 — я сам скриптом pg15check.sh <ветка> после снятия замка и пуша ветки; байт-в-байт на стенде, ручная проверка фазы 2, playwright --list/типы perf/e2e и замеры — главный контроллер, одним списком в финальном отчёте, не жду. Цена ошибки: нет.
  • R-4 (design #38): Ruling: в коммитах исполнителей трейлер Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> — как требуют план (Global Constraints) и WAVE2-RULES (явное указание человека приоритетнее общей подсказки). Цена ошибки: amend/rebase трейлера.
  • R-9 (design #43): Ruling: I-2 ревью Task 12 — единый источник высоты окна: измеряется реальная высота первой строки с данными (по умолчанию 37), используется в estimateSize/measureElement/rowHeight хука/padding; ROW_VIRTUALIZER_OPTIONS не правится. Причина: константа 37 при реальных ≈44 px даёт наложение строк (нарушение «визуально прежнее»). Цена: лишний рендер при первом измерении; ручная проверка контроллера на стенде.
  • R-10 (design #44): Ruling: T12.1 (окно запрашивается после первой страницы), T12.2 (запрос offset>=total при сужающем refine глубоко внизу — неизбежен, новый total заранее неизвестен), T12.4 (после ошибки смена поиска запускает refine вместо ожидания «Повторить»), T12.5 (заглушки на границе страниц после refine/«Обновить») — принимаются как осознанные отступления, deferred; проверка на стенде главным контроллером. Цена: лишний запрос/кратковременные заглушки.
  • R-11 (design #45): Ruling: сценарии стенда не засчитывают скелетоны как строки данных; O-7 (видимые пользователю скелетоны до конца загрузки окна) — решение за главным контроллером. Цена: нечестное «до/после» либо переделка loading на фронте.
  • R-5 (design #39): Ruling: I-1 финального ревью фазы 1 (стоимость серверного поиска) — deferred, не правим предикат без замера; цена: правка в 0053 до слияния, если замер даст недобор.
  • R-6 (design #40): Ruling: facets считает svod2_lines дважды (отклонение от design §4 из-за N² в коррелированном подзапросе) — цена: время facets с поиском.
  • R-7 (design #41): Ruling: значения поиска/фильтров не пишу��ся в лог db_time (M-2) — цена: нет.
  • R-8 (design #42): Ruling: M-4 (NUL в search → вероятно 5xx) deferred — цена: 5xx на искусственном запросе.

Находки (маркер, суть, статус)

Маркер Суть Статус
O-1 Перекрытие полей поиска: contract_summary перекрыто contract_detail при поиске на экране «до» (design «Найденные странности») deferred
O-2 Поиск по техническим id (design «Найденные странности») deferred
T1.1 NBSP/U+FEFF в SYNTHETIC_ROWS записаны сырыми символами (хрупко при форматировании) deferred
T1.2 SCREEN_CASES не ищет значения с NBSP/U+FEFF внутри deferred
T2.1 _JS_WHITESPACE сырыми невидимыми символами в svod2_oracle.py deferred
T2.2 расхождение Python lower() и JS toLowerCase не задокументировано в модуле deferred
T2.3 мёртвые ветки в js_number_text; OverflowError для огромных int deferred
T2.4 ключи фасетов без js_string (колонки строковые) deferred
T2.5 test_js_number_text_matches_node_vectors не параметризован deferred
T3.1 Эталон и порядок org_name зависят от collation БД (perf-pg): на PG15 контура golden-тест проверен rejected: прогон PG15.12 — эталон совпал, те же 3 падения
T3.2 svod2_seed.sql режется по ; — точка с запятой в тексте засева сломает скрипт deferred
T3.3 cd_id = expense_item_id (общий у нескольких строк), имя ключа вводит в заблуждение deferred
T3.4 добавлен фильтр search+type-hit (search+type из брифа даёт пусто) rejected: сохранены оба, это усиление
T3.5 / O-3 трейлер коммита b134cfd Sonnet 5.5 вместо Opus 5.5 (T1,T2 — Opus) deferred (документальное)
T4.1 тест trim не проверяет U+180E и U+0085 (не должны обрезаться) deferred
T4.2 casefold-тесты не покрывают весь U+0400–040F и Ø/İ deferred
T4.3 test_compact проверяет проекцию только на одной RF-строке deferred
T5.I-1 Important: коррелированный IN в v_svod2_facets (N^2) fixed e81df1d
T5.1 ELSE kv.value::text для object/array расходится с String(), но недостижимо (поля скалярные) deferred
T5.2 откат засева зависел от запроса к API fixed e81df1d
T5.3 "" и " " в тесте поиска передавались как NULL fixed e81df1d, f5bccc5
T5.4 фильтр из пробелов в SQL снимается NULLIF, в MRT не снимается (по design §3) rejected: решено в design §3
T5.5 EXISTS поиска заново соединяет таблицы вместо presence_*; header jsonb_build_object продублирован в трёх местах deferred (perf-фаза)
T5.6 test_facets_match_screen_oracle не передаёт " " (search or None) deferred
O-4 Исполнитель Task 5 в fix-раунде завёл собственного ревьюера (нарушение контракта «без сабагентов»); результат ревью не использован deferred (процесс)
T6.1 ORDER BY kind,value в facets-запросе по collation БД (клиент сортирует сам) deferred
T6.2 total по индексу result[0][4] вместо имени fixed 782a043 (= M-5)
T6.3 past-end второй вызов равен по цене первому deferred
T6.4 нет тестов на offset=None и пустой второй вызов deferred
T7.1 лог db_time пишет значения поиска/фильтров fixed 782a043 (= M-2)
T7.2 test_summary2_mode покрывает 4 комбинации из 32 deferred
T7.3 _parse_org_ids вызывается внутри веток, а не до ветвления deferred
I-1 Серверный поиск: ~1,46 с на 10 тыс. строк (translate-casefold, plpgsql числа); предложено регулярное выражение deferred (R-5, design #39; замер контроллера; закрывается вместе с T5.5)
M-1 нет теста перекрытия contract_summary пустой секцией (ооо лютик, дс-2) fixed 782a043
M-2 / T7.1 текст поиска/фильтров в логе db_time fixed 782a043
M-3 двойной svod2_lines в facets не в design.md fixed (design #40, docs-коммит)
M-4 NUL в search/фильтре → вероятно 5xx deferred (R-8, design #42)
M-5 / T6.2 total по индексу fixed 782a043
T10.1 EMPTY_FACET_MAP: Object.freeze(Map) не делает Map неизменяемым deferred (по брифу; не мутировать)
T10.2 getVisiblePage при rowHeight<=0 неявно безопасен deferred
T10.3 touchPage может оставить size>5, если все страницы в keepPages deferred (на практике ≤4)
T10.4 getSvod2RowId опирается на форму row.data.header deferred
T11.I-1 Important: begin(load) очищал pages/total/facets (расхождение с экраном «до») fixed 325c1c5
T11.I-2 Инвариант: хук обязан слать settled для каждого поколения (loading не залипает) fixed 325c1c5 (тест + комментарий; реализация в Task 12)
T11.1 fail(load/refine) не сбрасывает dataGeneration/loadedQueryKey (как «до») deferred
T11.2 отчёт исполнителя неверно цитирует бриф про «очищает» rejected: учтено в T11.I-1
O-5 Re-review Task 11 fix выполнен контроллером по диффу (1 строка), не отдельным ревьюером deferred (процесс)
T12.C-1 Critical: смена поиска/фильтра во время первой загрузки теряется fixed d74f528
T12.I-1 «Найдено N из M» рядом с ошибкой fixed d74f528
T12.I-2 ROW_HEIGHT=37 vs реальные ≈44 px, наложение строк fixed d74f528 (R-9; проверить измерение на стенде)
T12.1 окно запрашивается после ответа первой страницы deferred (R-10)
T12.2 запрос offset>=total при сужающем refine глубоко внизу deferred (R-10)
T12.3 варианты/«Найдено» на время загрузки показывают прежний запрос deferred
T12.4 после ошибки смена поиска запускает refine deferred (R-10)
T12.5 заглушки на границе страниц после refine/«Обновить» deferred (R-10)
T12.6 rowVirtualizerOptions пересоздаются каждый рендер; facetsMapRef пишется в рендере; load(mode) игнорирует mode; повтор при ошибке прокрутки на каждом кадре deferred
T12.7 нет тестов логики хука fixed частично d74f528 (чистая shouldRefine)
T12.8 измерение высоты ловило скелетон/«Нет данных» fixed fbc3601 (= I-2)
T12.9 высота определяется по строке 0; неоднородная высота fixed fbc3601 (максимум по отрисованным строкам, M-7); проверка на стенде
O-6 biome check Svod2Page.jsx: ошибка формата в строках scaleNumericColumns/, не наших (заявлено исполнителями; не подтверждено полностью) deferred
I-2 Important: высота строки (R-9) мерялась по скелетону при «Обновить» → пустой экран, потеря позиции fixed fbc3601
I-3 Important: возврат к ранее загруженным условиям (A→B→A) показывал строки по устаревшим поиску/фильтрам fixed fbc3601
M-6 заглушки ниже соседних строк (зазоры) fixed fbc3601
M-7 защита от зацикливания измерения высоты fixed fbc3601 (остаточно: значение не монотонно, не сбрасывается при смене условий)
M-8 syncWindow принимал страницы прежнего поколения fixed fbc3601
M-9 лишний refine на пробелы по краям поиска (refineKey из сырого search) deferred
T14.1 compare_stitched: ids.count O(n^2) deferred
T14.2 compare_stitched: строки без line_id считаются повторами deferred
T14.3 main перезапрашивает полный список для compact deferred
T14.4 страница за концом: total не проверяется deferred
T14.5 CLI --check: нет проверки обязательных аргументов deferred
T14.6 порядок импортов в тесте (biome organizeImports не запускался) deferred
T14.7 год/пользователь из sheets[0] без фильтра owner deferred (проверит контроллер на стенде)
T15.I-1 Important: filter_result_ms на «до» включал таймаут ожидания поиска 5 с fixed 5dc6662
T15.1 filter_result_ms на «после» может занижаться (stable до ответа) — учтено в filterResultMs (max) fixed 5dc6662
T15.2 pages_requested/json_bytes в записи фильтра включают исходную загрузку fixed 9272ea2
T15.3 signature() считает заглушки; filter_no_effect определяется по «Найдено» deferred
T15.4 rAF-сэмплер вносит одинаковую нагрузку в «до» и «после» rejected: одинаково для обеих сборок
T15.5 pages_requested на «до» = 0 — не сравнивать как число страниц fixed 9272ea2 (README, M-13)
T15.6 query разбирается вручную вместо URLSearchParams deferred
T16.C1 Critical: точка final в svod2-memory повторяла loadAll и снимала выбор «Все ССП/РФ» fixed 4e5ba53
T16.1 фиксированные 500 мс вместо ожидания стабилизации fixed 4e5ba53 (settle)
T16.2 waitRows неявно зависит от data=[] у незагруженной вкладки deferred
T16.3 rf_scrolled: MAX_STEPS может не дойти до конца на большом масштабе deferred (PERF_MAX_STEPS)
T16.4 нет attachNetlog в svod2-memory deferred
T17.1 ключи svod2_key «страница» не различают view=full/compact/search fixed 9272ea2
T17.2 docstring api_split не обновлён deferred
T17.3 /facets: колонка «строк ответа» считает ключи dict fixed 9272ea2
T17.4 трейлер коммита 558ad81 Haiku 5.5 вместо Opus 5.5 (см. O-3) deferred (документальное)
T17.5 имя ветки в конце perf/README.md не сверено rejected: проверено ревьюером, совпадает
I-4 Important: first_rows_ms/waitRows на «после» засчитывали строки-скелетоны (loading=true до settled) fixed 9272ea2
I-5 Important: README не описывал порядок до/после (checkout perf/ в worktree «до», stand.sh из своих worktree) fixed 9272ea2, 0f7c6c5
I-6 Important (ре-ревью): TimeoutError от waitForFunction ломал запись incomplete fixed 0f7c6c5
M-10 filter_result_ms на «после» не учитывает facets/остальные страницы deferred
M-11 first_rows_ms квантовался опросом 500 мс fixed 0f7c6c5 (raf-наблюдатель)
M-12 EXPLAIN v_svod2_go/rf (SET search_path) даёт один Function Scan без узлов deferred
M-13 README без списка новых метрик fixed 9272ea2
M-14 устаревший заголовок svod2-memory.spec.ts fixed 9272ea2
M-15 сверки идентифицируют строки по line_id (в GO не уникален); stitch_pages без защиты от бесконечного цикла deferred
O-7 UX фазы 2: в режиме load loading=true до settled → MRT рисует скелетоны в ячейках, пока не придут facets и страницы окна (на «до» строки сразу показывали данные). Допустимое «время ожидания», но отличие видно; проверить на стенде deferred (контроллеру)

🤖 Generated with Claude Code

Refs #236 (часть 2/3; `Closes #236` будет в PR 3). Jira AURORA-1843 (эпик AURORA-1834). Стек: PR 1 (#241) → этот PR → PR 3. Spec: `openspec/changes/aurora-1843-svod2-server-paging/`. ## Что сделано (фаза 2 — фронт) - Свод 2 получает строки страницами с сервера (`view=compact`, страница 300 строк), поиск и три колоночных фильтра считает сервер, варианты фильтров приходят из `/facets`. В памяти вкладки — окно из трёх страниц (LRU 5) + заглушки; та же таблица MRT, отступы виртуализатора (`paddingStart/paddingEnd`) задают высоту прокрутки по `total`. - Чистая логика: `svod2Paging.js` (окно, срез, LRU, параметры, редьюсер состояния вкладки с поколениями, `shouldRefine`, `shouldShowFound`); хук `useSvod2Pages.js` ходит в API (один AbortController на поколение, `settled` для каждого поколения, нет утечек rAF/контроллеров). `SummaryApi.getSvod2Page/getSvod2Facets`. - Единый источник высоты строки окна — измерение первой строки с данными (по умолчанию 37 px), `ROW_VIRTUALIZER_OPTIONS`, `SvodContext`, `SvodFilters` и свод 1 не менялись. - Для пользователя поведение прежнее: подписи, условия `disabled`, подсказка «Нажмите «Загрузить»…», `Alert` с «Повторить», сброс колоночных фильтров, «Найдено N из M». ## Тесты - `node --test` (RealtimeTable utils, SvodPage, Svod2Page): 52/52; `npm run build` ок; `git grep getSummary|invalidateSummary|matchesSearch` в Svod2Page пуст. - Проверка предпосылки (Task 9): MRT 3.2.1 разворачивает пользовательские `rowVirtualizerOptions` последними, высота `tbody` = `getTotalSize()`. - biome: одна ошибка формата в `Svod2Page.jsx` (`scaleNumericColumns`, `<Tab>`) — строки не менялись относительно 399234a. ## Чек-лист ручной проверки (главный контроллер, на стенде против сборки «до» на тех же данных) - [ ] GO и RF, admin и dfip, «Все» и выбор организаций. - [ ] Поиск, три фильтра и их сочетания, варианты с числами, «Найдено», сброс фильтров. - [ ] Вкладки и положение прокрутки; ползунок в середину и в конец. - [ ] Быстрый ввод, затем фильтр; ввод поиска во время первой загрузки; возврат к ранее загруженным условиям (A→B→A). - [ ] Высота строки (R-9): нет наложения строк и пустоты, «Обновить» на глубине сохраняет позицию; зазоры между заглушками. - [ ] Варианты select-ов обновляются после смены поиска/фильтров. - [ ] Единицы, «Перейти к задачам», горизонтальная прокрутка, ошибка догрузки. - [ ] Скриншот��. Замер до/после — добавит контроллер после прогона на стенде. ## Rulings I made - R-1 (design #35): Ruling: решения плана #23–#34 одобрены главным контроллером как есть — цена ошибки: нет. - R-2 (design #36): Ruling: хелпер `legacy_full` живёт в `api/tests/integration/conftest.py` (не в test_svod2_paging_api.py); Task 3 создаёт его там, Task 4 импортирует оттуда — иначе test_svod2_sql.py импортировал бы из тестового модуля. Цена ошибки: перенос функции. - R-3 (design #37): Ruling: проверки главного контроллера разделены: PG15.12 — я сам скриптом pg15check.sh <ветка> после снятия замка и пуша ветки; байт-в-байт на стенде, ручная проверка фазы 2, `playwright --list`/типы perf/e2e и замеры — главный контроллер, одним списком в финальном отчёте, не жду. Цена ошибки: нет. - R-4 (design #38): Ruling: в коммитах исполнителей трейлер `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>` — как требуют план (Global Constraints) и WAVE2-RULES (явное указание человека приоритетнее общей подсказки). Цена ошибки: amend/rebase трейлера. - R-9 (design #43): Ruling: I-2 ревью Task 12 — единый источник высоты окна: измеряется реальная высота первой строки с данными (по умолчанию 37), используется в estimateSize/measureElement/rowHeight хука/padding; ROW_VIRTUALIZER_OPTIONS не правится. Причина: константа 37 при реальных ≈44 px даёт наложение строк (нарушение «визуально прежнее»). Цена: лишний рендер при первом измерении; ручная проверка контроллера на стенде. - R-10 (design #44): Ruling: T12.1 (окно запрашивается после первой страницы), T12.2 (запрос offset>=total при сужающем refine глубоко внизу — неизбежен, новый total заранее неизвестен), T12.4 (после ошибки смена поиска запускает refine вместо ожидания «Повторить»), T12.5 (заглушки на границе страниц после refine/«Обновить») — принимаются как осознанные отступления, deferred; проверка на стенде главным контроллером. Цена: лишний запрос/кратковременные заглушки. - R-11 (design #45): Ruling: сценарии стенда не засчитывают скелетоны как строки данных; O-7 (видимые пользователю скелетоны до конца загрузки окна) — решение за главным контроллером. Цена: нечестное «до/после» либо переделка loading на фронте. - R-5 (design #39): Ruling: I-1 финального ревью фазы 1 (стоимость серверного поиска) — deferred, не правим предикат без замера; цена: правка в 0053 до слияния, если замер даст недобор. - R-6 (design #40): Ruling: facets считает svod2_lines дважды (отклонение от design §4 из-за N² в коррелированном подзапросе) — цена: время facets с поиском. - R-7 (design #41): Ruling: значения поиска/фильтров не пишу��ся в лог db_time (M-2) — цена: нет. - R-8 (design #42): Ruling: M-4 (NUL в search → вероятно 5xx) deferred — цена: 5xx на искусственном запросе. ## Находки (маркер, суть, статус) | Маркер | Суть | Статус | |---|---|---| | O-1 | Перекрытие полей поиска: contract_summary перекрыто contract_detail при поиске на экране «до» (design «Найденные странности») | deferred | | O-2 | Поиск по техническим id (design «Найденные странности») | deferred | | T1.1 | NBSP/U+FEFF в SYNTHETIC_ROWS записаны сырыми символами (хрупко при форматировании) | deferred | | T1.2 | SCREEN_CASES не ищет значения с NBSP/U+FEFF внутри | deferred | | T2.1 | _JS_WHITESPACE сырыми невидимыми символами в svod2_oracle.py | deferred | | T2.2 | расхождение Python lower() и JS toLowerCase не задокументировано в модуле | deferred | | T2.3 | мёртвые ветки в js_number_text; OverflowError для огромных int | deferred | | T2.4 | ключи фасетов без js_string (колонки строковые) | deferred | | T2.5 | test_js_number_text_matches_node_vectors не параметризован | deferred | | T3.1 | Эталон и порядок org_name зависят от collation БД (perf-pg): на PG15 контура golden-тест проверен | rejected: прогон PG15.12 — эталон совпал, те же 3 падения | | T3.2 | svod2_seed.sql режется по `;` — точка с запятой в тексте засева сломает скрипт | deferred | | T3.3 | cd_id = expense_item_id (общий у нескольких строк), имя ключа вводит в заблуждение | deferred | | T3.4 | добавлен фильтр search+type-hit (search+type из брифа даёт пусто) | rejected: сохранены оба, это усиление | | T3.5 / O-3 | трейлер коммита b134cfd Sonnet 5.5 вместо Opus 5.5 (T1,T2 — Opus) | deferred (документальное) | | T4.1 | тест trim не проверяет U+180E и U+0085 (не должны обрезаться) | deferred | | T4.2 | casefold-тесты не покрывают весь U+0400–040F и Ø/İ | deferred | | T4.3 | test_compact проверяет проекцию только на одной RF-строке | deferred | | T5.I-1 | Important: коррелированный IN в v_svod2_facets (N^2) | fixed e81df1d | | T5.1 | ELSE kv.value::text для object/array расходится с String(), но недостижимо (поля скалярные) | deferred | | T5.2 | откат засева зависел от запроса к API | fixed e81df1d | | T5.3 | "" и " " в тесте поиска передавались как NULL | fixed e81df1d, f5bccc5 | | T5.4 | фильтр из пробелов в SQL снимается NULLIF, в MRT не снимается (по design §3) | rejected: решено в design §3 | | T5.5 | EXISTS поиска заново соединяет таблицы вместо presence_*; header jsonb_build_object продублирован в трёх местах | deferred (perf-фаза) | | T5.6 | test_facets_match_screen_oracle не передаёт " " (search or None) | deferred | | O-4 | Исполнитель Task 5 в fix-раунде завёл собственного ревьюера (нарушение контракта «без сабагентов»); результат ревью не использован | deferred (процесс) | | T6.1 | ORDER BY kind,value в facets-запросе по collation БД (клиент сортирует сам) | deferred | | T6.2 | total по индексу result[0][4] вместо имени | fixed 782a043 (= M-5) | | T6.3 | past-end второй вызов равен по цене первому | deferred | | T6.4 | нет тестов на offset=None и пустой второй вызов | deferred | | T7.1 | лог db_time пишет значения поиска/фильтров | fixed 782a043 (= M-2) | | T7.2 | test_summary2_mode покрывает 4 комбинации из 32 | deferred | | T7.3 | _parse_org_ids вызывается внутри веток, а не до ветвления | deferred | | I-1 | Серверный поиск: ~1,46 с на 10 тыс. строк (translate-casefold, plpgsql числа); предложено регулярное выражение | deferred (R-5, design #39; замер контроллера; закрывается вместе с T5.5) | | M-1 | нет теста перекрытия contract_summary пустой секцией (ооо лютик, дс-2) | fixed 782a043 | | M-2 / T7.1 | текст поиска/фильтров в логе db_time | fixed 782a043 | | M-3 | двойной svod2_lines в facets не в design.md | fixed (design #40, docs-коммит) | | M-4 | NUL в search/фильтре → вероятно 5xx | deferred (R-8, design #42) | | M-5 / T6.2 | total по индексу | fixed 782a043 | | T10.1 | EMPTY_FACET_MAP: Object.freeze(Map) не делает Map неизменяемым | deferred (по брифу; не мутировать) | | T10.2 | getVisiblePage при rowHeight<=0 неявно безопасен | deferred | | T10.3 | touchPage может оставить size>5, если все страницы в keepPages | deferred (на практике ≤4) | | T10.4 | getSvod2RowId опирается на форму row.data.header | deferred | | T11.I-1 | Important: begin(load) очищал pages/total/facets (расхождение с экраном «до») | fixed 325c1c5 | | T11.I-2 | Инвариант: хук обязан слать settled для каждого поколения (loading не залипает) | fixed 325c1c5 (тест + комментарий; реализация в Task 12) | | T11.1 | fail(load/refine) не сбрасывает dataGeneration/loadedQueryKey (как «до») | deferred | | T11.2 | отчёт исполнителя неверно цитирует бриф про «очищает» | rejected: учтено в T11.I-1 | | O-5 | Re-review Task 11 fix выполнен контроллером по диффу (1 строка), не отдельным ревьюером | deferred (процесс) | | T12.C-1 | Critical: смена поиска/фильтра во время первой загрузки теряется | fixed d74f528 | | T12.I-1 | «Найдено N из M» рядом с ошибкой | fixed d74f528 | | T12.I-2 | ROW_HEIGHT=37 vs реальные ≈44 px, наложение строк | fixed d74f528 (R-9; проверить измерение на стенде) | | T12.1 | окно запрашивается после ответа первой страницы | deferred (R-10) | | T12.2 | запрос offset>=total при сужающем refine глубоко внизу | deferred (R-10) | | T12.3 | варианты/«Найдено» на время загрузки показывают прежний запрос | deferred | | T12.4 | после ошибки смена поиска запускает refine | deferred (R-10) | | T12.5 | заглушки на границе страниц после refine/«Обновить» | deferred (R-10) | | T12.6 | rowVirtualizerOptions пересоздаются каждый рендер; facetsMapRef пишется в рендере; load(mode) игнорирует mode; повтор при ошибке прокрутки на каждом кадре | deferred | | T12.7 | нет тестов логики хука | fixed частично d74f528 (чистая shouldRefine) | | T12.8 | измерение высоты ловило скелетон/«Нет данных» | fixed fbc3601 (= I-2) | | T12.9 | высота определяется по строке 0; неоднородная высота | fixed fbc3601 (максимум по отрисованным строкам, M-7); проверка на стенде | | O-6 | biome check Svod2Page.jsx: ошибка формата в строках scaleNumericColumns/<Tab>, не наших (заявлено исполнителями; не подтверждено полностью) | deferred | | I-2 | Important: высота строки (R-9) мерялась по скелетону при «Обновить» → пустой экран, потеря позиции | fixed fbc3601 | | I-3 | Important: возврат к ранее загруженным условиям (A→B→A) показывал строки по устаревшим поиску/фильтрам | fixed fbc3601 | | M-6 | заглушки ниже соседних строк (зазоры) | fixed fbc3601 | | M-7 | защита от зацикливания измерения высоты | fixed fbc3601 (остаточно: значение не монотонно, не сбрасывается при смене условий) | | M-8 | syncWindow принимал страницы прежнего поколения | fixed fbc3601 | | M-9 | лишний refine на пробелы по краям поиска (refineKey из сырого search) | deferred | | T14.1 | compare_stitched: ids.count O(n^2) | deferred | | T14.2 | compare_stitched: строки без line_id считаются повторами | deferred | | T14.3 | main перезапрашивает полный список для compact | deferred | | T14.4 | страница за концом: total не проверяется | deferred | | T14.5 | CLI --check: нет проверки обязательных аргументов | deferred | | T14.6 | порядок импортов в тесте (biome organizeImports не запускался) | deferred | | T14.7 | год/пользователь из sheets[0] без фильтра owner | deferred (проверит контроллер на стенде) | | T15.I-1 | Important: filter_result_ms на «до» включал таймаут ожидания поиска 5 с | fixed 5dc6662 | | T15.1 | filter_result_ms на «после» может занижаться (stable до ответа) — учтено в filterResultMs (max) | fixed 5dc6662 | | T15.2 | pages_requested/json_bytes в записи фильтра включают исходную загрузку | fixed 9272ea2 | | T15.3 | signature() считает заглушки; filter_no_effect определяется по «Найдено» | deferred | | T15.4 | rAF-сэмплер вносит одинаковую нагрузку в «до» и «после» | rejected: одинаково для обеих сборок | | T15.5 | pages_requested на «до» = 0 — не сравнивать как число страниц | fixed 9272ea2 (README, M-13) | | T15.6 | query разбирается вручную вместо URLSearchParams | deferred | | T16.C1 | Critical: точка final в svod2-memory повторяла loadAll и снимала выбор «Все ССП/РФ» | fixed 4e5ba53 | | T16.1 | фиксированные 500 мс вместо ожидания стабилизации | fixed 4e5ba53 (settle) | | T16.2 | waitRows неявно зависит от data=[] у незагруженной вкладки | deferred | | T16.3 | rf_scrolled: MAX_STEPS может не дойти до конца на большом масштабе | deferred (PERF_MAX_STEPS) | | T16.4 | нет attachNetlog в svod2-memory | deferred | | T17.1 | ключи svod2_key «страница» не различают view=full/compact/search | fixed 9272ea2 | | T17.2 | docstring api_split не обновлён | deferred | | T17.3 | /facets: колонка «строк ответа» считает ключи dict | fixed 9272ea2 | | T17.4 | трейлер коммита 558ad81 Haiku 5.5 вместо Opus 5.5 (см. O-3) | deferred (документальное) | | T17.5 | имя ветки в конце perf/README.md не сверено | rejected: проверено ревьюером, совпадает | | I-4 | Important: first_rows_ms/waitRows на «после» засчитывали строки-скелетоны (loading=true до settled) | fixed 9272ea2 | | I-5 | Important: README не описывал порядок до/после (checkout perf/ в worktree «до», stand.sh из своих worktree) | fixed 9272ea2, 0f7c6c5 | | I-6 | Important (ре-ревью): TimeoutError от waitForFunction ломал запись incomplete | fixed 0f7c6c5 | | M-10 | filter_result_ms на «после» не учитывает facets/остальные страницы | deferred | | M-11 | first_rows_ms квантовался опросом 500 мс | fixed 0f7c6c5 (raf-наблюдатель) | | M-12 | EXPLAIN v_svod2_go/rf (SET search_path) даёт один Function Scan без узлов | deferred | | M-13 | README без списка новых метрик | fixed 9272ea2 | | M-14 | устаревший заголовок svod2-memory.spec.ts | fixed 9272ea2 | | M-15 | сверки идентифицируют строки по line_id (в GO не уникален); stitch_pages без защиты от бесконечного цикла | deferred | | O-7 | UX фазы 2: в режиме load loading=true до settled → MRT рисует скелетоны в ячейках, пока не придут facets и страницы окна (на «до» строки сразу показывали данные). Допустимое «время ожидания», но отличие видно; проверить на стенде | deferred (контроллеру) | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 9 commits 2026-10-28 23:18:50 +03:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

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

Сборки: «до» — вершина 1842 399234a (0049), «после» — вершина PR 3 3e9979d (включает этот PR fbc3601, 0053). Масштаб 1, D0, CPU 1x, Chromium 1600×900. Сценарии: scroll.spec.ts (PERF_TARGETS='svod/svod2_*' dfip, 2 повтора, полная прокрутка 2074 / 3176 шагов), svod2-memory.spec.ts (3 повтора), ручная проверка фазы 2 tools/svod2_phase2.mjs (dfip GO, dfip RF, admin GO; два прогона: before/after и before2/after2 с исправленным кликом по select-фильтрам).

Скорость и память (цели design, не условие слияния)

метрика цель до после вердикт
шаг прокрутки, GO / RF, мс ≤ 1.10× 40.6 / 41.7 36.5 / 35.9 (0.90× / 0.86×); heavy_frame_n 1 / 8.5 → 0.5 / 0.5 выполнено
first_rows_ms, GO / RF ≤ 0.5× 1197.5 / 1750 425.5 / 492.5 (0.36× / 0.28×) выполнено
память final, МБ ≤ 0.30× 586.8 42.6 (0.073×); узлов DOM 241 225 → 18 121 выполнено
память GO / RF / RF после прокрутки, МБ 38.0 / 61.1 / 570.7 21.2 / 22.2 / 42.4
placeholder_frame_share ≤ 5 % 0 0 формально выполнено (метрика не видит пустых строк после прыжка, см. ниже)
загрузка до 20 строк, GO / RF / admin GO 1180 / 1823 / 1289 мс 383 / 497 / 362 мс
«Обновить» на глубине, мс 2970 / 3564 / 3154 2046 / 2107 / 2036
json_bytes_total за прокрутку, GO / RF 30,9 / 47,2 МБ 7,8 / 12,5 МБ (страниц 33 / 53)
фильтр RF (filter_result_ms) 432 857 медленнее
фильтр GO не снято: «filter did not stabilize in 120s» на обеих сборках
Память на масштабе 2 (цель ≤ 1,25× «после» s1) — не снята.

Фаза 2: ручная проверка против «до»

  • Высота строк: hmin = hmax = 37.44 px и в «до», и в «после» (settled); наложений (overlaps) и зазоров (gaps) 0 во всех кадрах всех сессий. Окно: 27 строк вверху, 35 в середине, 25 в конце.
  • «Обновить» на глубине: положение прокрутки «до» уезжает на 6 px (103 609 → 103 615; 158 739 → 158 745), «после» остаётся (103 609 → 103 609; 158 739 → 158 739).
  • Поиск «аренд»: GO «Найдено 7 из 5 617 строк», RF «Найдено 0 из 8 597 строк»; кадры *-07-search побайтно идентичны до и после (все три сессии).
  • Варианты select-ов после поиска (Вид сметы / Направление / ССП): совпадают до и после — GO и admin: «АХР (7)», «Развитие (7)», ССП РФ-01 (3), РФ-02 (2), РФ-05 (1), РФ-06 (1); RF (0 найдено): без вариантов и «до», и «после».
  • Горизонтальная прокрутка / верх: после «Обновить» кадры x0/x1/x2 «до» и «после» отличаются сдвигом ~6 px по вертикали (провер��но глазами на x1; размеры PNG x0/x2 отличаются так же, на 0,15–1 %) из-за дрейфа «до»; top и settled — до 1 байта PNG. Оговорка: на обеих сборках (вершина 1842 уже в «до») у свода 2 на скриншотах видны лишь первые 4 колонки без числовых (см. #227); различить «до»/«после» по числовым колонкам по этим кадрам нельзя.
  • Заглушки на границах страниц: в просмотренном кадре settled (GO, середина) пустых строк нет, детектор skeletons = 0 во всех кадрах; пустые строки сразу после прыжка — O-7 ниже.

O-7: что видно через 50 мс после прыжка (*-02-mid-50ms, *-02-end-50ms) против *-03-*-settled

до после
данные через 50 мс строки с текстом и чипами 17 пустых строк, без текста, без серых полос; шапка, фильтры на месте
высота строк в кадре 37.44 37.00 (settled 37.44; скролл-высота 207 909 px не меняется)
время до текста (mid/end_jump_to_text_ms, прогон 2) 31–54 45–54 (шаг измерения ~50 мс)
скелетоны (серые полосы) при «Обновить» через 100 мс есть есть (шаг строк 34 px «до», 37 px «после»)
Длительность «пусто» на масштабе 1 не больше разрешения замера (~50 мс), масштаб 2 не снимался (там серверная страница 0,5–0,8 с против 0,15–0,2 с). Детектор скелетонов сценария пустых строк не считает (R-11). Решение по O-7 — за человеком.
Видео и кадры: /private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/1843/phase2/ (before*/after*/after2*-*.png, video-*).
## Ночной замер — часть 2 (итог) Сборки: «до» — вершина 1842 `399234a` (0049), «после» — вершина PR 3 `3e9979d` (включает этот PR fbc3601, 0053). Масштаб 1, D0, CPU 1x, Chromium 1600×900. Сценарии: `scroll.spec.ts` (PERF_TARGETS='svod/svod2_*' dfip, 2 повтора, полная прокрутка 2074 / 3176 шагов), `svod2-memory.spec.ts` (3 повтора), ручная проверка фазы 2 `tools/svod2_phase2.mjs` (dfip GO, dfip RF, admin GO; два прогона: `before`/`after` и `before2`/`after2` с исправленным кликом по select-фильтрам). ### Скорость и память (цели design, не условие слияния) | метрика | цель | до | после | вердикт | |---|---|---|---|---| | шаг прокрутки, GO / RF, мс | ≤ 1.10× | 40.6 / 41.7 | **36.5 / 35.9** (0.90× / 0.86×); heavy_frame_n 1 / 8.5 → 0.5 / 0.5 | выполнено | | first_rows_ms, GO / RF | ≤ 0.5× | 1197.5 / 1750 | **425.5 / 492.5** (0.36× / 0.28×) | выполнено | | память `final`, МБ | ≤ 0.30× | 586.8 | **42.6** (0.073×); узлов DOM 241 225 → 18 121 | выполнено | | память GO / RF / RF после прокрутки, МБ | | 38.0 / 61.1 / 570.7 | 21.2 / 22.2 / 42.4 | | | placeholder_frame_share | ≤ 5 % | 0 | 0 | формально выполнено (метрика не видит пустых строк после прыжка, см. ниже) | | загрузка до 20 строк, GO / RF / admin GO | | 1180 / 1823 / 1289 мс | 383 / 497 / 362 мс | | | «Обновить» на глубине, мс | | 2970 / 3564 / 3154 | 2046 / 2107 / 2036 | | | json_bytes_total за прокрутку, GO / RF | | 30,9 / 47,2 МБ | 7,8 / 12,5 МБ (страниц 33 / 53) | | | фильтр RF (`filter_result_ms`) | | 432 | **857** | медленнее | | фильтр GO | | не снято: «filter did not stabilize in 120s» на обеих сборках | | | Память на масштабе 2 (цель ≤ 1,25× «после» s1) — не снята. ### Фаза 2: ручная проверка против «до» - **Высота строк:** hmin = hmax = 37.44 px и в «до», и в «после» (settled); наложений (overlaps) и зазоров (gaps) 0 во всех кадрах всех сессий. Окно: 27 строк вверху, 35 в середине, 25 в конце. - **«Обновить» на глубине:** положение прокрутки «до» уезжает на 6 px (103 609 → 103 615; 158 739 → 158 745), «после» остаётся (103 609 → 103 609; 158 739 → 158 739). - **Поиск «аренд»:** GO «Найдено 7 из 5 617 строк», RF «Найдено 0 из 8 597 строк»; кадры `*-07-search` побайтно идентичны до и после (все три сессии). - **Варианты select-ов после поиска** (Вид сметы / Направление / ССП): совпадают до и после — GO и admin: «АХР (7)», «Развитие (7)», ССП РФ-01 (3), РФ-02 (2), РФ-05 (1), РФ-06 (1); RF (0 найдено): без вариантов и «до», и «после». - **Горизонтальная прокрутка / верх:** после «Обновить» кадры x0/x1/x2 «до» и «после» отличаются сдвигом ~6 px по вертикали (провер��но глазами на x1; размеры PNG x0/x2 отличаются так же, на 0,15–1 %) из-за дрейфа «до»; top и settled — до 1 байта PNG. Оговорка: на обеих сборках (вершина 1842 уже в «до») у свода 2 на скриншотах видны лишь первые 4 колонки без числовых (см. #227); различить «до»/«после» по числовым колонкам по этим кадрам нельзя. - **Заглушки на границах страниц:** в просмотренном кадре settled (GO, середина) пустых строк нет, детектор `skeletons` = 0 во всех кадрах; пустые строки сразу после прыжка — O-7 ниже. ### O-7: что видно через 50 мс после прыжка (`*-02-mid-50ms`, `*-02-end-50ms`) против `*-03-*-settled` | | до | после | |---|---|---| | данные через 50 мс | строки с текстом и чипами | **17 пустых строк, без текста, без серых полос**; шапка, фильтры на месте | | высота строк в кадре | 37.44 | **37.00** (settled 37.44; скролл-высота 207 909 px не меняется) | | время до текста (`mid/end_jump_to_text_ms`, прогон 2) | 31–54 | 45–54 (шаг измерения ~50 мс) | | скелетоны (серые полосы) при «Обновить» через 100 мс | есть | есть (шаг строк 34 px «до», 37 px «после») | Длительность «пусто» на масштабе 1 не больше разрешения замера (~50 мс), масштаб 2 не снимался (там серверная страница 0,5–0,8 с против 0,15–0,2 с). Детектор скелетонов сценария пустых строк не считает (R-11). **Решение по O-7 — за человеком.** Видео и кадры: `/private/tmp/claude-501/-Users-andrey-epifancev-projects-fomichev-claude/6fa9c75e-eb11-4e5d-98c1-05eb590ab14f/scratchpad/night/1843/phase2/` (`before*/after*/after2*-*.png`, `video-*`).
aep added 1 commit 2026-10-29 11:37:10 +03:00
Конфликты в Svod2Page.jsx (импорты, content) и svod2Styles.test.js разрешены
объединением: серверная пагинация 1843 плюс grid-no-grow, pinning и портал заголовка 1842.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep added 2 commits 2026-10-29 12:18:39 +03:00
Ячейки строк с data-svod2-placeholder рисуют MUI Skeleton wave высотой 20 с детерминированной шириной.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep added 1 commit 2026-10-29 12:22:10 +03:00
aep added 1 commit 2026-10-29 13:29:31 +03:00
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/236-aurora-1843-svod2-server-paging-web:feat/236-aurora-1843-svod2-server-paging-web
git checkout feat/236-aurora-1843-svod2-server-paging-web
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#243
No description provided.