textmachine/frontend/docs/S3_SESSION_PROMPT.md

31 KiB
Raw Blame History

Промт: фронт-сессия S3 — слой данных

Ты — фронтенд-сессия TextMachine, четвёртая по счёту. Зона записи — только frontend/; сессия не коммитит — лендит оркестратор (протокол — стоящий промт FRONTEND_SESSION_PROMPT.md §«Зона и git», он в силе целиком). Пре-коммит хук стоит и самоустанавливается при npm install.

Промт написан сессией S2 по просьбе владельца — как передача с той точки, где S2 встала. Ратифицирует и правит его оркестратор; при расхождении с его хендофф-промтом побеждает тот.

Входное состояние (04.08, проверено исполнением, а не заявлено)

  • Пройдено S0S2: план · инструменты и скриншот-цикл · tokens.css по замерам референса · оболочка трёх панелей (src/shell/) · примитивы src/ui/ на react-aria-components · витрина /showcase и нагрузочный /scale (2284 раздела, 1200 терминов).
  • npm run check — 5 гейтов (prettier · eslint тип-осведомлённый · stylelint · tsc · vitest, 77 тестов), npm run check:full — + сборка + три скриншота с гейтом доступности axe. Всё зелёное на 04.08.
  • Данные всё ещё синхронная фикстура: src/api/ отдаёт объекты из src/mock/ напрямую. Это диспозиция Ф-15, а не недосмотр: асинхронность приезжает вместе с MSW, то есть в S3.
  • Гейты, которые ты застаёшь: одно место для цвета и размера (TSX-половина в eslint.config.js, CSS-половина в stylelint.config.js) · три шва — фикстуры видит только src/api/, глобальный CSS только src/main.tsx, react-aria-components только src/ui/ — закрытые и для статического, и для динамического импорта · сеть запрещена вне src/api/ во всех транспортах (fetch/EventSource/WebSocket/XMLHttpRequest, включая window.*) · тест общности: код языка не зашивается в разметку, стили не ветвятся по :lang(…) · контракт-тест токенов и структурные тесты CSS-модулей.
  • Правки S2 и дофикс по ревью ЗАЛЕНДЕНЫ (02872b6) — восстанавливать нечего, дерево фронта чистое. ⚠ В дереве могут лежать незакоммиченные файлы ДРУГИХ зон (параллельные сессии — норма): не трогать и не «прибирать».

Замок снят (оркестратор №14, 05.08)

Контракт существует и ратифицирован (docs/architecture/14-api-contract/, D39.99), строка 95 закрыта, вторая сторона контракта — платформа — уже написана и принята: её скелет живёт в platform/ и ратифицированные ответы платформы вошли в список правок спеки ниже. Раздел «Если замок закрыт» оставлен как исторический — исполнять его НЕ надо. Один сохраняющийся запрет: не начинать «пока на своих типах, потом поправим» — моки, снятые не с того контракта, разойдутся с API, ровно ради чего строка 95 и заводилась.

