textmachine/frontend/docs/S37_SESSION_PROMPT.md

208 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Промт: фронт-сессия 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. Контекст холодному исполнителю:
> оболочка и витрина построены сессиями S0S3.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 (⚠ её разделы 1215 стоят
в ОБРАТНОМ порядке после §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`); поправить при закрытии строки.
## Что НЕ делать
- Не строить экраны S4S7 (загрузка книги, механизм подписи, содержимое настроек, экспорт).
- Не трогать канон контракта `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: если по ходу покажется, что пора вернуть ресайз столбцов — НЕ возвращать; строка
требует свежего замера от новой базы, это не задача пака.
## Отчёт и приёмка
- Каждая задача 13: ТЕСТ, падающий на до-фиксном коде (vitest поверх MSW; поток мокается
событиями), и зелёный после; в журнал — что именно ловит тест и как воспроизводился дефект.
- `npm run check` и `npm run check:full` зелёные на сдаче; счётчик тестов вырос, ни одна
существующая проверка не ослаблена.
- **Мандат самопроверки:** ревью ИСПОЛНЕНИЕМ своего кода, своих кадров и своих сценариев +
адверсариальное ревью диффа (author≠reviewer) в конце; находки с диспозициями — в журнал.
- Прогон по всем задачам таблицей в `frontend-PROGRESS.md`: задача → что сделано → тест/замер →
либо диспозиция. Хвосты — строками Ф-N в `BACKLOG.md` (Ф-49/50/51/40/47 закрыть или
переформулировать честно), вопросы владельцу — В-строками.
- Промт S4 не писать — его выдаёт оркестратор после приёмки этого пака.