31 KiB
Промт: фронт-сессия S3 — слой данных
Ты — фронтенд-сессия TextMachine, четвёртая по счёту. Зона записи — только frontend/;
сессия не коммитит — лендит оркестратор (протокол — стоящий промт FRONTEND_SESSION_PROMPT.md
§«Зона и git», он в силе целиком). Пре-коммит хук стоит и самоустанавливается при npm install.
Промт написан сессией S2 по просьбе владельца — как передача с той точки, где S2 встала. Ратифицирует и правит его оркестратор; при расхождении с его хендофф-промтом побеждает тот.
Входное состояние (04.08, проверено исполнением, а не заявлено)
- Пройдено S0–S2: план · инструменты и скриншот-цикл ·
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 и заводилась.
Обязательное чтение до кода (порядок)
frontend/docs/FRONTEND_SESSION_PROMPT.md— стоящий промт: сценарий §3, жёсткие ограничения §4, «как работать» §7. Его не редактировать. ⚠ Раскладка §2 частично устарела — её перевешивают решения владельца из журнала зоны.frontend/docs/frontend-PROGRESS.mdцеликом: «Текущее состояние» · «Решения владельца по продукту» · «Открытые вопросы» · хронику 04.08 (в ней — что S2 построила, где отошла от референса и что поправило ревью оркестратора).frontend/docs/FRONTEND_PLAN.md— §0.1 (карта канона: что читать перед чем), §0.2 (транспорт: с движком фронт не разговаривает никогда — это ратифицированный анти-паттерн, D39.81/D39.85), §5.4 и §5.4.1 (формы обхода гейтов и названные границы), §6 (что уже известно про движок и как это меняет форму данных — читать до первой строки типов).frontend/docs/STACK_DECISIONS.md— §2 пины (там уже названы@tanstack/react-queryиmsw), §5 транспорт и правила стрима, §7 риск ре-ингеста. Библиотеки сам не выбирай и версии по памяти не «обновляй»: твои знания об экосистеме устарели, сверка — live по npm.docs/research/23-engine-platform-seam.md—docs/README.mdтребует читать его перед любым кодом стыка; форма событий и правило ре-синка оттуда.frontend/docs/BACKLOG.md— строки Ф-14 (вход фронта в контракт), Ф-15 (закрывается этой сессией), Ф-19, Ф-21.references/*.pngи.shots/*.png— открой и посмотри до первой правки.
Правки спеки — ПЕРВЫЙ шаг S3 (список актуализирован оркестратором №14, 05.08)
Контракт ратифицирован. Спеку правишь ты: минорный бамп версии, регенерация типов, батарея, компаньон-К-таблица получает статусы. Ниже — ПОЛНЫЙ список; он вырос с 04.08, потому что платформа ответила на свои К-вопросы и владелец сменил модель лимитов. Порядок правок твой, но ни одна не «на потом»: спека — нормативная поверхность, и на ней уже стоит код платформы.
А. Решения владельца 04.08 (D39.100).
- К-1: десять статусов приняты — статусов больше не трогать.
- К-8:
BookStatusполучает 11-е значениеpaused(резюмируемый стоп; вfailedне мапить — спека это уже запрещает). Оповещение «перевод остановлен: лимиты исчерпаны». - К-5:
eta_secondsв прогресс-модель (движок отдаёт,status.go:112; поле опциональное). - К-3: метка главы — ИЗ ДАННЫХ книги; зашитых форм «Глава N» не существует, легальна книга без номеров и глав вовсе. Проверь спеку И фикстуры на зашитую форму.
Б. Ратифицированные ответы платформы. Это уже НЕ вопросы — это форма, которую спека обязана выразить.
⚠ Ссылки на
platform/docs/*ниже и в разделе Г — происхождение, а не задание. Всё, что тебе нужно, приведено в этом промте целиком. В зону платформы ходить НЕ надо: там прямо сейчас живая сессия, её документы движутся, и промт, который зависит от чужого живого файла, ломается ровно в тот момент, когда сосед его правит. Если всё же полезешь — только читать, и знай, что читаешь черновик.
- К-4 (ревизия): счётчик пер-КНИЖНЫЙ, одно и то же число несут все книго-скоупные чтения и
idкадра потока. Прозой в спеку: ревизия монотонна В ПРЕДЕЛАХ скоупа и между скоупами не сравнивается · отбрасывание устаревшего чтения — обязанность клиента · докачка поLast-Event-IDчитаетrevision >= R, не>(одна транзакция = одна ревизия, но НЕСКОЛЬКО кадров; строгое «больше» теряет кадры-братья) · после транзакции ПОЛНОЙ ЗАМЕНЫ (банк пересобирается целиком, пере-чанковка заменяет главы) сервер обязан выдатьresync_required: дельта-чтение удаления выразить не может. - К-7 (пагинация): keyset-курсор на КАЖДОМ списочном ответе,
?limit=&cursor=,next_cursor: string|nullприсутствует ВСЕГДА (добавить позже — значит молча отрезать хвост у клиента, который поля не читает). Дефолты: главы 5000 · банк 1000 · замечания 500. ⚠ Курсор привязан к СТРУКТУРНОЙ эпохе (поколение манифеста /chunker_version), НЕ к ревизии книги: ревизия бампается на каждой материализации, и привязка к ней даёт вечный рестарт пагинации во время прогона на книге в 5000 глав. Отклонение протухшего курсора — обязанность СЕРВЕРА (MUST), клиент её исполнить не может: курсор непрозрачен. - К-12 (экспорт): опрос, а не пуш.
202+Location; статус-чтение отдаётready:falseс заголовкомRetry-After, объявленным ЯВНО как заголовок этого 200-ответа (RFC 9110 определяет его для 503 и 3xx — на 200 общая семантика не действует, поэтому объявляем сами). - CSRF: браузерный клиент обязан слать заголовок
X-TM-Clientна небезопасных запросах cookie-пути. Место — описаниеsessionCookie. Несущим является ПРИСУТСТВИЕ заголовка, значение любое: не выдумывай токенную семантику. 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 — слой данных, и ни шагом дальше
src/api/становится асинхронным: MSW как сетевой мок + серверное состояние на пинах из STACK_DECISIONS §2 (версии сверить live на дату сессии;.npmrcдержитsave-exact). Каждый новый пин — строкой в таблицуFRONTEND_PLAN.mdс датой релиза и «зачем нам».- Фикстуры всех состояний, а не одного счастливого: ожидание · ошибка · пусто · частично · длинный хвост · потеря соединения на стриме. Правило S1/S2, которое здесь действует сильнее всего: фикстура по умолчанию — трудный случай. Наивный счётчик глав должен на ней давать ноль (идёт черновая волна), иначе скриншот-цикл смотрит на удобное.
- Живой прогресс —
EventSourceза швомsrc/api/(STACK_DECISIONS §5; гейт сети уже запрещает его где-либо ещё). Докачка поLast-Event-ID/ревизии — часть контракта, не выдумка. - Ф-15 закрывается здесь: ветки ожидания/ошибки/пустоты появляются вместе с MSW, а не после первого продуктового экрана.
- Типы
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/, где значение входит, а не каждый компонент: перевод «вердикт → вид» уже расползался по экранам, и контрольный вопрос владельца требует, чтобы новое значение правило один файл. - Поля не выдумывать. Нет в контракте — это вопрос владельцу/оркестратору в «Открытые вопросы» журнала зоны, а не догадка в типе. Дефект «фикстура выдумала поле» в этой зоне случался дважды и оба раза стоил экрана.
Не в скоупе: экраны S4–S7 · редактор текста · светлая тема · мобильная раскладка · 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-panelsv4:className/styleпанели уезжают во ВЛОЖЕННЫЙ div, у него всегда inlineoverflow:auto·Separatorобязан быть прямым ребёнкомGroup· ключ хранилища раскладки содержит состав видимых панелей, поэтому сворачивание удалением панели меняет ключ (сделано осознанно: так поле оболочки остаётся ровно замеренным).- Скриншот-цикл:
vite buildиpreviewв один момент дают пустой экран — это гонка, а не регрессия; перезапусти прогон, прежде чем «чинить» рабочий код. - Зонды и живые нарушения: файлы зондов удалять за собой (в S2 агенты ревью оставили свои
в чужом дереве), правленые файлы восстанавливать из памяти процесса, а не
git checkout— в дереве могут лежать незакоммиченные правки параллельных сессий. - Первому красному результату зонда не верить: три «находки» первого прогона S2 оказались дефектами зонда (селектор бил по трём панелям сразу), и соблазн «починить» рабочий код был вполне реальный.
- esquery: селектор вида
Property[key.value=/^--/] > Literal[…]ловит собственный ключ свойства — в TSX-гейте это записано комментарием, не убирай его.
Технические рамки
- Новый маршрут → сразу в
KNOWN_ROUTES(scripts/shot.mjs); тест сверяет списки и упадёт. - Гейты не ослаблять. Точечное подавление — только именованное правило с причиной.
- Файл = компонент + модуль стилей рядом; >150 строк — делить. Комментарии — одна-две строки «почему», не «что».
- Тексты фикстур — настоящие пропорции кириллицы и иероглифов, объём минимальный: вопрос авторского права (В-2) у владельца, цитаты не расширять.
Как работать
- Ревью исполнением — мандат проекта, не пожелание: приложение поднимается, скриншоты
сняты и ПРОСМОТРЕНЫ, каждый гейт проверен живым нарушением с перечнем форм,
npm run check:fullзелёный. «Должно работать» ревью не является. - Скриншот-цикл — с первого изменения, критерий сведения — не растровое совпадение, а плотность и впечатление (стоящий промт §8).
- Адверсариальная самопроверка перед финишем (author≠reviewer): пройди по своим правкам с установкой опровергать. S1 так поймала пять дефектов в собственных гейтах, S2 — восемь.
- Спорное с каноном или новое продуктовое решение не принимай сам: вопрос в «Открытые вопросы к владельцу» журнала зоны, и продолжай то, что вопроса не требует.
Готово — это когда
- Замок проверен и результат записан: либо 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 на четырёх узлах — принято как есть до слова владельца.