From 045aa4ba183170480b3c0491c7c1cd10aa85a646 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 20:41:09 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20aurora-1839-cell-memo=20?= =?UTF-8?q?=E2=80=94=20=D0=BF=D0=BE=D1=80=D0=BE=D0=B3=D0=B8=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D1=83=D1=8E=D1=82=20?= =?UTF-8?q?=D1=81=D0=BB=D0=B8=D1=8F=D0=BD=D0=B8=D0=B5,=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=D1=8B=20=D1=83=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Решения 28.10: пороги производительности — цель, недобор в follow-up; машина замеров Apple M4, пороги абсолютные; замеры выполняет контроллер после ночного прогона; пустая полоса при перетаскивании по умолчанию принимается как штатное поведение MRT. Co-Authored-By: Claude Opus 5.5 --- .../changes/aurora-1839-cell-memo/design.md | 29 ++++++++++++++++--- .../changes/aurora-1839-cell-memo/proposal.md | 1 + .../specs/sheet-table/spec.md | 13 +++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/openspec/changes/aurora-1839-cell-memo/design.md b/openspec/changes/aurora-1839-cell-memo/design.md index 42992fa..8505837 100644 --- a/openspec/changes/aurora-1839-cell-memo/design.md +++ b/openspec/changes/aurora-1839-cell-memo/design.md @@ -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=""]`, а если его нет — последний дочерний `div` с `cursor: col-resize` внутри ячейки заголовка той же колонки. Ячейка заголовка находится по горизонтальной координате `[data-col-select-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. diff --git a/openspec/changes/aurora-1839-cell-memo/proposal.md b/openspec/changes/aurora-1839-cell-memo/proposal.md index 241550e..77b9b18 100644 --- a/openspec/changes/aurora-1839-cell-memo/proposal.md +++ b/openspec/changes/aurora-1839-cell-memo/proposal.md @@ -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 с разбором. Корректность — жёсткое условие. - Поведение для пользователя не меняется: поиск, фильтры, ошибки валидации, блокировки соавторов, выделение строки, редактирование, разрядность и закрепление работают как раньше. ## Не меняется diff --git a/openspec/changes/aurora-1839-cell-memo/specs/sheet-table/spec.md b/openspec/changes/aurora-1839-cell-memo/specs/sheet-table/spec.md index fe29b00..3e6993e 100644 --- a/openspec/changes/aurora-1839-cell-memo/specs/sheet-table/spec.md +++ b/openspec/changes/aurora-1839-cell-memo/specs/sheet-table/spec.md @@ -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