docs(openspec): aurora-1839-cell-memo — пороги не блокируют слияние, замеры у контроллера

Решения 28.10: пороги производительности — цель, недобор в follow-up;
машина замеров Apple M4, пороги абсолютные; замеры выполняет контроллер
после ночного прогона; пустая полоса при перетаскивании по умолчанию
принимается как штатное поведение MRT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-28 20:41:09 +04:00
parent 8a99c3d3b5
commit 045aa4ba18
3 changed files with 39 additions and 4 deletions

View File

@ -95,6 +95,10 @@
- шаг прокрутки FORM_2/AHR ≤ 25 мс (стенд, масштаб 1, CPU 1×);
- сценарий «закрепить колонку, потянуть ширину закреплённой — время кадра». В `perf/e2e` его нет, что добавить — описано ниже.
7. **Связь с AURORA-1838 не блокирующая.** В Jira она заменена на «связанные».
8. **Пороги производительности не являются условием слияния.**
- PR вливается по зелёным тестам и ручному регрессу. Цифры замеров приводятся в PR.
- Недобор до порога (например, шаг > 25 мс) — это follow-up issue с разбором, что съедает остаток, а не блокировка PR.
- Корректность — жёсткое условие слияния: контракт, закреплённые колонки, подсветки и состояние ячеек (требования spec без замеров).
## Решения дизайна
@ -243,7 +247,7 @@ const pinnedLayoutKey = useMemo(
- ещё в очистке эффекта `ColumnResizer` при размонтировании во время перетаскивания.
Ручная проверка: начать тянуть, переключиться Alt+Tab, вернуться.
- **[Пока тело заморожено, диапазон виртуальных колонок не пересчитывается]** → если сузить колонку больше, чем на ширину трёх колонок overscan, справа до отпускания мыши будет пустая полоса. Это штатное поведение MRT при его собственном перетаскивании. Сценарий ручной проверки: сузить широкую колонку на 400 px. Если полоса мешает, запасной вариант — выставлять флаг только при перетаскивании закреплённой колонки. Тогда перетаскивание незакреплённой останется таким, как сейчас (дорогим), и это нужно будет записать в design.md.
- **[Пока тело заморожено, диапазон виртуальных колонок не пересчитывается]** → если сузить колонку больше, чем на ширину трёх колонок overscan, справа до отпускания мыши будет пустая полоса. Это штатное поведение MRT при его собственном перетаскивании. Сценарий ручной проверки: сузить широкую колонку на 400 px. По умолчанию полоса принимается как штатное поведение MRT (решение 11 в «Decisions made without discussion»). Запасной вариант — выставлять флаг только при перетаскивании закреплённой колонки. Его включают, только если ручная проверка покажет, что полоса мешает работе. Тогда перетаскивание незакреплённой колонки останется таким, как сейчас (дорогим), и это нужно будет записать в design.md.
- **[Входящая правка соавтора во время перетаскивания перерисует тело]** → `Memo_MRT_TableBody` сравнивает `data`. Ячейки тянутой колонки на время перетаскивания покажут рамку 2px — MRT рисует её у колонки с `isResizingColumn` (`:2169-2177`). Это косметика, только во время перетаскивания, и исчезает по отпусканию.
- **[`editingCell` в состоянии `RealtimeTable` после сброса указывает на прежний объект `cell`]** → MRT и наш код сравнивают по `id` (`:2443`, `RealtimeTable.jsx:562-580`), редактор продолжает работать. Ручная проверка: закрепить колонку или изменить поиск при открытом редакторе.
- **[Пересоздание колонок по фильтру на каждый символ]** → по D1 это только `[...columns]`, без `structuredClone`. Стоимость — пересборка `Column` и ячеек видимых строк, она и так происходит на каждый рендер владельца в MRT 3.2.1.
@ -341,7 +345,12 @@ const pinnedLayoutKey = useMemo(
**Атрибут ресайзера в базе.** На ветке «до» атрибута `data-col-resize-id` нет. Сценарий ищет ресайзер так: `[data-col-resize-id="<id>"]`, а если его нет — последний дочерний `div` с `cursor: col-resize` внутри ячейки заголовка той же колонки. Ячейка заголовка находится по горизонтальной координате `[data-col-select-id="<id>"]`. Запасной путь нужен только для замера базы.
**Машина замера.** Порог 25 мс задан по базе на Apple M4 (`perf/REPORT.md`, методика). Если стенд поднят на другой машине, в PR приводятся абсолютные цифры до и после, а также база `test` на этой машине. Вопрос о переводе порога в относительный (−72 % от базы `test`) вынесен человеку (см. отчёт автора дизайна).
**Машина замера.** Та же Apple M4, на которой снят отчёт профилирования (`perf/REPORT.md`, методика). Пороги абсолютные (25 мс), отдельный прогон базы `test` сверх обычного «до/после» не нужен.
**Кто и когда меряет.** Все замеры из таблицы выше, включая новый `pinned-resize.spec.ts`, выполняет контроллер на стенде после основных ночных замеров.
- В плане это точка ожидания.
- PR открывается с кодом, тестами и чек-листом ручного регресса, цифры дописываются в PR после замера.
- Колонка «Порог» в таблице — цель, а не условие слияния (решение 8 в разделе «Решения (согласовано 28.10)»). Недобор оформляется follow-up issue с разбором.
## Что оставлено исполнителю
@ -354,8 +363,9 @@ const pinnedLayoutKey = useMemo(
2. управляемые `columnFilters`/`columnSizingInfo` и флаг перетаскивания в `TableHead`/`ColumnResizer`;
3. `tableColumns` и `memoMode: 'cells'`;
4. ручной регресс;
5. сценарий `pinned-resize.spec.ts` (после ночного прогона);
6. замеры и цифры в PR.
5. сценарий `pinned-resize.spec.ts` — отдельным коммитом в `perf/`, после ночного прогона;
6. открыть PR с кодом, тестами и чек-листом ручного регресса;
7. точка ожидания: замеры выполняет контроллер, цифры дописываются в PR, недобор — в follow-up issue.
## Decisions made without discussion
@ -390,3 +400,14 @@ const pinnedLayoutKey = useMemo(
9. **2026-10-28 — capability называется `sheet-table`, новый модуль — `utils/cellMemoReset.js`, а не `columnLayoutUtils.js` из черновика.**
- Обоснование: ключ включает фильтры, это не только раскладка.
- Цена ошибки: только имена.
10. **2026-10-28 (контроллер) — машина замеров — та же Apple M4, на которой снят отчёт профилирования. Пороги абсолютные (25 мс), отдельный прогон базы сверх обычного «до/после» не нужен.**
- Обоснование: на той же машине база отчёта М1 сопоставима с замерами «после» напрямую.
- Цена ошибки: если замер всё же пойдёт на другой машине, абсолютные цифры будут несравнимы с отчётом. Тогда нужен прогон базы `test` на этой машине и пересчёт порога в относительный (−72 % от базы).
11. **2026-10-28 (контроллер) — пустая полоса справа при перетаскивании незакреплённой колонки решается по ручной проверке; по умолчанию полоса принимается как штатное поведение MRT, флаг выставляется для любой колонки (D3).**
- Обоснование: так ведёт себя MRT при собственном ресайзе. Альтернатива (флаг только для закреплённых) возвращает дорогое перетаскивание незакреплённых колонок.
- Цена ошибки: пользователь видит кратковременную пустую полосу до отпускания мыши. Откат — около 5 строк, см. «Риски».
12. **2026-10-28 (контроллер) — замеры, включая новый `pinned-resize.spec.ts`, выполняет контроллер на стенде после основных ночных замеров; в плане — точка ожидания.**
- PR открывается с кодом, тестами и чек-листом ручного регресса, цифры дописываются в PR.
- Сценарий `pinned-resize.spec.ts` идёт в этом же PR отдельным коммитом (D6).
- Обоснование: стенд занят ночным прогоном, а пороги не блокируют слияние (решение человека 8).
- Цена ошибки: если замер покажет регрессию, исправление пойдёт уже после открытия PR, возможно отдельным PR по follow-up.

View File

@ -24,6 +24,7 @@ AURORA-1836 убрала чтение layout в `measureElement`, AURORA-1837 п
- обработчики читают актуальное состояние через `ref` или `table.getState()`.
- Добавляются node-тесты ключа сброса и допущения о пересоздании ячеек в `@tanstack/table-core`.
- В стенд `perf/e2e` добавляется сценарий «закрепить колонку и потянуть её ширину — время кадра».
- Пороги производительности (≤ 25 мс на шаг и др.) — цель, а не условие слияния. PR вливается по зелёным тестам и ручному регрессу, цифры замеров (контроллер, Apple M4) дописываются в PR. Недобор — follow-up issue с разбором. Корректность — жёсткое условие.
- Поведение для пользователя не меняется: поиск, фильтры, ошибки валидации, блокировки соавторов, выделение строки, редактирование, разрядность и закрепление работают как раньше.
## Не меняется

View File

@ -15,6 +15,15 @@
Горизонтальная прокрутка (ось `x`) того же листа MUST быть не медленнее базы. База — ветка AURORA-1837, на которой стоит эта задача.
Замер — на той же машине Apple M4, на которой снят отчёт профилирования, пороги абсолютные.
Пороги этого требования и остальных требований с замерами — цель, а не условие слияния. Цифры до и после SHALL быть приведены в PR. Если цель не достигнута, SHALL быть заведена follow-up issue с разбором того, что съедает остаток. Слияние блокируют только требования корректности.
#### Scenario: Недобор до цели оформляется follow-up
- **WHEN** замер после изменения показывает медиану шага больше 25 мс или регрессию относительно базы
- **THEN** PR вливается по зелёным тестам и ручному регрессу, цифры приведены в PR
- **AND** заведена follow-up issue с разбором, какие работы главного потока занимают остаток
#### Scenario: Вертикальная прокрутка FORM_2/AHR после изменения
- **WHEN** на стенде прогоняется `PERF_TARGETS='FORM_2/AHR/*' PERF_ROLES=rf PERF_AXES=y PERF_TRACE=0 PERF_REPEATS=3 npx playwright test scenarios/scroll.spec.ts` на сборке этой ветки
- **THEN** медиана `main_thread_ms_per_step` по трём повторам не больше 25 мс
@ -28,6 +37,8 @@
Ячейка таблицы листа, которая при прокрутке осталась в видимой области и у которой не изменились данные строки и определение колонки, SHALL NOT перерисовываться. Перерисовываются только ячейки, которые появились на экране. Строки таблицы и служебные колонки (раскрытие строк) перерисовываться могут.
Числовая цель по счётчику рендеров не блокирует слияние (см. требование «Шаг прокрутки…»). Неизменность вида ячеек при прокрутке — требование корректности.
#### Scenario: Счётчик рендеров ячеек на шаг падает
- **WHEN** на профилирующей сборке (`:8081`) прогоняется `PERF_HOT_MODE=react` `perf/e2e/scenarios/hotspots.spec.ts` для FORM_2/AHR по оси `y`
- **THEN** число рендеров `ForwardRef(Styled(td))` на шаг кратно меньше 1 696, порядка числа ячеек, появившихся на экране за шаг
@ -183,6 +194,8 @@
Перетаскивание ширины закреплённой колонки на листе FORM_2/AHR SHALL стоить не больше главного потока на шаг движения мыши, чем на ветке AURORA-1837. Медиана `main_thread_ms_per_step` по трём повторам сценария `perf/e2e/scenarios/pinned-resize.spec.ts` на тех же данных и сборке того же типа. Цифры до и после, а также p95 интервала кадра MUST быть приведены в описании PR.
Порог — цель, а не условие слияния; недобор оформляется follow-up issue (см. требование «Шаг прокрутки…»). Корректная раскладка закреплённых колонок после отпускания мыши — требование корректности.
#### Scenario: Замер перетаскивания ширины закреплённой колонки
- **WHEN** сценарий закрепляет на FORM_2/AHR (роль rf, CPU 1×) первую незакреплённую колонку данных и тянет её правую границу шагами по 4 px вправо и обратно, с ожиданием кадра после каждого шага
- **THEN** медиана `main_thread_ms_per_step` на этой ветке не больше, чем на ветке AURORA-1837