docs(openspec): aurora-1839 — факты table-core по закреплению, новая раскладка pinned-resize, решения 21 и 22
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7d7a98f9a4
commit
472f2ea222
@ -38,7 +38,7 @@
|
||||
Исключение: пока `columnSizingInfo.isResizingColumn`, `draggingColumn` или `draggingRow` истинны, MRT подставляет прежний массив из `columnDefsRef` и колонки не пересоздаёт (`:1695-1700`). Проверено node-скриптом на `table-core`:
|
||||
- новый массив с теми же определениями даёт новые ячейки;
|
||||
- прежний массив — те же ячейки;
|
||||
- смена `columnPinning` при прежнем массиве — **новая ячейка только у закрепляемой или открепляемой колонки**, ячейки остальных колонок те же. Причина: `getLeftVisibleCells`/`getRightVisibleCells` (`ColumnPinning.js:86-98`) возвращают копии `{...cell, position}`, а центральные ячейки берутся из тех же объектов `allCells`. Допущение черновика «ячейки пересоздаются только при смене `columns`/`data`» верно для ширины и видимости, но неверно для закрепляемой колонки (O-1, решение 18);
|
||||
- смена `columnPinning` при прежнем массиве — **новые ячейки у всех колонок закреплённой стороны**, центральные ячейки те же (характеристика: закрепление второй колонки слева пересоздаёт обе закреплённые, скрытие закреплённой — оставшуюся). Причина: `getLeftVisibleCells`/`getRightVisibleCells` (`ColumnPinning.js:86-98`) возвращают копии `{...cell, position}`, а центральные ячейки берутся из тех же объектов `allCells`. Допущение черновика «ячейки пересоздаются только при смене `columns`/`data`» верно для ширины и видимости, но неверно для закреплённых колонок (O-1, решения 18, 19). Ключ сброса нужен из-за ширины закреплённой колонки (смена `columnSizing` ячейки не пересоздаёт) и как страховка на случай смены поведения библиотеки;
|
||||
- смена `columnSizing` при прежнем массиве — те же ячейки (тест-характеристика).
|
||||
4. **Пока `isResizingColumn` истинно, тело таблицы заморожено.** `MRT_Table` рендерит `Memo_MRT_TableBody` (`:3780`), а тот сравнивает только `table.options.data` (`:2526`). Так MRT ведёт себя при собственном перетаскивании ширины.
|
||||
5. **Прокрутка перерисовывает только внутренние компоненты MRT.** Виртуализаторы созданы в `MRT_TableBody` (строки) и `MRT_Table` (колонки), `@tanstack/react-virtual` перерисовывает свой компонент через `useReducer`. `RealtimeTable` при прокрутке не рендерится: в нём нет состояния, меняющегося по `scroll`. `ColumnSelectionOverlay` держит своё состояние, `useHeaderPortal` состояния не держит.
|
||||
@ -292,11 +292,11 @@ const pinnedLayoutKey = useMemo(
|
||||
- после отпускания ключ соответствует новым ширинам.
|
||||
|
||||
Тест-допущение на `@tanstack/table-core` — фикстура `createTable` и `updateState`, как в `columnLayoutUtils.test.js`:
|
||||
- при прежних `columns`/`data` `row.getVisibleCells()[i]` те же;
|
||||
- при новом массиве `columns` с теми же определениями — новые;
|
||||
- при смене `columnPinning` с прежним массивом — те же.
|
||||
- при новом массиве `columns` с теми же определениями `row.getVisibleCells()[i]` новые;
|
||||
- при смене `columnSizing` с прежним массивом — те же (поэтому ключ обязателен для ширины закреплённой колонки);
|
||||
- при смене `columnPinning` с прежним массивом — новые у ячеек закреплённых колонок, центральные те же (закрепление второй колонки слева пересоздаёт обе закреплённые; скрытие закреплённой — оставшуюся закреплённую).
|
||||
|
||||
Третье утверждение — причина, по которой нужен явный сброс. Тест ссылается на этот design.md.
|
||||
Тест фиксирует характеристику table-core 8.20.5, а не ожидание дизайна, и ссылается на этот design.md.
|
||||
|
||||
**Линт:** `cd web && npx biome check src`. `useExhaustiveDependencies` настроен на уровень `info`. Зависимость `cellMemoResetKey` в `useMemo` для `tableColumns` намеренная, помечается комментарием.
|
||||
|
||||
@ -339,10 +339,10 @@ const pinnedLayoutKey = useMemo(
|
||||
**Сценарий `perf/e2e/scenarios/pinned-resize.spec.ts` (добавить)** — по образцу `scroll.spec.ts`: `loginAs`, `PROBE_SCRIPT`, `startProbe`/`perStep`, `throttle`, `append`, `buildTargets` по манифесту, фильтр `PERF_TARGETS`.
|
||||
1. Перед навигацией (через `addInitScript` или `page.evaluate` до загрузки листа) очистить в localStorage ключи `${formId}_${formType}_${sheetName}_${direction}_columnPinning`, `_columnSizing`, `_columnVisibility` этого листа, чтобы начальная раскладка была по умолчанию: закреплены `mrt-row-expand` и `sort_order`.
|
||||
2. Открыть лист FORM_2/AHR под `rf`, дождаться строк (как `scroll.spec.ts`, `MIN_ROWS`).
|
||||
3. Выбрать первую незакреплённую колонку данных: первый `[data-col-select-id]` в строке букв, чей id не `sort_order`/`mrt-row-expand`, а лучше первый `data.*`. Кликнуть его, затем кнопку «Закрепить область»: `[data-pin-panel] button[aria-label="Закрепить область"]`, MUI `Tooltip` ставит `aria-label` по `title`. Дождаться `data-active="true"` на кнопке.
|
||||
4. Найти ресайзер `[data-col-resize-id="<id>"]`, взять `boundingBox`, `page.mouse.move` в его центр, `page.mouse.down()`.
|
||||
3. Выбрать две первые колонки `data.*` в DOM-порядке (нет второй — `skip_reason: 'second_column_not_found'`). Для каждой по очереди: кликнуть `[data-col-select-id]`, затем кнопку «Закрепить область»: `[data-pin-panel] button[aria-label="Закрепить область"]`, MUI `Tooltip` ставит `aria-label` по `title`. Дождаться `data-active="true"` на кнопке.
|
||||
4. Найти ресайзер ПЕРВОЙ из закреплённых колонок `[data-col-resize-id="<id>"]`, взять `boundingBox`, `page.mouse.move` в его центр, `page.mouse.down()`.
|
||||
5. `startProbe`. Затем 50 шагов по +4 px и 50 шагов по −4 px: `page.mouse.move(x, y)` и после каждого шага ожидание кадра (`requestAnimationFrame`, как `tick` в `lib/scroll.ts`). Потом `page.mouse.up()`, ещё два кадра, `stop()`. `steps = 100`.
|
||||
6. Проверка геометрии после отпускания: для первой видимой строки тела взять `td[data-pinned="true"]` по порядку и проверить, что `left[i+1]` равен `left[i] + width[i]` с допуском 1 px. Нарушение — `incomplete: true, skip_reason: 'pinned_overlap'`.
|
||||
6. Проверка геометрии после отпускания: для первой видимой строки тела взять `td[data-pinned="true"]` по порядку и проверить, что `left[i+1]` равен `left[i] + width[i]` с допуском 1 px. Нарушение — `incomplete: true, skip_reason: 'pinned_overlap'`. Меньше 4 `td[data-pinned="true"]` в строке — `skip_reason: 'pinned_cells_missing'` (`pinned_cells_n`). Рост ширины на пике хода (шаг 50) меньше 100 px — `skip_reason: 'resize_no_effect'`.
|
||||
7. `append({ scenario: 'pinned_resize', target: 'FORM_2/AHR/null@pinned', role: 'rf', cpu_rate, repeat, metrics: { ...probe, ...perStep(probe, steps), steps } })`.
|
||||
8. Повторы — `PERF_REPEATS` (по умолчанию 3). Режим `PERF_HOT_MODE=react` по образцу `hotspots.spec.ts` — опционально, для числа `Styled(td)` на шаг перетаскивания.
|
||||
|
||||
@ -436,13 +436,18 @@ const pinnedLayoutKey = useMemo(
|
||||
- Обоснование: решение 12 и требование «PR открывается с кодом, тестами и чек-листом». Backend в задаче не меняется.
|
||||
- Цена ошибки: если ручной регресс найдёт нарушение корректности, исправление пойдёт коммитами в уже открытый PR.
|
||||
18. **2026-10-28 (контроллер, R-2, O-1) — тест-допущение table-core заменено на фиксацию фактического поведения 8.20.5; ключ сброса по закреплению сохранён.**
|
||||
- Факт: смена `columnPinning` при прежнем `columns` пересоздаёт ячейку только закрепляемой колонки; смена `columnSizing` ячейки не пересоздаёт. «Контекст» п. 3 и D1 исправлены.
|
||||
- Обоснование: решение человека (сброс при закреплении, скрытии, фильтрах) не меняется. Остальные ячейки закрепление не пересоздаёт, а их `left`/`data-pinned` считает мемоизированная обёртка MRT.
|
||||
- Факт: смена `columnPinning` при прежнем `columns` пересоздаёт ячейки закреплённых колонок (стороны), центральные те же; смена `columnSizing` ячейки не пересоздаёт. «Контекст» п. 3 и D1 исправлены.
|
||||
- Обоснование: решение человека (сброс при закреплении, скрытии, фильтрах) не меняется. Центральные ячейки закрепление не пересоздаёт, а их `left`/`data-pinned` считает мемоизированная обёртка MRT.
|
||||
- Цена ошибки: если table-core когда-нибудь начнёт пересоздавать все ячейки, ключ станет частично избыточным (лишняя перерисовка на редких действиях). Тесты `table-core:` это покажут.
|
||||
19. **2026-10-28 (исполнитель задачи 1, R-3) — формулировка «ключ избыточен по закреплению» из рулинга R-2 неточна и не принята.** Тест pinning утверждает: новая только ячейка закрепляемой колонки, остальные те же. Обоснование — решение 18. Цена ошибки: только формулировка.
|
||||
19. **2026-10-28 (исполнитель задачи 1, R-3) — формулировка «ключ избыточен по закреплению» из рулинга R-2 неточна и не принята.** Исходный тест pinning утверждал: новая только ячейка закрепляемой колонки; уточнено финальным ревью (M-1): пересоздаются ячейки всех закреплённых колонок стороны. Обоснование — решение 18. Цена ошибки: только формулировка.
|
||||
20. **2026-10-28 (исполнитель задачи 6, R-4) — в сценарий `pinned-resize` добавлены решения, которых нет в плане:**
|
||||
- новая причина пропуска `skip_reason: 'column_not_found'` (не нашлась колонка для закрепления);
|
||||
- если в манифесте нет пользователя `rf`, тесты не регистрируются;
|
||||
- ожидание не менее 20 строк сделано через `page.waitForFunction` с таймаутом `PERF_LOAD_TIMEOUT_MS`, без SETTLE-логики `scroll.spec.ts`.
|
||||
- Обоснование: аналогично `resizer_not_found`; сценарий не должен падать целым прогоном при отсутствии данных.
|
||||
- Цена ошибки: запись помечается `incomplete` вместо падения теста. Видно по `skip_reason` в результатах.
|
||||
21. **2026-10-28 (контроллер, R-5, I-2) — сценарий `pinned-resize` изменён: закрепляются две data-колонки, тянется первая; добавлены проверки `pinned_cells_missing` (<4 закреплённых td) и `resize_no_effect`.**
|
||||
- Обоснование: прежняя раскладка тянула последнюю закреплённую колонку, у которой нет закреплённого соседа справа, поэтому устаревший `left` появиться не мог и проверка `findPinnedGap` не могла упасть.
|
||||
- Ширина измеряется одним `boundingBox` на шаге 50 (пик хода), до хода — один вызов; цена — один дополнительный вызов внутри пробы из 100 шагов.
|
||||
- Цена ошибки: замеры нового сценария несравнимы со снятыми по старой раскладке; сравнение с базой делается тем же сценарием на обеих ветках.
|
||||
22. **2026-10-28 (контроллер, R-6, M-3, O-3) — диапазон `['','']` не нормализуется в ключе; код не меняется.** В TanStack такой диапазон не снимает фильтр колонки, ключ отличается от «без фильтров»; это безвредный лишний сброс. Наблюдение O-3 (вне скоупа, follow-up): `Cell/Cell/Cell.jsx` ~112 `createHighlightedContent` вызывает `q.replace` на значении фильтра-массива (поведение базы). Цена ошибки: одна лишняя перерисовка на редком действии.
|
||||
|
||||
@ -180,7 +180,7 @@
|
||||
- при изменении данных;
|
||||
- пока ширину колонки тянут.
|
||||
|
||||
Тест MUST также фиксировать допущение `@tanstack/table-core`: новый массив колонок даёт новые объекты ячеек, а смена закрепления при прежнем массиве колонок — нет.
|
||||
Тест MUST также фиксировать поведение `@tanstack/table-core` 8.20.5: новый массив колонок даёт новые объекты ячеек; смена `columnSizing` при прежнем массиве колонок ячейки не пересоздаёт (поэтому ключ обязателен для ширины закреплённой колонки); смена `columnPinning` пересоздаёт ячейки закреплённых колонок (сторон), центральные ячейки остаются теми же.
|
||||
|
||||
#### Scenario: Ключ реагирует на раскладку закреплённых колонок и фильтры
|
||||
- **WHEN** запускается `node --test` для модуля ключа сброса
|
||||
@ -188,7 +188,7 @@
|
||||
|
||||
#### Scenario: Допущение о пересоздании ячеек
|
||||
- **WHEN** запускается `node --test` для теста допущения на `@tanstack/table-core`
|
||||
- **THEN** тест подтверждает: при прежних `columns` и `data` объекты `row.getVisibleCells()[i]` те же; при новом массиве `columns` (даже с теми же определениями) — новые; при смене `columnPinning` с прежним массивом `columns` — те же
|
||||
- **THEN** тест подтверждает: при новом массиве `columns` (даже с теми же определениями) объекты `row.getVisibleCells()[i]` новые; при смене `columnSizing` с прежним массивом `columns` — те же; при смене `columnPinning` с прежним массивом `columns` — новые у ячеек закреплённых колонок, у центральных — те же
|
||||
|
||||
### Requirement: Перетаскивание ширины закреплённой колонки не медленнее базы
|
||||
|
||||
@ -197,6 +197,6 @@
|
||||
Порог — цель, а не условие слияния; недобор оформляется follow-up issue (см. требование «Шаг прокрутки…»). Корректная раскладка закреплённых колонок после отпускания мыши — требование корректности.
|
||||
|
||||
#### Scenario: Замер перетаскивания ширины закреплённой колонки
|
||||
- **WHEN** сценарий закрепляет на FORM_2/AHR (роль rf, CPU 1×) первую незакреплённую колонку данных и тянет её правую границу шагами по 4 px вправо и обратно, с ожиданием кадра после каждого шага
|
||||
- **WHEN** сценарий закрепляет на FORM_2/AHR (роль rf, CPU 1×) две первые колонки данных и тянет правую границу первой из них шагами по 4 px вправо и обратно, с ожиданием кадра после каждого шага
|
||||
- **THEN** медиана `main_thread_ms_per_step` на этой ветке не больше, чем на ветке AURORA-1837
|
||||
- **AND** после отпускания мыши закреплённые колонки примыкают друг к другу (проверка геометрии в сценарии проходит)
|
||||
- **AND** после отпускания мыши не менее четырёх `td[data-pinned="true"]` строки (`mrt-row-expand`, `sort_order`, две колонки данных) примыкают друг к другу (проверка геометрии в сценарии проходит)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user