22 KiB
Промт: фронт-сессия 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 стартует после неё отдельным промтом. По всем задачам — прогон в отчёте; молча пропустить задачу нельзя: сделано / диспозиция с обоснованием / вопрос В-строкой.
Обязательное чтение до кода (порядок)
FRONTEND_SESSION_PROMPT.md— стоящий промт: зона, git, жёсткие ограничения.frontend-PROGRESS.md: шапка «Текущее состояние», затем приёмочная запись оркестратора 10.08 (первая в хронике) — в записи S3.6 есть число-дрейф против финального дерева, и поправки перечислены именно там; затем сама запись S3.6 (⚠ её разделы 12–15 стоят в ОБРАТНОМ порядке после §11).BACKLOG.mdстроки Ф-40, Ф-47, Ф-49, Ф-50, Ф-51 — формулировки не переизобретать, но и не принимать на веру: КАЖДУЮ проверить по коду до правки.- Контракт: зонная копия
frontend/docs/api-contract/openapi.yaml— §Unit,EventEnvelope,Revision,resync_required,BankTerm; поведение ревизий и правило>= R— норматив. 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-note2.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 не писать — его выдаёт оркестратор после приёмки этого пака.