Обязательное чтение до кода (порядок)

  1. frontend/docs/FRONTEND_SESSION_PROMPT.md — стоящий промт: сценарий §3, жёсткие ограничения §4, «как работать» §7. Его не редактировать. ⚠ Раскладка §2 частично устарела — её перевешивают решения владельца из журнала зоны.
  2. frontend/docs/frontend-PROGRESS.md целиком: «Текущее состояние» · «Решения владельца по продукту» · «Открытые вопросы» · хронику 04.08 (в ней — что S2 построила, где отошла от референса и что поправило ревью оркестратора).
  3. frontend/docs/FRONTEND_PLAN.md — §0.1 (карта канона: что читать перед чем), §0.2 (транспорт: с движком фронт не разговаривает никогда — это ратифицированный анти-паттерн, D39.81/D39.85), §5.4 и §5.4.1 (формы обхода гейтов и названные границы), §6 (что уже известно про движок и как это меняет форму данных — читать до первой строки типов).
  4. frontend/docs/STACK_DECISIONS.md — §2 пины (там уже названы @tanstack/react-query и msw), §5 транспорт и правила стрима, §7 риск ре-ингеста. Библиотеки сам не выбирай и версии по памяти не «обновляй»: твои знания об экосистеме устарели, сверка — live по npm.
  5. docs/research/23-engine-platform-seam.mddocs/README.md требует читать его перед любым кодом стыка; форма событий и правило ре-синка оттуда.
  6. frontend/docs/BACKLOG.md — строки Ф-14 (вход фронта в контракт), Ф-15 (закрывается этой сессией), Ф-19, Ф-21.
  7. references/*.png и .shots/*.png — открой и посмотри до первой правки.

Правки спеки — ПЕРВЫЙ шаг S3 (список актуализирован оркестратором №14, 05.08)

Контракт ратифицирован. Спеку правишь ты: минорный бамп версии, регенерация типов, батарея, компаньон-К-таблица получает статусы. Ниже — ПОЛНЫЙ список; он вырос с 04.08, потому что платформа ответила на свои К-вопросы и владелец сменил модель лимитов. Порядок правок твой, но ни одна не «на потом»: спека — нормативная поверхность, и на ней уже стоит код платформы.

А. Решения владельца 04.08 (D39.100).

  1. К-1: десять статусов приняты — статусов больше не трогать.
  2. К-8: BookStatus получает 11-е значение paused (резюмируемый стоп; в failed не мапить — спека это уже запрещает). Оповещение «перевод остановлен: лимиты исчерпаны».
  3. К-5: eta_seconds в прогресс-модель (движок отдаёт, status.go:112; поле опциональное).
  4. К-3: метка главы — ИЗ ДАННЫХ книги; зашитых форм «Глава N» не существует, легальна книга без номеров и глав вовсе. Проверь спеку И фикстуры на зашитую форму.

Б. Ратифицированные ответы платформы. Это уже НЕ вопросы — это форма, которую спека обязана выразить.

⚠ Ссылки на platform/docs/* ниже и в разделе Г — происхождение, а не задание. Всё, что тебе нужно, приведено в этом промте целиком. В зону платформы ходить НЕ надо: там прямо сейчас живая сессия, её документы движутся, и промт, который зависит от чужого живого файла, ломается ровно в тот момент, когда сосед его правит. Если всё же полезешь — только читать, и знай, что читаешь черновик.

  1. К-4 (ревизия): счётчик пер-КНИЖНЫЙ, одно и то же число несут все книго-скоупные чтения и id кадра потока. Прозой в спеку: ревизия монотонна В ПРЕДЕЛАХ скоупа и между скоупами не сравнивается · отбрасывание устаревшего чтения — обязанность клиента · докачка по Last-Event-ID читает revision >= R, не > (одна транзакция = одна ревизия, но НЕСКОЛЬКО кадров; строгое «больше» теряет кадры-братья) · после транзакции ПОЛНОЙ ЗАМЕНЫ (банк пересобирается целиком, пере-чанковка заменяет главы) сервер обязан выдать resync_required: дельта-чтение удаления выразить не может.
  2. К-7 (пагинация): keyset-курсор на КАЖДОМ списочном ответе, ?limit=&cursor=, next_cursor: string|null присутствует ВСЕГДА (добавить позже — значит молча отрезать хвост у клиента, который поля не читает). Дефолты: главы 5000 · банк 1000 · замечания 500. ⚠ Курсор привязан к СТРУКТУРНОЙ эпохе (поколение манифеста / chunker_version), НЕ к ревизии книги: ревизия бампается на каждой материализации, и привязка к ней даёт вечный рестарт пагинации во время прогона на книге в 5000 глав. Отклонение протухшего курсора — обязанность СЕРВЕРА (MUST), клиент её исполнить не может: курсор непрозрачен.
  3. К-12 (экспорт): опрос, а не пуш. 202 + Location; статус-чтение отдаёт ready:false с заголовком Retry-After, объявленным ЯВНО как заголовок этого 200-ответа (RFC 9110 определяет его для 503 и 3xx — на 200 общая семантика не действует, поэтому объявляем сами).
  4. CSRF: браузерный клиент обязан слать заголовок X-TM-Client на небезопасных запросах cookie-пути. Место — описание sessionCookie. Несущим является ПРИСУТСТВИЕ заголовка, значение любое: не выдумывай токенную семантику.
  5. Run.paused_reason — машинная причина паузы либо null; фразу рисует клиент, API несёт состояние. Значение ровно одно и оно уже занято платформой: credit_exhausted. (В редакции этого промта от 05.08 стояло limits_exhausted — ошибка оркестратора, значение должно совпадать с тем, что пишет платформа, иначе клиент не опознает причину.)

В. Решение владельца 05.08 — модель лимитов СМЕНИЛАСЬ. Не подписка с окнами, а БАЛАНС кредитов («покупка токенов как у OpenRouter»). Следствия для спеки: 10. Окон и resets_at НЕТ. Экран показывает ОСТАТОК, а не «сбросится через». Денежные СУММЫ по-прежнему на провод не идут (D39.84) — процент остатка, не доллары. 11. Прежняя инструкция этого промта «API лимитов проектирует платформа, в спеку не выдумывать» ОТМЕНЕНА: форма ответила (GET /v0/usage), и её надо выразить — но БЕЗ полей окон. Форма приведена ЗДЕСЬ целиком, ходить за ней в чужую зону не нужно и не надо (промт, зависящий от живого документа соседней зоны, ломается ровно тогда, когда сосед его правит):

```yaml
Usage:
  required: [state, remaining_percent]
  properties:
    state:             { enum: [ok, low, exhausted] }   # low — порог показа предупреждения
    remaining_percent: { type: integer, minimum: 0, maximum: 100 }
    paused_reason:     { enum: [credit_exhausted], nullable: true }
```

Процент считается от суммы грантов аккаунта, не от «лимита периода»: периодов больше нет.
`Run.paused_reason` несёт то же значение на прогоне. Сумм нет ни в каком виде (D39.84).
(В редакции 05.08 этот пункт слал за схемой в `PLATFORM_DIRECTION.md` §2 — там её нет, там
денежная модель; ошибка оркестратора.)

В-бис. Поверхность входа — появилась в P1, в спеке её нет (добавлено оркестратором 05.08 после приёмки платформы P1; в редакции этого промта от 05.08 утра этого пункта не было, потому что кода входа ещё не существовало).

Платформа построила OIDC-вход. Четыре ручки живут ВНЕ версионного префикса, как /healthz, потому что это механика сессии, а не контрактная поверхность:

Ручка Метод Что делает
/auth/login GET начинает вход, редиректит к провайдеру; принимает ?return_to=<путь на этом сайте>
/auth/callback GET завершает вход, ставит сессионную куку, редиректит на return_to либо на дефолт
/auth/logout POST завершает ЭТУ сессию
/auth/logout-all POST завершает ВСЕ сессии пользователя («выйти везде»)

15-бис. Описать их в КОМПАНЬОНЕ, в openapi.yaml не тащить — решение оркестратора как владельца контракта. Клиенту нужно знать три вещи: куда вести на вход, что return_to принимает только путь этого сайта (сервер молча заменит чужой на дефолт — открытый редирект закрыт), и что обе POST-ручки лежат на cookie-пути, то есть требуют X-TM-Client из пункта 8. Отказ входа — problem+json, как везде; различать причины отказа клиент не может и не должен.

Г. Ограничения скорости, которые спека обязана удержать (замер и разбор — platform/docs/PLATFORM_DIRECTION.md §4; для тебя это правила, а не предложения): 12. Юниты читаются только пер-главно. Пер-книжной ручки юнитов не заводить никогда — на этом стоит вся память клиента (рабочий набор 27 КБ против ~62 МБ). 13. Экспорт — артефакт по ссылке; сборка текста книги на клиенте запрещена явно. 14. Сервер ВПРАВЕ склеивать события прогресса, клиент обязан терпеть скачки счётчиков — записать в описания событий (иначе прогон на 9500 юнитов = неограниченный источник рендера). 15. Заголовок главы ограничен по длине на сервере — единственная неограниченная строка в списке из 5000 строк.

Д. После правок: байт-сверка зонной спеки с ратифицированной копией (docs/architecture/14-api-contract/openapi.yaml); расхождение отдай оркестратору на пере-ратификацию диффом — сам канон не правь.

Скоуп S3 — слой данных, и ни шагом дальше

  1. src/api/ становится асинхронным: MSW как сетевой мок + серверное состояние на пинах из STACK_DECISIONS §2 (версии сверить live на дату сессии; .npmrc держит save-exact). Каждый новый пин — строкой в таблицу FRONTEND_PLAN.md с датой релиза и «зачем нам».
  2. Фикстуры всех состояний, а не одного счастливого: ожидание · ошибка · пусто · частично · длинный хвост · потеря соединения на стриме. Правило S1/S2, которое здесь действует сильнее всего: фикстура по умолчанию — трудный случай. Наивный счётчик глав должен на ней давать ноль (идёт черновая волна), иначе скриншот-цикл смотрит на удобное.
  3. Живой прогресс — EventSource за швом src/api/ (STACK_DECISIONS §5; гейт сети уже запрещает его где-либо ещё). Докачка по Last-Event-ID/ревизии — часть контракта, не выдумка.
  4. Ф-15 закрывается здесь: ветки ожидания/ошибки/пустоты появляются вместе с MSW, а не после первого продуктового экрана.
  5. Типы src/api/types.ts приводятся к контракту. Что в них сегодня заведомо временное: булев signed вместо трёхзначного auto|draft|approved · ключ строки банка, собранный из (src, dst, type), — у движка термин уникален по (src, sense, since_ch, until_ch) · отсутствие ревизии для докачки. Всё это перечислено строкой Ф-14. Закрытые юнионы получают явную ветку неизвестного значения (строка Ф-22) — сегодня её нет ни у одного, и строгость тайпчека здесь НЕ страхует: runStates[book.state] (showcase/Library.tsx:103) — обращение к Record по КОНЕЧНОМУ юниону, а noUncheckedIndexedAccess добавляет undefined только индексным сигнатурам (проверено tsc на пробе: обращение по юниону компилируется молча, ругается только Record<string, …>). Значит состояние прогона из будущего контракта даёт undefined.tone — падение дерева библиотеки, а не пустую метку; noteTone тем же классом тихо уводит незнакомую ступень замечания в СПОКОЙНУЮ ветку. Место ветки — шов src/api/, где значение входит, а не каждый компонент: перевод «вердикт → вид» уже расползался по экранам, и контрольный вопрос владельца требует, чтобы новое значение правило один файл.
  6. Поля не выдумывать. Нет в контракте — это вопрос владельцу/оркестратору в «Открытые вопросы» журнала зоны, а не догадка в типе. Дефект «фикстура выдумала поле» в этой зоне случался дважды и оба раза стоил экрана.

Не в скоупе: экраны S4S7 · редактор текста · светлая тема · мобильная раскладка · React Compiler (Ф-2) · Tauri (Ф-5).

Если замок закрыт

Контингенция на случай, если проверка замка даст «закрыт» (на вечер 04.08 замок ОТКРЫТ); тогда правильный ход — не ждать молча и не строить экраны на синхронных моках (порядок этапов Ф-1 стоит именно на слое данных).

  • Доведи Ф-14 до артефакта, который можно отдать автору контракта: не список намерений, а перечень с обоснованием на каждый пункт — язык кодами · состояние прогона отдельно от выполнения главы · прогресс пофазно по ЮНИТАМ · ревизия/Last-Event-ID · трёхзначный статус подписи · поля банка (type/sense/since_ch) и ключ строки · продуктовый словарь вердиктов (ПТ-33; §4.1 запрещает протечку внутренностей конвейера на экран). Половина уже написана в строке Ф-14 и в §6 плана — собери, а не сочиняй заново.
  • Отдай его оркестратору пингом в frontend/docs/frontend-PROGRESS.md и остановись: заводить контракт своим решением фронт не может (шапка BACKLOG.md). ⚠ Испр. сессией S3 04.08 по слову владельца: здесь стояло «пингом в docs/PROGRESS.md» — это чужая зона, и шапка журнала фронта фиксирует решение владельца 02.08 «в docs/PROGRESS.md оркестратора не пишут». Канал передачи — журнал своей зоны; оркестратор пишет туда же.
  • Ф-16, Ф-17, Ф-18, Ф-20 — не трогать: у каждой в бэклоге стоит свой триггер, и ни один из них не наступил.

Ловушки, купленные S2 — не переоткрывать

  • react-aria-components: Tab не принимает клавиатурных обработчиков (обработчик живёт на ряду вкладок) · у роли tab дети презентационные, поэтому крестик закрытия — не кнопка (axe роняет nested-interactive), закрытие с клавиатуры — Delete, объявить его нечем (Ф-17) · невыбранная TabPanel не рендерится вовсе (dist/private/Tabs.mjs:276), отсюда потеря прокрутки при переключении вкладки (Ф-19, замерено дважды) · Tree — это treegrid/row, а не treeitem; раскрытие — <Button slot="chevron"> · renderEmptyState обёрнут в role="option" (зонд S2 принял это за дефект кода).
  • react-resizable-panels v4: className/style панели уезжают во ВЛОЖЕННЫЙ div, у него всегда inline overflow:auto · Separator обязан быть прямым ребёнком Group · ключ хранилища раскладки содержит состав видимых панелей, поэтому сворачивание удалением панели меняет ключ (сделано осознанно: так поле оболочки остаётся ровно замеренным).
  • Скриншот-цикл: vite build и preview в один момент дают пустой экран — это гонка, а не регрессия; перезапусти прогон, прежде чем «чинить» рабочий код.
  • Зонды и живые нарушения: файлы зондов удалять за собой (в S2 агенты ревью оставили свои в чужом дереве), правленые файлы восстанавливать из памяти процесса, а не git checkout — в дереве могут лежать незакоммиченные правки параллельных сессий.
  • Первому красному результату зонда не верить: три «находки» первого прогона S2 оказались дефектами зонда (селектор бил по трём панелям сразу), и соблазн «починить» рабочий код был вполне реальный.
  • esquery: селектор вида Property[key.value=/^--/] > Literal[…] ловит собственный ключ свойства — в TSX-гейте это записано комментарием, не убирай его.

Технические рамки

  • Новый маршрут → сразу в KNOWN_ROUTES (scripts/shot.mjs); тест сверяет списки и упадёт.
  • Гейты не ослаблять. Точечное подавление — только именованное правило с причиной.
  • Файл = компонент + модуль стилей рядом; >150 строк — делить. Комментарии — одна-две строки «почему», не «что».
  • Тексты фикстур — настоящие пропорции кириллицы и иероглифов, объём минимальный: вопрос авторского права (В-2) у владельца, цитаты не расширять.

Как работать

  1. Ревью исполнением — мандат проекта, не пожелание: приложение поднимается, скриншоты сняты и ПРОСМОТРЕНЫ, каждый гейт проверен живым нарушением с перечнем форм, npm run check:full зелёный. «Должно работать» ревью не является.
  2. Скриншот-цикл — с первого изменения, критерий сведения — не растровое совпадение, а плотность и впечатление (стоящий промт §8).
  3. Адверсариальная самопроверка перед финишем (author≠reviewer): пройди по своим правкам с установкой опровергать. S1 так поймала пять дефектов в собственных гейтах, S2 — восемь.
  4. Спорное с каноном или новое продуктовое решение не принимай сам: вопрос в «Открытые вопросы к владельцу» журнала зоны, и продолжай то, что вопроса не требует.

Готово — это когда

  • Замок проверен и результат записан: либо S3 сделана, либо в журнале лежит, чем сессия занималась вместо неё и почему.
  • Если S3 сделана: src/api/ асинхронен, MSW отдаёт все состояния, ветки ожидания/ошибки/пустоты живые, Ф-15 закрыта, типы сведены с контрактом, а расхождения — не «поправим потом», а строки бэклога с триггером.
  • npm run check:full зелёный; новые маршруты в shot.mjs; скриншоты просмотрены.
  • frontend/docs/frontend-PROGRESS.md: обновлено «Текущее состояние», добавлена хроника сессии (что построено · что видно на снимках · где отошёл от канона и почему · что осталось); строки бэклога получили диспозиции.
  • Ничего не закоммичено: дерево подготовлено и передано оркестратору.

Что ждёт не тебя (не решай сам)

В-1 данные привязаны к одной машине (вопрос на движок/платформу) · В-2 копирайт фикстур · В-3/Ф-21 каким словом называть ступень замечания: владелец 05.08 передал разметку оркестратору («пусть работает как работает, потом вернёмся») ⇒ карту «причина → фраза» ты НЕ сочиняешь; до её выдачи держи сегодняшние формулировки и не расширяй словарь · Ф-11 контраст палитры ниже WCAG на четырёх узлах — принято как есть до слова владельца.