textmachine/docs/archive/prompts/FRONTEND_S37_SESSION_PROMPT_2026-08-10.md

25 KiB
Raw Permalink Blame History

⚠⚠ ПРОМТ ОТРАБОТАН — исторический, НЕ задание. Исполнен восьмой фронт-сессией (S3.7, 1012.08), ПРИНЯТ и заленден D39.129 (3e465bd + доукомплектация b3405ff): контракт 0.2.2 в каноне (Ф-47 sense-required), i18n-механизм построен (задача 9, ПТ-36), комментарии кода переведены на английский; спорная находка 8.1 разрешена браузерным замером в пользу сессии. Живое состояние зоны — frontend-PROGRESS.md; находки приёмки S3.7 — вход S4-промта.

Промт: фронт-сессия S3.7 — долги слоя данных и мелкие хвосты (до S4)

Ты — фронтенд-сессия TextMachine, восьмая по счёту. Зона записи — только frontend/; сессия НЕ коммитит — дерево готовит и передаёт на лендинг оркестратору. Протокол зоны — стоящий промт FRONTEND_SESSION_PROMPT.md §«Зона и git», он в силе целиком; итоги, пинги и вопросы владельцу — ТОЛЬКО зонный журнал frontend-PROGRESS.mddocs/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), интернационализацию строк интерфейса (Ф-35: словарь + гейт, без хардкода русского) и мелкие хвосты. 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.yamlrequired + описание правила пустого), бамп 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:59awaiting() на tracked-результатах + useIsFetching даёт полный ре-рендер экрана на каждом префетче; чинить ТОЛЬКО с замером до/после, иначе не трогать;
  • src/api/vocabulary.ts:112termStatus.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); поправить при закрытии строки.

9. Ф-35 — строки интерфейса без хардкода: словарь + гейт

Сегодня ~90 русских литералов живут прямо в разметке (Ф-35); локаль примитивов уже сведена в одно место (src/ui/Locale.tsx, interfaceLocale). Сделать интерфейс переводимым МЕХАНИЗМОМ, не переводом:

  • все строки интерфейса уезжают в КАТАЛОГ сообщений, механизм — ОТРАСЛЕВОЙ i18n-инструмент, не самописная поделка: компонент берёт строку по ключу, локаль = данные, новый язык добавляется файлом перевода без захода в компоненты. Кандидатов сравнить и выбор обосновать в журнале (у react-aria уже есть свой слой @internationalized/string; react-intl / lingui — альтернативы); новая зависимость — с точным пином и записью в STACK_DECISIONS.md §3, как того требует политика пинов; цену бандла замерить;
  • гейт против нового хардкода: кириллический литерал в JSX/разметке = ошибка линта (исключения — модуль словаря и фикстуры); гейт проверить ЖИВЫМ нарушением, как Ф-4/Ф-9;
  • хранилище выбора языка подключить к словарю; каркасный пункт настроек «язык интерфейса» уже есть — честное состояние «сейчас интерфейс только русский» остаётся, пока локаль содержимого одна (ru);
  • границы прежние: фикстурная проза (тексты мок-книг, названия глав, термины банка) — ДАННЫЕ, не интерфейс, в словарь не уезжает; продуктовый список языков — решение владельца (если нужен ответ сейчас — В-строкой); переводы строк на другие языки НЕ выдумывать.

Приёмка: гейт ловит подсунутый литерал (показать нарушением); кадры семи маршрутов не изменились — это переезд строк, не перевод.

Что НЕ делать

  • Не строить экраны 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 закрыть или переформулировать честно; Ф-35 и Ф-25 закрываются в своей код-половине, продуктовая половина Ф-35 «какие языки» остаётся на владельце), вопросы владельцу — В-строками.
  • Промт S4 не писать — его выдаёт оркестратор после приёмки этого пака.