208 lines
22 KiB
Markdown
208 lines
22 KiB
Markdown
# Промт: фронт-сессия S3.7 — долги слоя данных и мелкие хвосты (до S4)
|
||
|
||
Ты — фронтенд-сессия TextMachine, восьмая по счёту. Зона записи — **только `frontend/`**; сессия
|
||
НЕ коммитит — дерево готовит и передаёт на лендинг оркестратору. Протокол зоны — стоящий промт
|
||
`FRONTEND_SESSION_PROMPT.md` §«Зона и git», он в силе целиком; итоги, пинги и вопросы владельцу —
|
||
ТОЛЬКО зонный журнал `frontend-PROGRESS.md` (в `docs/PROGRESS.md` фронт не пишет). В дереве живут
|
||
незакоммиченные файлы ДРУГИХ зон (полигон: `eval/*`, `docs/*`) — не трогать и не «прибирать»;
|
||
у полигона прямо сейчас идёт ЖИВОЙ платный прогон — ничего в `eval/` и `~/books/` не запускать,
|
||
не читать замки, не убивать процессы.
|
||
|
||
> Промт написан оркестратором (10.08.2026) по приёмке S3.6. Контекст холодному исполнителю:
|
||
> оболочка и витрина построены сессиями S0–S3.6 и приняты владельцем (три круга замечаний);
|
||
> слой данных стоит с S3 — `src/api/` разговаривает настоящим HTTP через MSW-мок сети,
|
||
> живой поток идёт по SSE (`src/showcase/useRunStream.ts`), чтения — TanStack Query
|
||
> (`src/api/queries.ts`), контракт — зонная копия `frontend/docs/api-contract/openapi.yaml`
|
||
> (0.2.1), типы генерятся из неё. Эта сессия НЕ строит новых экранов: она закрывает названный
|
||
> техдолг слоя данных (Ф-49…Ф-51), одну ратифицированную правку контракта (Ф-47) и мелкие
|
||
> хвосты. S4 стартует после неё отдельным промтом. По всем задачам — прогон в отчёте;
|
||
> **молча пропустить задачу нельзя**: сделано / диспозиция с обоснованием / вопрос В-строкой.
|
||
|
||
## Обязательное чтение до кода (порядок)
|
||
|
||
1. `FRONTEND_SESSION_PROMPT.md` — стоящий промт: зона, git, жёсткие ограничения.
|
||
2. `frontend-PROGRESS.md`: шапка «Текущее состояние», затем **приёмочная запись оркестратора
|
||
10.08 (первая в хронике)** — в записи S3.6 есть число-дрейф против финального дерева,
|
||
и поправки перечислены именно там; затем сама запись S3.6 (⚠ её разделы 12–15 стоят
|
||
в ОБРАТНОМ порядке после §11). `BACKLOG.md` строки Ф-40, Ф-47, Ф-49, Ф-50, Ф-51 —
|
||
формулировки не переизобретать, но и не принимать на веру: КАЖДУЮ проверить по коду
|
||
до правки.
|
||
3. Контракт: зонная копия `frontend/docs/api-contract/openapi.yaml` — §`Unit`,
|
||
`EventEnvelope`, `Revision`, `resync_required`, `BankTerm`; поведение ревизий и правило
|
||
`>= R` — норматив.
|
||
4. `FRONTEND_PLAN.md` §5 — гейты и замеры: что уже стоит и как расширять.
|
||
|
||
## Задачи
|
||
|
||
### 1. Ф-49 — текст открытой главы не подписан ни на один сигнал изменения
|
||
|
||
`keys.units` не инвалидируется нигде: `useRunStream.ts` по кадрам инвалидирует notes/bank/book,
|
||
кадр `chapter` патчит лишь СПИСОК глав, а открытые главы живут на keep-alive вкладках и не размонтируются —
|
||
читатель, не уводивший фокус из окна, нового текста не увидит вообще. Контракт прямо предписывает
|
||
обратное (`openapi.yaml`, `Unit`: «the live "something changed" signal arrives as an event; the
|
||
text arrives with a read after the boundary»).
|
||
|
||
Сделать: инвалидация чтений юнитов открытой главы на кадре `chapter` этой главы И на смене
|
||
`status` книги (границы стадий — за всю черновую волну кадр `chapter` может не прийти ни разу,
|
||
пока К-10 открыт). Безопасная сторона: лишняя инвалидация дешевле невидимого текста, но
|
||
НЕ инвалидировать все главы на каждый кадр — скоуп по ключу главы из кадра.
|
||
|
||
### 2. Ф-50 — кадр потока, пришедший до появления чтения в кэше, теряется молча
|
||
|
||
`useRunStream.ts`: обработчики делают `list === undefined ? list : {...}` — в пустой кэш кадр
|
||
не пишется, а `acceptFrame` уже сдвинул отметку ревизии; `freshest(undefined, next)` затем
|
||
принимает снимок, сделанный ДО кадра. Окно — старт экрана и resync. Рядом второе того же класса:
|
||
многостраничное чтение штампует «рваный» список ревизией ПОСЛЕДНЕЙ страницы (`queries.ts`),
|
||
хотя контракт предупреждает, что ревизия движется во время пагинации.
|
||
|
||
Сделать: кадр, не нашедший кэша, обязан оставить след — либо инвалидация ключа, либо отметка
|
||
ревизии не сдвигается до успешной записи; для пагинации — ревизия списка не выше МИНИМАЛЬНОЙ
|
||
из страниц либо перечитывание при сдвиге ревизии между страницами. Выбор решения обосновать
|
||
в журнале ЗАМЕРОМ/тестом, не рассуждением.
|
||
|
||
### 3. Ф-51 — `resync` сбрасывает один гард из двух
|
||
|
||
На `resync_required` зовётся `invalidateQueries()` без ключа: данные сохраняются, гард
|
||
`dropStaleReads` продолжает сравнивать новый снапшот со СТАРОЙ отметкой — а по контракту resync
|
||
выдаётся именно после полной замены, где монотонность ревизии не обещана. Вторым слоем: вызов
|
||
без ключа бьёт по чужим скоупам (библиотека, usage, run-options, другие книги).
|
||
|
||
Сделать: `resetQueries`/`removeQueries` по скоупу КНИГИ + сброс отметок ревизий этой книги
|
||
(стриминговая сторона уже сбрасывает — довести симметрию до кэша). Чужие скоупы не трогать.
|
||
|
||
### 4. Ф-47 — правка контракта: `sense` становится обязательным (РАТИФИЦИРОВАНО)
|
||
|
||
Решение оркестратора (автор контракта), исполняется этой сессией в зонной копии:
|
||
`BankTerm.sense` — в `required`; пустая строка = «различителя нет». Дифф в канон
|
||
`docs/architecture/14-api-contract/` переносит оркестратор при лендинге — канон сам не трогать.
|
||
|
||
Сделать: правка `frontend/docs/api-contract/openapi.yaml` (в `required` + описание правила пустого),
|
||
бамп `info.version` 0.2.1 → 0.2.2, `npm run contract` зелёный, `npm run contract:types` —
|
||
перегенерить `src/api/schema.ts`, убрать костыли `?? ''` по `sense` (проверить грепом ВСЕ места,
|
||
не по счёту из строки бэклога — он там завышен), фикстуры мока привести (каждый терм несёт
|
||
`sense`, хотя бы пустой строкой).
|
||
|
||
### 5. Ф-40 — общий модуль стилей поля ввода
|
||
|
||
`src/ui/TextField.module.css` и `src/ui/FilterField.module.css` держат байт-одинаковые `.input`,
|
||
`::placeholder`, `:focus`; отличие — цвет границы в покое. Роли примитивов разные, сливать
|
||
примитивы нельзя — общий CSS-модуль поля на оба, отличия остаются у ролей.
|
||
|
||
### 6. Ф-25 — свод языка комментариев (конвенция владельца 04.08)
|
||
|
||
Комментарии S1/S2 в ~40 файлах (`api/index.ts`, `showcase/*`, `ui/*`, `tokens/*`, `scripts/*`)
|
||
остались русскими; приёмка нашла русские комментарии и в **~14 НОВЫХ файлах S3.6** (например,
|
||
`src/showcase/About.tsx`) — вопреки конвенции и уточнению владельца по ходу самой S3.6.
|
||
Перевести на английский ОДНИМ механическим проходом, покрыв ОБА множества. Граница жёсткая:
|
||
переводятся комментарии, имена тестов и служебные строки; **UI-текст и фикстурная проза остаются
|
||
русскими — это продукт, а не код.** Смыслы не менять; комментарий, который при переводе оказался
|
||
враньём против кода, — чинить по коду и назвать в журнале.
|
||
|
||
### 7. Сторожа на то, что мерилось руками
|
||
|
||
Из таблицы долгов S3.6: зум 125/150/200% и узкая панель замерены руками, сторожа нет. Дёшево
|
||
и без новых механизмов: в существующие сценарии добавить прогон одной-двух ключевых проверок
|
||
на втором вьюпорте/зуме (выбор — по замеру, что реально ловит регресс раскладки). Если дёшево
|
||
не выходит — честная диспозиция в журнале, не молчание.
|
||
|
||
Отдельно: референс fleet_2.png, на котором стоит палитра S3.6 и категория `toned` в
|
||
`src/tokens/tokens.test.ts`, докинут владельцем в `frontend/references/` при приёмке 10.08
|
||
(быструю пиксельную пробу якорей оркестратор сделал — совпало; запись в журнале). Прогнать
|
||
полный `measure.py` по палитре и вкладкам, записать числа в `FRONTEND_PLAN.md` §5 — замер
|
||
становится воспроизводимым; ⚠ PIL для `measure.py` не запинен (см. 8.5) — решается тем же
|
||
пунктом.
|
||
|
||
### 8. Находки приёмки S3.6 (закрыть здесь же)
|
||
|
||
Приёмочное ревью оркестратора (12 агентов: шесть линз + скептики-верификаторы, author≠reviewer)
|
||
подтвердило 12 находок. Ниже те, что закрывает эта сессия; каждая грунтована `file:line` —
|
||
проверяй по коду, не по списку (строки могли съехать).
|
||
|
||
**8.1 (HIGH) Замер ширин таблицы банка молча проваливается в скрытой keep-alive вкладке
|
||
и не повторяется при показе.** `src/ui/Table.tsx:232` — зависимости эффекта замера
|
||
`[frame, named, sample, marks, hasRows]` не знают о видимости вкладки; под
|
||
`content-visibility: hidden` (`src/ui/Tabs.module.css`) геометрия нулевая, `read()` не набирает столбцов,
|
||
10 кадров ретрая и молчаливый `return`. Сценарий: открыть книгу, уйти с вкладки «Банк» ДО прихода
|
||
`/bank`, вернуться — столбцы падают в `1fr` без потолков (ровно «баг 4» владельца, объявленный
|
||
починенным). Лечение выбрать по замеру (триггер на возврат видимости:
|
||
`contentvisibilityautostatechange` / ResizeObserver / зависимость от выбранной вкладки);
|
||
приёмка — playwright-сценарий, воспроизводящий уход-возврат, и падающий на до-фиксном коде тест.
|
||
|
||
**8.2 Вторичный текст и индикаторы теряют контраст на новом синем `--color-selected`.**
|
||
Выбранная строка дерева — ПОСТОЯННОЕ состояние (открытый раздел всегда выбран), а содержимое
|
||
не перекрашивается: на #1c4478 лежат текст 12px #9b9b9b (3.52:1 при пороге 4.5), иконка #707070
|
||
(1.98:1), дорожка прогресса #262626 (1.55:1) — `src/ui/Tree.module.css:46` красит только фон,
|
||
`src/showcase/Library.module.css` содержимое не трогает. Починить пары на selected-фоне, не уезжая от
|
||
референса; axe этого НЕ ловит (non-text не проверяет, выбранную строку со счётчиком не видел) —
|
||
добавить сторож в сцену.
|
||
|
||
**8.3 Путь из «Замечаний» не загарден: `open` по id, отсутствующему в `chapters`.**
|
||
`src/showcase/Showcase.tsx:186` + `src/showcase/Notes.tsx:53-54` — строка сводки с определённым, но отсутствующим id
|
||
рисуется кликабельной; `Tabs` контролируемый (авто-фолбэка react-stately на первую вкладку НЕТ),
|
||
итог — пустой центр без empty-состояния. Гардить как Library-путь (`positionOf`), сироту
|
||
помечать и в строке сводки (сейчас `data-orphan` только при `chapter_id === undefined`).
|
||
|
||
**8.4 Съёмка нестабильна — два источника гонки, лечить оба.**
|
||
(а) `src/showcase/useScreenshotFlag.ts:22` — на первом рендере `useIsFetching() === 0` и экран
|
||
объявляется `ready` ДО старта первого запроса (нотификация батчится в отдельную задачу); гейт
|
||
`scripts/shot.mjs` ждёт ровно `dataset.screen`. (б) Поймано исполнением на приёмке: axe стартует во время 220мс-анимации
|
||
карточки ожидания — один прогон `npm run shot` напечатал «контраст: 4 узла» на `/loading`,
|
||
два повторных — ноль. Стабилизировать цикл: убрать транзиент `ready`, дожидаться устаканивания
|
||
анимаций до прогона axe.
|
||
|
||
**8.5 Минорные — одним проходом** (правки на строки, не механизмы):
|
||
- `src/ui/Table.tsx:257` — кэш канвас-замера не инвалидируется, если шрифт так и не загрузился
|
||
(отравление ширинами fallback);
|
||
- `src/showcase/Document.tsx:33` — пин вкладки срабатывает на ЛЮБОЙ `mouseup` при непустом
|
||
глобальном выделении (правый клик, выделение в соседней панели);
|
||
- `src/showcase/Showcase.tsx:59` — `awaiting()` на tracked-результатах + `useIsFetching` даёт
|
||
полный ре-рендер экрана на каждом префетче; чинить ТОЛЬКО с замером до/после, иначе не трогать;
|
||
- `src/api/vocabulary.ts:112` — `termStatus.label` («предложен»/«черновик»/«подписан») не
|
||
рендерится ни одним экраном: снять либо применить; продуктовые слова не выдумывать;
|
||
- `src/tokens/tokens.css:27` — в комментарии `--color-text-muted` число 3.9:1, фактически 3.58:1;
|
||
`tokens.css:33` — `--color-note` 2.98:1 на панели при пороге 3:1 для non-text (добрать);
|
||
`tokens.css:68` — комментарий `--row-height-term` ссылается на удалённый двухстрочный список;
|
||
- `src/showcase/Status.module.css:8` — `.paused`/`.offline` стали no-op (цвет совпал с базой):
|
||
вернуть отличие либо снять классы;
|
||
- `src/ui/Button.module.css:36` — подпись disabled-кнопки ТЕКСТОМ `--color-text-muted` против
|
||
контракта токена «non-text only» (WCAG disabled освобождает — противоречие контракту токена,
|
||
привести одно к другому);
|
||
- `scripts/scenes.mjs:170` — сторож обрезки вкладок молча зеленеет, если заголовок вкладки
|
||
перестанет быть `span`;
|
||
- `scripts/measure.py:13` — PIL нигде не запинен: манифест окружения для `scripts/` либо честная
|
||
шапка «чем ставить».
|
||
|
||
**8.6 Доки зоны — привести к коду** (правится СВОЁ; записи журнала S3.6 не переписывать —
|
||
поправки к ним уже аннотированы приёмочной записью оркестратора в журнале):
|
||
- `FRONTEND_PLAN.md` §5.6 описывает СНЯТУЮ модель вкладок (вырез в хроме) как текущую — в коде
|
||
пилюля по fleet.png (возврат разделом 13 записи S3.6); §4 говорит «сценариев пять» — их семь;
|
||
- `frontend/README.md` — вписать S3.6 (и эту сессию) в карту сессий;
|
||
- `BACKLOG.md` Ф-47 — «`?? ''` в трёх местах» завышено: вхождение одно
|
||
(`src/showcase/Bank.tsx:166`); поправить при закрытии строки.
|
||
|
||
## Что НЕ делать
|
||
|
||
- Не строить экраны S4–S7 (загрузка книги, механизм подписи, содержимое настроек, экспорт).
|
||
- Не трогать канон контракта `docs/architecture/14-api-contract/` — только зонную копию (задача 4).
|
||
- Поля банка из черновика S3.5/S3.6 (`note`, `gender`, `contexts`…) НЕ заводить ни в спеку,
|
||
ни в фикстуры — черновик ждёт ратификации (граница ПТ-33 — на владельце).
|
||
- Продуктовые слова не выдумывать: В-3 (ступень замечания), В-4 (глава без заголовка),
|
||
В-6 (фраза паузы), В-8 (слово состояния в дереве) — на владельце.
|
||
- Тесты и гейты не подгонять под зелень (D39.121): красный гейт — вопрос оркестратору
|
||
через журнал, не правка гейта.
|
||
- Никакого `git add`/`git commit` — дерево передаётся на лендинг как есть.
|
||
- Ф-48: если по ходу покажется, что пора вернуть ресайз столбцов — НЕ возвращать; строка
|
||
требует свежего замера от новой базы, это не задача пака.
|
||
|
||
## Отчёт и приёмка
|
||
|
||
- Каждая задача 1–3: ТЕСТ, падающий на до-фиксном коде (vitest поверх MSW; поток мокается
|
||
событиями), и зелёный после; в журнал — что именно ловит тест и как воспроизводился дефект.
|
||
- `npm run check` и `npm run check:full` зелёные на сдаче; счётчик тестов вырос, ни одна
|
||
существующая проверка не ослаблена.
|
||
- **Мандат самопроверки:** ревью ИСПОЛНЕНИЕМ своего кода, своих кадров и своих сценариев +
|
||
адверсариальное ревью диффа (author≠reviewer) в конце; находки с диспозициями — в журнал.
|
||
- Прогон по всем задачам таблицей в `frontend-PROGRESS.md`: задача → что сделано → тест/замер →
|
||
либо диспозиция. Хвосты — строками Ф-N в `BACKLOG.md` (Ф-49/50/51/40/47 закрыть или
|
||
переформулировать честно), вопросы владельцу — В-строками.
|
||
- Промт S4 не писать — его выдаёт оркестратор после приёмки этого пака.
|