textmachine/frontend/docs/S3_SESSION_PROMPT.md

328 lines
36 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 — слой данных
Ты — фронтенд-сессия 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.md``docs/README.md` требует читать его **перед любым
кодом стыка**; форма событий и правило ре-синка оттуда.
6. `frontend/docs/BACKLOG.md` — строки **Ф-14** (вход фронта в контракт), **Ф-15** (закрывается
этой сессией), Ф-19, Ф-21.
7. `references/*.png` и `.shots/*.png` — открой и посмотри до первой правки.
## Правки спеки — ПЕРВЫЙ шаг S3 (список актуализирован оркестратором №15, 07.08: добавлены В-трис и В-кватер)
Контракт ратифицирован. Спеку правишь ты: минорный бамп версии, регенерация типов, батарея,
компаньон-К-таблица получает статусы. **Ниже — ПОЛНЫЙ список; он вырос с 04.08**, потому что
платформа ответила на свои К-вопросы и владелец сменил модель лимитов. Порядок правок твой,
но ни одна не «на потом»: спека — нормативная поверхность, и на ней уже стоит код платформы.
**А-нуль. Шапка `info` спеки уже исправлена оркестратором №15 (07.08) — НЕ возвращай её обратно.**
Три дня после ратификации файл называл себя «DRAFT of frontend session S3 — not a ratified
contract» и ссылался на компаньон по пути, которого рядом с каноном нет. Сейчас там сказана
правда: ратифицирован D39.99, канон в `docs/architecture/14-api-contract/`, зонная копия —
байт-зеркало, компаньон = `README.md` рядом с каноном. **Твоё в этом пункте одно:** строка
`version` всё ещё `0.1.0-draft` — бампни её первым же действием (минорный бамп, как и сказано
ниже), и байт-сверку копий из пункта Д делай уже с этой шапкой.
**А. Решения владельца 04.08 (D39.100).**
1. **К-1:** десять статусов приняты — статусов больше не трогать.
2. **К-8:** `BookStatus` получает 11-е значение **`paused`** (резюмируемый стоп; в `failed` не
мапить — спека это уже запрещает). Оповещение «перевод остановлен: лимиты исчерпаны».
3. **К-5:** `eta_seconds` в прогресс-модель (движок отдаёт, `status.go:112`; поле опциональное).
4. **К-3:** метка главы — ИЗ ДАННЫХ книги; зашитых форм «Глава N» не существует, легальна книга
без номеров и глав вовсе. Проверь спеку И фикстуры на зашитую форму.
**Б. Ратифицированные ответы платформы. Это уже НЕ вопросы — это форма, которую спека обязана
выразить.**
> ⚠ Ссылки на `platform/docs/*` ниже и в разделе Г — **происхождение, а не задание.** Всё, что тебе
> нужно, приведено в этом промте целиком. В зону платформы ходить НЕ надо: там прямо сейчас живая
> сессия, её документы движутся, и промт, который зависит от чужого живого файла, ломается ровно в
> тот момент, когда сосед его правит. Если всё же полезешь — только читать, и знай, что читаешь
> черновик.
5. **К-4 (ревизия):** счётчик пер-КНИЖНЫЙ, одно и то же число несут все книго-скоупные чтения и
`id` кадра потока. Прозой в спеку: ревизия монотонна В ПРЕДЕЛАХ скоупа и между скоупами не
сравнивается · отбрасывание устаревшего чтения — обязанность клиента · докачка по
`Last-Event-ID` читает **`revision >= R`**, не `>` (одна транзакция = одна ревизия, но
НЕСКОЛЬКО кадров; строгое «больше» теряет кадры-братья) · после транзакции ПОЛНОЙ ЗАМЕНЫ
(банк пересобирается целиком, пере-чанковка заменяет главы) сервер обязан выдать
`resync_required`: дельта-чтение удаления выразить не может.
6. **К-7 (пагинация):** keyset-курсор на КАЖДОМ списочном ответе, `?limit=&cursor=`,
`next_cursor: string|null` присутствует ВСЕГДА (добавить позже — значит молча отрезать хвост
у клиента, который поля не читает). Дефолты: главы 5000 · банк 1000 · замечания 500.
Курсор привязан к СТРУКТУРНОЙ эпохе (поколение манифеста / `chunker_version`), **НЕ к
ревизии книги**: ревизия бампается на каждой материализации, и привязка к ней даёт вечный
рестарт пагинации во время прогона на книге в 5000 глав. Отклонение протухшего курсора —
обязанность СЕРВЕРА (MUST), клиент её исполнить не может: курсор непрозрачен.
7. **К-12 (экспорт):** опрос, а не пуш. `202` + `Location`; статус-чтение отдаёт `ready:false`
с заголовком **`Retry-After`**, объявленным ЯВНО как заголовок этого 200-ответа (RFC 9110
определяет его для 503 и 3xx — на 200 общая семантика не действует, поэтому объявляем сами).
8. **CSRF:** браузерный клиент обязан слать заголовок **`X-TM-Client`** на небезопасных запросах
cookie-пути. Место — описание `sessionCookie`. Несущим является ПРИСУТСТВИЕ заголовка,
значение любое: не выдумывай токенную семантику.
9. **`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 строк.
**В-трис. Управляемый потолок прогона — РЕШЕНИЕ ВЛАДЕЛЬЦА 07.08, поверхности в спеке нет**
(добавлено оркестратором №15 по приёмке платформы P2, D39.109/D39.110; в редакции 05.08 этого
пункта не было — решения не существовало).
Проблема, которую это решает: перевод книги стоит денег, и человек должен уметь сказать «не
больше вот столько» ДО старта, а не узнавать пост-фактум. Сегодня потолок живёт только в конфиге
книги на стороне движка, и управлять им нельзя вообще.
Форма, ратифицированная владельцем: **шкала в интерфейсе от минимума до максимума**, ноль выбрать
НЕЛЬЗЯ (подсвечивается серым — прогон с нулевым потолком не начнётся), виджет выбираешь сам
(ползунок — предложение владельца, не мандат). Три вещи в этой форме — «делай РОВНО так», потому
что каждая куплена проверкой:
16. **Единица шкалы — ГЛАВЫ, а не доллары.** Не «поставить $3», а «перевести до N глав». Причина
жёсткая: D39.84 запрещает денежные суммы на пользовательском проводе и экране, и П-5 показывает
процент ровно поэтому. Доллары на этой шкале = амендмент D39.84, а он не выдан. Побочная
выгода: минимум шкалы объясняет себя сам — одна глава.
17. **Максимум шкалы = ДОСТУПНЫЙ остаток, то есть баланс МИНУС открытые холды** — не весь баланс.
Платформа берёт холд на сумму потолка ДО спавна прогона, поэтому шкала, упёртая в полный
баланс, запирает всё на одну книгу и вторую начать нельзя. Два числа платформа уже различает.
18. **Это потолок ПРОГОНА, не книги.** Он едет со стартом прогона и не является свойством книги;
в спеке ему место рядом с созданием прогона, а не в модели книги.
⚠ Чего в спеке НЕ выражать: пересчёт «глав → деньги». Он живёт на платформе (по оценке движка) и
наружу не выходит ни в каком виде. Поверхность отдаёт границы шкалы и выбранное значение в главах.
**В-кватер. ФРОНТ ЖИВЁТ НА ТОМ ЖЕ ORIGIN, что платформа — сегодня это не выбор, а факт**
(замер приёмки №15 на живом бинаре, строка PD-96 регистра платформы).
CORS-слоя в платформе нет вовсе: preflight `OPTIONS` с чужим `Origin` получает 401 от гарда
сессии, заголовков `Access-Control-*` нет ни на одном ответе. Значит браузерный клиент с другого
origin неработоспособен как класс, а не «требует настройки». Для тебя два следствия: в деве ходи
через прокси dev-сервера на тот же origin (не напрямую на порт платформы), и **не проектируй ничего
вокруг кросс-origin запросов** — если продукт когда-нибудь потребует отдельный домен фронта, это
отдельная работа платформы, и она не сделана. Заголовок `X-TM-Client` (пункт 8) при этом обязателен
и на same-origin: он не про CORS.
**Д. После правок:** байт-сверка зонной спеки с ратифицированной копией
(`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 на четырёх узлах — принято как есть до слова владельца.