diff --git a/frontend/README.md b/frontend/README.md index 6e52ee5a..1f298d3d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,11 +1,8 @@ # frontend — веб-интерфейс -Зона записи сессий «Фронт». Пройдены S0 (план), S1 (инструменты, скриншот-цикл, токены, витрина), -S2 (оболочка трёх панелей и слой `src/ui/`), S3 (слой данных на контракте API), S3.5 (фикс-пак -оболочки по 24 замечаниям владельца), S3.6 (второй круг: палитра по замеру референсов, модель -вкладок, банк таблицей) и S3.7 (долги слоя данных Ф-49…Ф-51, `sense` обязателен в контракте 0.2.2, -строки интерфейса в каталог и гейт против хардкода, английский язык кода). Продуктовых экранов -ещё нет — они на S4–S7 (`docs/BACKLOG.md`, Ф-1). +Зона записи сессий «Фронт». **Статус этапов, что построено и решения владельца по продукту — +`docs/frontend-PROGRESS.md`** (единственный носитель; здесь его копии намеренно нет, она отставала). +Что впереди — `docs/BACKLOG.md`, Ф-1. ## Как запустить @@ -18,9 +15,10 @@ npm run shot # снимки в .shots/ — открыть и посмо npm run scenes # сценарии интеракций: клик → кадр → проверка состояния ``` -Маршрутов семь, и все снимаются (`/showcase` `/scale` `/empty` `/loading` `/error` `/offline` -`/partial`) — маршрут выбирает мир фикстур. Новый экран заводится вместе с маршрутом и строкой -в `scripts/shot.mjs` — расхождение двух списков падает тестом. +Маршрут выбирает мир фикстур, и снимаются все. Список маршрутов выводится из +`src/api/scenarios.ts` (`src/routes.tsx`), второй его экземпляр держит `scripts/shot.mjs`, +и расхождение двух списков падает тестом `src/routes.test.ts`. Прозаической копии списка здесь +нет намеренно: она отставала на этап, а два списка кода замкнуты друг на друга. Два ключа снимка стоит знать: `--size 1280x764` даёт вьюпорт референса для прямого наложения, `--dpr 1` снимает то, что видит владелец на своём мониторе (при дефолтных 2x полупиксель CSS @@ -45,13 +43,14 @@ npm run scenes # сценарии интеракций: клик → ка ## Документы -- [`docs/FRONTEND_SESSION_PROMPT.md`](docs/FRONTEND_SESSION_PROMPT.md) — промт фронт-сессий: - область этапов, референсы с измеренными значениями, экраны, ограничения, порядок работы. +- [`docs/FRONTEND_SESSION_PROMPT.md`](docs/FRONTEND_SESSION_PROMPT.md) — нормы зоны (не задание): + референсы с измеренными значениями, продуктовый сценарий экранов, жёсткие ограничения + владельца, порядок работы и критерий сравнения с референсом. - [`docs/STACK_DECISIONS.md`](docs/STACK_DECISIONS.md) — пины версий, ловушки и список того, что достраивается в движке. Источник: многоагентное исследование 02.08.2026. -- [`docs/FRONTEND_PLAN.md`](docs/FRONTEND_PLAN.md) — план S0: сверенные пины, карта `src/`, - слои каскада, правила поддерживаемости в проверяемой форме, протокол скриншот-цикла, - перепроверенные замеры референса и список расхождений витрины. +- [`docs/FRONTEND_PLAN.md`](docs/FRONTEND_PLAN.md) — как пишется фронт: сверенные пины, карта + `src/`, слои каскада, правила поддерживаемости в проверяемой форме, протокол скриншот-цикла, + перепроверенные замеры референса и границы гейтов. - [`docs/frontend-PROGRESS.md`](docs/frontend-PROGRESS.md) — журнал зоны: текущее состояние, решения владельца по продукту, открытые вопросы, хроника сессий. Имя с префиксом `frontend-` намеренно: журнал оркестратора называется `docs/PROGRESS.md`, и одноимённые файлы путали @@ -65,10 +64,9 @@ npm run scenes # сценарии интеракций: клик → ка генерится `src/api/schema.ts`. ⚠ **Не черновик** (исправлено S4 14.08): контракт ратифицирован D39.99, эта копия — байт-зеркало канона `docs/architecture/14-api-contract/` ⚠ **С ФРИЗА ЗОНЫ ОТСТАЁТ РАТИФИЦИРОВАННО (D39.142 п.5): нормативен ТОЛЬКО канон (0.9.0), зеркало стоит на 0.2.3 — байт-равенства сегодня НЕТ. Синк `cmp` + перегенерация типов — первое касание зоны при разморозке.**; правит её фронт-сессия, переносит в канон диффом оркестратор при лендинге (прецедент Ф-47/0.2.2). -- [`docs/API_CONTRACT_DRAFT.md`](docs/API_CONTRACT_DRAFT.md) — спутник спеки: откуда взято - каждое решение (выведено из кода движка · предложено фронтом · открыто), обоснования, - зависимости и двенадцать открытых вопросов. При расхождении по форме побеждает YAML, - при вопросе «почему так» — этот файл. +- [`docs/API_CONTRACT_DRAFT.md`](docs/API_CONTRACT_DRAFT.md) — ⚠ **исполнено:** зонный указатель + на канон спутника `docs/architecture/14-api-contract/README.md`. Провенанс каждого решения, + обоснования, зависимости и К-вопросы живут ТАМ; при расхождении по форме побеждает спека. - `references/` — скриншоты-референсы (Fleet, Antigravity), в git не едут: кладёт владелец. ## Язык кода и язык интерфейса — разные вещи @@ -76,7 +74,7 @@ npm run scenes # сценарии интеракций: клик → ка Исходники зоны **английские**: комментарии, имена тестов, сообщения ошибок и логи скриптов. Русский в `src/` живёт ровно в двух местах, и оба — данные: каталог сообщений `src/i18n/ru.ts` (слова интерфейса, переводится файлом) и фикстуры `src/mock/` (проза книги, -которая стоит вместо настоящей). Держат это два гейта, и правила у них РАЗНЫЕ: ESLint запрещает текст в разметке МЕСТОМ, а не алфавитом (буква в `JSXText` или в читаемом атрибуте — ловится и русский литерал, и английский), а `src/i18n/catalogue.test.ts` подметает то, чего AST не видит, — комментарии, CSS, python-скрипты и корневые конфиги зоны. +которая стоит вместо настоящей). Держат это два гейта с РАЗНЫМИ правилами: ESLint ловит текст в разметке по МЕСТУ (а не по алфавиту), `src/i18n/catalogue.test.ts` — то, чего AST не видит: комментарии, CSS, python-скрипты и корневые конфиги зоны. Почему именно так — `docs/STACK_DECISIONS.md` §1. ## Источник внешнего вида — один diff --git a/frontend/docs/API_CONTRACT_DRAFT.md b/frontend/docs/API_CONTRACT_DRAFT.md index 77e70a08..1c301985 100644 --- a/frontend/docs/API_CONTRACT_DRAFT.md +++ b/frontend/docs/API_CONTRACT_DRAFT.md @@ -1,336 +1,29 @@ # ⚠ ИСПОЛНЕНО. Канон компаньона живёт не здесь -> **Не читать как действующий документ и не править.** Этот файл — черновик спутника, написанный -> фронт-сессией S3 04.08 и с тех пор **ратифицированный и перенесённый**. Канон компаньона — +> **Не читать как действующий документ и не править.** Этот файл был черновиком спутника спеки +> (фронт-сессия S3, 04.08); с тех пор он **ратифицирован и перенесён**. Канон компаньона — > [`../../docs/architecture/14-api-contract/README.md`](../../docs/architecture/14-api-contract/README.md), -> рядом с канонической спекой; там же живёт ревью-шапка ратификации D39.99 и К-таблица. +> рядом с канонической спекой; там же ревью-шапка ратификации D39.99 и К-таблица. > -> Баннер поставлен фронт-сессией S3 08.08 по указанию оркестратора №15: два экземпляра одного -> состояния расходятся при первой же правке, и расходится всегда тот, который забыли. **Один факт — -> один носитель.** Ниже — историческое тело; ссылаться на него можно, поддерживать — нет. -> -> Куда смотреть вместо него: +> Баннер поставлен по указанию оркестратора №15 (08.08): два экземпляра одного состояния +> расходятся при первой же правке, и расходится всегда тот, который забыли. **Один факт — +> один носитель.** > > | Что нужно | Где это теперь | > |---|---| -> | норматив контракта (ФОРМА) | `docs/architecture/14-api-contract/openapi.yaml`; зонное байт-зеркало — `api-contract/openapi.yaml` | +> | норматив контракта (ФОРМА) | канон `docs/architecture/14-api-contract/openapi.yaml`. ⚠ Зонное зеркало `api-contract/openapi.yaml` с фриза зоны отстаёт РАТИФИЦИРОВАННО (D39.142 п.5): нормативен только канон | > | провенанс, обоснования, К-вопросы | `docs/architecture/14-api-contract/README.md` | -> | правки, которые ждут переноса в канон | канон `docs/architecture/14-api-contract/` (патч ПРИМЕНЁН и удалён, D39.115) — живёт до лендинга S3 и удаляется | -## Историческое тело (04.08, не поддерживается) +**Тело черновика снято 02.09** и здесь не воспроизводится. Причина не в объёме: канон несёт те же +разделы под ТЕМИ ЖЕ номерами (§0 пометки провенанса · §1 почему YAML · §2.1–2.13 решения и их +происхождение · §3 зависимости · §4 К-вопросы · §5 ревью-вопрос · приложение А), и с тех пор он +ушёл вперёд на редакции 0.3.0…0.9.0 — то есть ссылка вида «§2.7 спутника» резолвится в каноне +и отдаёт ДЕЙСТВУЮЩУЮ редакцию, а не редакцию 0.2.x. -> **Нормативная поверхность контракта — [`api-contract/openapi.yaml`](api-contract/openapi.yaml)** -> (OpenAPI 3.1). Этот файл её НЕ дублирует: он несёт то, чего YAML не выражает — откуда взято -> каждое решение, чем оно обосновано, что осталось открытым. При расхождении по ФОРМЕ -> побеждает YAML; при вопросе «почему так» — этот файл. -> -> **Статус: черновик фронт-сессии S3 на ратификацию.** Дом ратифицированной копии — -> `docs/architecture/14-api-contract/`, зона оркестратора; перенос делает он, генерация типов -> после ратификации идёт из перенесённой копии — контракт первичен, код вторичен. -> -> **Язык.** Спека английская: из неё генерятся типы, а исходники фронта по конвенции -> английские (слово владельца 04.08). Ссылки на К-вопросы внутри YAML набраны латинской -> `K-N` — это те же вопросы §4. Спутник и остальные доки зоны — русские. -> -> **Зона строки 95 — «оркестратор/бэкенд/фронт».** Фронт авторитетен в одной трети: форма -> read-модели и продуктовые словари. Транспорт платформы (пути, аутентификация, коды) и -> работы движка (99–103) здесь ПРЕДЛОЖЕНЫ и без подтверждения своих зон не действуют. +⚠ Держать тело рядом было не просто дублем, а активной ловушкой: канон прямо объявляет прежний +ответ §5 **опровергнутым исполнением** (research/28 Б-0, разбор — D39.138) и не сохраняет его +даже как историю, «потому что он учил зону неправде»; §2.5 (прогресс), §2.6 (`finalizing`), +§2.8 (слова банка) и §2.11 (канал потока) с тех пор пере-решены. Черновик 0.2.x отвечал бы на эти +вопросы устаревшим способом молча. -## 0. Пометки провенанса - -| Пометка | Что значит | -|---|---| -| **✓ выведено** | следует из кода движка или ратифицированного решения; грунт `file:line` рядом | -| **◆ предложено** | решение фронта, разумное по его сведениям; подтверждает названная зона | -| **○ открыто** | развилка, на которую у фронта ответа нет; перечень — §4 | - -⚠ Пометка ставится **на утверждение, а не на раздел**: у одного пункта половина бывает -выведенной, а половина предложенной. Первая редакция черновика этим и грешила — восемь мест -несли ✓ там, где верно было ◆; ниже разведено. - ---- - -## 1. Почему YAML, а не проза - -Контракт — машинный артефакт: из него генерируются типы, по нему линтуется форма, им -типизируются моки. Прозаический контракт расходится с кодом ровно тем способом, ради -предотвращения которого заведена строка 95. - -Инструменты, пины и отклонение по пиру TS не дублирую: они в `STACK_DECISIONS.md` §3 и в -бэклоге зоны — Ф-23 (`overrides` вместо `--legacy-peer-deps`), Ф-24 (AsyncAPI отложен с -причиной). Четыре формы нарушения гейта, каждая проверена живьём, — `FRONTEND_PLAN.md` §5.4.2. - ---- - -## 2. Решения и их происхождение - -### 2.1. Язык — код, никогда не имя — ✓ выведено - -Движок держит коды (`backend/internal/config/book.go:26-27`), ключ пары — `zh-ru` -(`configs/langpacks/zh-ru/`). Имя языка в данных — пар-специфика в общем слое, запрещённая -§2 канона. Вторая цена, дороже: `lang` элемента берётся из данных книги, и пара ja→ru с именем -вместо кода отрисует кандзи китайскими начертаниями молча. - -### 2.2. Идентификаторы непрозрачны — ✓ выведено - -`glossary.id` — свежий автоинкремент на каждой пересборке банка и намеренно не хешируется -(`store/migrate.go:173-174`); номер главы плотный, «Chapters that yield no text … do NOT -consume a chapter number» (`chunk/chunker.go:99-105`), поэтому правка исходника сдвигает -номера последующих глав. Стабильность обеспечивает платформа при персисте манифеста (100). - -### 2.3. Заголовок главы отдельным полем — ◆ предложено - -**Движок сегодня делает ОБРАТНОЕ**, и это надо назвать прямо: титул рендерится -детерминистически из шаблона пары (`configs/langpacks/zh-ru/heading.txt`: `template Глава {n}`), -исходный маркер вырезается из текста для модели (`chunk/chunker.go:110-114`), а на экспорте -титул **вклеивается внутрь текста первого юнита** (`pipeline/export.go:215`), причём колонка -исходника остаётся без него. - -Выведена здесь только МЕХАНИКА. Само поле `heading` — предложение фронта, и у него есть цена -на другой стороне: движку придётся отдавать титул отдельно. Альтернатива (оставить вклейку, -фронт отрезает строку) хуже: отрезание титула из текста — это парсинг прозы, и он сломается -на первой главе без заголовка. Развилка — К-2. - -**Расхождение фикстуры, найденное разбором:** дерево витрины показывает «Раздел 2. …», колонка -оригинала — неснятый «第二节:». Для zh→ru движок не порождает ни одной из форм. Не чинится -до ответа на К-2. - -### 2.4. Состояние — у прогона; у главы выполнение — ✓ выведено - -Подпись банка это один стоп на всю книгу (`pipeline/mining.go:201`), поэтому «глава ждёт -подписи, пока соседняя финализируется» — невозможная картина. У главы движок держит -`ChapterPassport` (`pipeline/status.go:37-55`). - -### 2.5. Прогресс пофазно и в юнитах — ✓ выведено - -«A unit is DONE when every member draft AND the unit's edit resolved ok» -(`pipeline/status.go:328-331`), редактура не стартует до стопа банка ⇒ сквозной счётчик стоит -на нуле всю черновую волну. Зависимость — строка 99. - -### 2.6. Словарь статусов — ✓ лестница, ◆ ненормальные исходы - -Лестница дословно из строки 95: «загрузка → разбор → перевод → подпись банка → финал → -готово». **`not_started` — дыра, найденная самопроверкой черновика:** лестница описывает -идущий прогон, а библиотека обязана показывать разобранную книгу, которую не запускали. -`stopped`, `rejected`, `not_started` контракт ВЫВОДИТ из поведения процесса, а не получает -полем: механика стопа у движка есть (`cmd/tmctl/main.go:63`), но «кто нажал» знает платформа. - -**Стоп по потолку — не `failed`** ✓ выведено: «Ceiling is a hard, book-wide stop (not a -per-chunk flag): the job stays 'pending' and resume continues once the ceiling is raised» -(`pipeline/stagerun.go:488-489`). Мапить его в `failed` запрещено — это соврало бы про -резюмируемость. Каким статусом и словом он показывается — К-8, вопрос владельцу. - -### 2.7. Состояние пары выводится из ПАРЫ — ✓ выведено (исправление первой редакции) - -Первая редакция утверждала «`withheld` = текст не выдан» как факт о движке. **Это было -ложно:** флагнутый юнит легально приходит С ТЕКСТОМ в двух случаях — - -- косметическая зачистка санитайзера: «the chunk is NOT lost — the cleaned text is committed - as the export» (`pipeline/disposition.go:96-104`); -- c-lite member-drop: редактор отгружает отредактированный чистый остаток, а юнит флагнут - из-за выпавшего члена (`pipeline/export.go:203-207`). - -Поэтому состояние выводится из ПАРЫ (вердикт, наличие финального текста): флаг+текст → -`translated` с замечанием; флаг+пусто → `withheld`. - -**Причина флага при этом ПРОИЗВОЛЬНА, и «единственный легальный случай» — снято** (ревью -оркестратора, round-2, пункт 4; утверждение противоречило выводу строкой выше). При c-lite drop -юнит несёт `FlagReason` ПЕРВОГО выпавшего члена, каким бы он ни был (`export.go:203-207`: -`ce.FlagReason = drops[0].Reason`, и тут же `ce.FinalText … still ships`). Значит «текст + -замечание» — это класс, а не один случай, и карта вердиктов обязана иметь фразу для каждой -причины, а не для двух. - -**Но `glossary_miss` в этот класс НЕ входит, и это проверено отдельно** (иначе правка выше -воскресила бы невоспроизводимый пример). `memberDrops` берёт причину из ЧЕРНОВОЙ строки члена -(`status.go:242-257`: `draftStages[cs.Stage] && flagged`), а `glossary_miss` ставится -пост-чеком только там, где отгружается финал: в черновой волне — лишь когда она сама финальная -(`waverun.go:373-381`, draft-only), в c-lite — на строке РЕДАКТУРЫ (`waverun.go:494`). В -пайплайне, где текст отгружает редактура (то есть где c-lite drop вообще возможен), черновая -строка `glossary_miss` нести не может. При включённом гейте текст удерживается целиком -(`export.go:295-300`), при выключенном мисса нет вовсе. **Итог: пара «текст + промах словаря» -невозможна ни одним каналом — фикстура витрины, показывавшая её, переведена на c-lite drop.** - -**Свежесть** ✓ выведено: `target` обновляется на границах стадий и на стопах, а не -непрерывно — посреди прогона канала чтения не существует (эксклюзивный лок движка; -санкционированное чтение — завершённый либо остановленный прогон, `research/23` §0, §4). - -### 2.8. Банк: словари ✓, имена ◆, `kind` ◆ с дырой - -Значения выведены из схемы и гейтов Go; **имена полей контракта — предложение фронта.** - -| Поле | Словарь | Грунт | -|---|---|---| -| `status` | `auto · draft · approved` | `store/migrate.go:191`; только `approved` — канон | -| `kind` ◆ | `name · place · title · term · nickname` **плюс отсутствие значения** | `terminology/classify.go:15` + `pipeline/banknote.go:74`; пустое — `membank/memseed.go:323-326` | -| `origin` | `seed · ruby · mined` | пути записи, см. ниже | -| `sense` | свободный текст, ОБЯЗАТЕЛЕН с 0.2.2; пустая строка = «различителя нет» (Ф-47, ратифицировано оркестратором, внесено S3.7) | `store/migrate.go:182` | -| `since_chapter`/`until_chapter` | целые, `0` = без границы | `store/migrate.go:189-190` | - -**Фантом `auto` в провенансе убран.** Первая редакция взяла словарь из комментария схемы -(`migrate.go:193`: `seed|ruby|auto`) — комментарий устарел. По путям записи `"auto"` пишет -**статус**, не провенанс (`membank/memseed.go:328`: `Status:"auto", Source:"ruby"`), а майнинг -ставит `mined` (`pipeline/mining.go:424`). Правка комментария в движке — за оркестратором. - -**`kind` пере-размечен ✓→◆, и вот почему это не косметика** (ревью round-2, пункт 3). Словарь -из пяти значений выведен верно, но ЗАКРЫТЫМ и обязательным он делает нелегальной легальную -строку: ruby-кандидат получает `Type: ""`, если его класс не `name` — то есть gloss и -ambiguous живут без типа по построению (`membank/memseed.go:323-326`: `typ := ""`, и только -`class == rubyClassName` даёт `"name"`). Материализатору read-модели такую строку было -физически нечем заполнить. **Правило пустого:** `kind` присутствует всегда и допускает `null`; -`null` значит «движок не решил», строка при этом остаётся подписываемой, и клиенту запрещено -и выбрасывать её, и додумывать тип за движок. Проекция `""` → `null` — работа платформы. - -**Ложный друг устранён.** У движка колонка `source` — это ПРОВЕНАНС. Первая редакция назвала -провенанс `origin`, а имя `source` отдала ДРУГОЙ колонке (тексту термина) — то есть завела -между схемами ложного друга. Теперь: провенанс `origin`, формы термина `src`/`dst`, как их -зовёт сам движок; имя `source` в схеме банка не используется вовсе. - -### 2.9. Подпись — набор решений — ✓ выведено - -Конвейер заменяет банк целиком (`store/migrate.go:169-170`), поэтому `PATCH /term/{id}` молча -не работает. Механика дословно: «for EACH term either promote it into the mined-delta file OR -decline it in the mined-rejects file, then resume — the stop clears once every proposed term -is promoted or rejected» (`pipeline/mining.go:201`). Отсюда: решение `promote|decline` · -счётчик «решено N из M» · запрет «продолжить» при неполном наборе · частичное сохранение ◆. - -`POST /runs/{id}/resume` — **нормативная операция, а не резерв** (первая редакция помечала её -«○ резерв строки 94», хотя тут же делала её носителем снятия стопа банка). Резерв строки 94 — -это `stop`, продуктовая кнопка. - -### 2.10. Ревизия — ✓ у ре-синка, ◆ у чтений - -**✓ выведено:** правило ре-синка — идемпотентный апсерт по `(run_id, seq)`, канал согласования -— `status --json` (`research/23` §2, §8; D39.85). - -**◆ предложено фронтом:** что ревизию несут и ЧТЕНИЯ, и что счётчик у потока и у чтений ОДИН. -Обоснование — гонка, которую иначе нечем разрешить: фронт живёт на снимке и потоке разом, -а рефетч по возврату фокуса окна у ратифицированного `@tanstack/react-query` включён по -умолчанию, то есть гонка на каждое переключение вкладки. Но это просьба, не вывод; выбор — -К-4. - -**Скоуп ревизии** (дыра первой редакции: она отдавала `revision` на межкнижной библиотеке при -пер-прогонном определении): в спеке ревизия объявлена НА РЕСУРС — у библиотеки своя, у -прогона своя. Единая сквозная или пер-ресурсная — часть К-4. - -### 2.11. Разрыв потока — ◆ предложено - -При переподключении клиент шлёт `Last-Event-ID`. Если сервер докачать не может, он обязан -ответить событием `resync_required`, а не молча начать с текущего момента: **реплей истории -запрещён**, иначе разовое событие вроде `note` теряется молча и замечание не появится -до перезагрузки. Клиент по этому событию перечитывает снимки. - -### 2.12. Разрешающий список — ✓ инвариант, ◆ форма - -Проекция «read-модель → фронт» строится как allowlist. Что лежит в операторских структурах -(`pipeline/status.go:58-130`, `:37-55`) и не может доехать: пять денежных полей плюс -`cost_usd` главы (§4.8 — денег в MVP-интерфейсе нет вовсе) · `routing` вида «stage=model», -`content_labels`, `content_routing_problems` (ПТ-33) · снапшот, дрифт, ре-билл · -`escalations`, `postcheck_misses`, `style_flags`, `glossary_miss_flagged`, `stages_skipped`, -`repair_applied`, `worst_flag_reason` · `flag_reason` и `detail` — последний несёт сырой текст -движка вида «CJK leak in the ru output: 第一节»; строку собирает `checks/sanitizer.go:658`, -а `pipeline/export.go:34` — лишь объявление поля, куда она доезжает. - -### 2.13. ПТ-34 — перевод не индексируется — ✓ инвариант - -Реестр требований назначает носителем ПТ-34 в том числе контракт, а первая редакция пункта -не имела вовсе. В спеке: приложение живёт под `X-Robots-Tag: noindex`, ответы с текстом -перевода несут `Cache-Control: no-store`, ссылка на выгрузку выдаётся только владельцу. - ---- - -## 3. Зависимости: без чего контракт не заработает - -| Что | Строка | Без чего именно | -|---|---|---| -| Пофазный прогресс `draft ∥ edit` | 99 | прогресс (§2.5) | -| Персист манифеста + `chunker_version` | 100 | стабильный `id` главы (§2.2) | -| Машиночитаемая таблица ПОДПИСИ | 101 | экран подписи (§2.9) | -| Событийный эмиттер + событие потолка | 103 | весь поток (§2.11), событие `note`, событие `ceiling` | -| **Артефакт экспорта БАНКА** | **строки нет — заводит оркестратор** | чтение `GET /books/{id}/bank` | -| **Механизм поднятия потолка** | **строки нет — заводит оркестратор** | `POST /runs/{id}/resume` после стопа по потолку (см. ниже) | -| HTTP/SSE, аутентификация, воркер | П-1 | всё; в `platform/` ноль строк кода | - -⚠ **Отдельно про банк — дыра, найденная ревью оркестратора.** Строка 101 даёт таблицу -ПОДПИСИ (стоп-таблица, кап 20 на stdout — `cmd/tmctl/render.go:98`), а не экспорт всего банка; -сам банк живёт в приватном SQLite движка, читать который платформе запрещено (D39.85). То есть -у чтения `/bank` сегодня **нет канала вообще**. Фронт этого не решает — нужна строка единого -бэклога, и заводит её оркестратор. - -Та же природа у события `note`: пер-юнитных замечаний посреди прогона движок не эмитит — -зависимость на словарь строки 103. - -⚠ **Потолок: `resume` сам по себе не сдвинет прогон** (ревью round-2, пункт 5). Движок -продолжает «once the ceiling is raised» (`pipeline/stagerun.go:488-489`), а канала поднятия -в контракте нет — и в MVP-интерфейсе быть не может: денег на экране нет вовсе (D39.84). Значит -между стопом по потолку и продолжением обязан стоять механизм ПЛАТФОРМЫ (поднятие по политике, -или явное действие вне интерфейса книги), и до него `resume` после потолка возвращает прогон -в то же состояние. Что при этом видит пользователь — К-8, вопрос владельцу; чем поднимают — -строка единого бэклога, которой нет. - ---- - -## 4. Открытые вопросы - -| # | Вопрос | Кому | -|---|---|---| -| К-1 | Десять статусов (§2.6) — принять или поправить? Три контракт выводит, а не получает | владелец / автор контракта | -| К-2 | Титул главы: отдать полем `heading` (предложено) — или оставить вклейку в текст, и фронт отрезает строкой? | автор контракта + бэкенд | -| К-3 | Метка главы в дереве: титул это ровно «Глава N», узлов 2284 — дерево одинаковых по форме строк | владелец (продуктовое) | -| К-4 | Ревизия: одна сквозная на прогон или своя на ресурс? И несут ли её чтения вообще (§2.10) | платформа | -| К-5 | Показывать ли оценку времени. **ПТ-19 существует** (`docs/product-requirements.md:49`: «видимый прогресс/ETA — из Ф3-видения ридер-IDE»), то есть посылка «не запрошено» неверна; вопрос в том, показываем ли в MVP | владелец (продуктовое) | -| К-6 | Ступени замечания: сколько их и где граница. Сегодняшние две — проекция ОПЕРАТОРСКОЙ лестницы рангов, а она не обязана совпадать с продуктовой осью | владелец (продуктовое) | -| К-7 | Пагинация: 2284 главы и 1200 терминов одним ответом или курсором? Фронт виртуализует, ему годится любой | платформа | -| К-8 | Стоп по потолку: каким статусом и каким словом? В `failed` мапить нельзя — стоп резюмируемый | владелец (продуктовое) | -| К-9 | **Отказ прескрина не выразим ни одним из десяти статусов.** Абьюз/misuse-прескрин до трат токенов и UI-контракт отказа — строка 94 (ПТ-16); книга, отклонённая прескрином, это не `rejected` (тот про неразобранный файл) и не `failed` | владелец + автор контракта | -| К-10 | **Выполнение главы — тот самый несплитованный счётчик, который §2.5 объявляет негодным.** `Chapter.units_done` не разведён по фазам, значит дерево глав показывает ноль всю черновую волну — ровно то, из-за чего прогресс книги сделан пофазным. Развести и тут (цена — пофазные счётчики НА ГЛАВУ в строке 99) или показывать в дереве другое | автор контракта + бэкенд | -| К-11 | **Условная обязательность полей — выражена у банка, не выражена у чтений.** У `BankDecision` констрейнт поставлен (`if action=promote → dst` непустой), и вот что это стоило, измерено: spectral его валидирует, а **openapi-typescript его игнорирует** — в генерённых типах `dst?: string` как был. То есть 3.1-условие защищает сервер, но не экран; клиентское сужение (юнион `promote`-с-`dst` ↔ `decline`) — работа подписного экрана S5, писать его до экрана не на чем проверить. Остаётся решить то же для чтений: `Note` не требует ни `chapter_id`, ни `unit_id`, `Unit.target` не обязателен при `translated`; обе схемы служат и вложенно, и отдельно, поэтому простое `required` соврало бы | автор контракта | -| К-12 | **Завершение выгрузки: опрос или событие?** Чтение `GET /books/{id}/exports/{id}` заведено — без него создающий вызов был тупиком (`ready:false` и ни слова дальше). Но пушить ли завершение ещё и кадром потока, чтобы не опрашивать, решает платформа: у неё воркер и её цена | платформа | - ---- - -## 5. Проверка ревью-вопросом строки 95 - -**«Сменится стадия конвейера — придётся ли править фронт?»** - -| Изменение в движке | Правит ли фронт | -|---|---| -| переименована стадия / добавлена волна | **нет** — имена стадий не пересекают шов, прогресс пофазный, а не постадийный | -| сменилась модель или маршрутизация | **нет** — `routing`/`content_labels` в allowlist не входят | -| добавлена новая причина флага | **нет** — на провод идёт продуктовая фраза, карта живёт в контракте | -| добавлен новый тип термина | **нет** — словарь расширяется минором, ветка неизвестного стоит на шве | -| добавлено новое продуктовое состояние | **да, один файл** — карта «статус → вид» на шве `src/api/`; это и есть контрольный вопрос владельца | -| сменился чанкер, главы пере-разобраны | **частично** — код фронта не правится (ключ непрозрачный, номер отображаемый), но **сохранность соответствия старых `id` новым главам контрактом не гарантируется**: это работа персиста манифеста (строка 100). Если соответствие потеряно, у пользователя разъезжаются открытые вкладки и закладки — не правка кода, но видимый ущерб, и решать его строке 100 | - -Единственное безусловное «да» — то, которое и должно быть «да». - ---- - -## Приложение А. Карта «вердикт → продуктовая фраза» — ЗАГОТОВКА - -Заполняет автор контракта вместе с бэкендом и владельцем. **Правило: фраза пишется по -доккомменту `disposition.go`, а не по имени константы, и рядом кладётся цитата** — иначе -повторяется инверсия, стоившая двух фраз (`glossary_miss` подан как «термин не подписан», -хотя термин ПОДПИСАН и его проигнорировали, `disposition.go:78-79`; `sanitizer_stripped` подан -как потеря текста, хотя «the chunk is NOT lost», `disposition.go:99`). - -⚠ **Русские фразы ниже — плейсхолдеры, а не предложение фронта.** Словарь продуктовый, его -слова выбирает владелец (ПТ-33, В-3). Ступень — тоже: сегодняшние `attention`/`glance` -унаследовали ОПЕРАТОРСКУЮ ось рангов, а она не обязана совпадать с продуктовой (К-6). - -| Причина движка | Ранг | Продуктовая фраза | Ступень | -|---|---|---|---| -| `hard_refusal` · `soft_refusal` · `content_filter` · `hard_block` | 0 | ⬜ | ⬜ | -| `cjk_artifact` · `excision_suspect` · `coverage_fail` | 1 | ⬜ | ⬜ | -| `sanitizer_defect` | 2 | ⬜ | ⬜ | -| `loop_degenerate` | 3 | ⬜ | ⬜ | -| `decode_error` | 4 | ⬜ | ⬜ | -| `glossary_miss` | 5 | плейсхолдер: «Подписанный термин не применён в переводе» | ⬜ | -| `length` · `empty` | 6 | ⬜ | ⬜ | -| `sanitizer_stripped` | 7 | плейсхолдер: «Служебная разметка вычищена автоматически» | ⬜ | -| `upstream_not_ok` | 8 (по умолчанию) | ⬜ | ⬜ | -| незнакомая причина | 8 (по умолчанию) | ⬜ нейтральная, НЕ «ошибка» | ⬜ | - -Причин пятнадцать; `upstream_not_ok` в первой редакции отсутствовал — у него нет своей ветки -в `flagReasonSeverity`, поэтому он падает в ранг по умолчанию (`pipeline/status.go:174`), -как и любая будущая причина. Последняя строка — не формальность: контракт обязан иметь фразу -для причины, которой ещё не существует. +Исходное тело — в истории git этого файла. diff --git a/frontend/docs/API_CONTRACT_INPUT.md b/frontend/docs/API_CONTRACT_INPUT.md index 50417714..3e1a7535 100644 --- a/frontend/docs/API_CONTRACT_INPUT.md +++ b/frontend/docs/API_CONTRACT_INPUT.md @@ -1,13 +1,15 @@ # Вход фронта в контракт API v0 — материал для строки 95 > ⚠ **ИСПОЛНЕНО И БОЛЬШЕ НЕ ПОДДЕРЖИВАЕТСЯ (04.08).** Этот файл — ВХОД, написанный до -> контракта: просьба к его автору. Контракт с тех пор написан, и все 18 пунктов ниже стали -> либо полем спеки [`api-contract/openapi.yaml`](api-contract/openapi.yaml), либо разделом §2 -> спутника [`API_CONTRACT_DRAFT.md`](API_CONTRACT_DRAFT.md) с тем же грунтом `file:line`, либо -> К-вопросом §4. **Читать для решения — те два файла; этот не сверяется с ними и будет -> расходиться.** Ценность, ради которой он не удалён: аудиторский след — как каждая просьба -> выводилась из кода движка, и что ломалось без неё. Место такому — `docs/archive/`, перенос -> при ратификации делает оркестратор (зона). +> контракта: просьба к его автору. Контракт с тех пор написан и ратифицирован, и все 18 пунктов +> ниже стали либо полем канонической спеки `docs/architecture/14-api-contract/openapi.yaml`, либо +> разделом §2 канонического спутника `docs/architecture/14-api-contract/README.md`, либо его +> К-вопросом §4. **Читать для решения — канон; этот файл с ним не сверяется и уже расходится:** +> его `file:line` в чужую зону протухли (проверено 02.09 — цитаты грепаются, номера строк нет), +> а часть просьб канон пере-решил. Ценность, ради которой он не удалён и не сокращён: +> аудиторский след — как каждая просьба выводилась из кода движка, и что ломалось без неё; +> цитаты из движка в нём дословные и грепаются. Место такому — `docs/archive/`, перенос делает +> оркестратор (зона). > > **Что это было.** Ф-14 бэклога зоны, доведённый до артефакта: перечень того, что интерфейсу > нужно от read-модели платформы, с обоснованием на КАЖДЫЙ пункт. Адресат — автор контракта diff --git a/frontend/docs/BACKLOG.md b/frontend/docs/BACKLOG.md index ca8127e2..03b24f87 100644 --- a/frontend/docs/BACKLOG.md +++ b/frontend/docs/BACKLOG.md @@ -1,38 +1,38 @@ # Бэклог фронта (зонный) -> Ведёт зона `frontend/` (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог `docs/PROGRESS.md` — трекер движка/полигона/доков, фронт-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99–102 — пофазный прогресс · манифест глав · машиночитаемая таблица подписи · трасса). **Развод «предложить/ратифицировать» (D39.99 п.4, после инцидента остановки S3): ПРЕДЛАГАТЬ артефакты для чужих решений — черновики контрактов, входы дизайна — фронт не только может, но обязан; не может он их РАТИФИЦИРОВАТЬ. «Не заводит своим решением» значит «финальное слово не его», а не «не смей писать черновик».** Засеян оркестратором при лендинге D39.84 — дальше правят фронт-сессии. +> Ведёт зона `frontend/` (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог `docs/PROGRESS.md` — трекер движка/полигона/доков, фронт-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99–102 — пофазный прогресс · манифест глав · машиночитаемая таблица подписи · трасса; все четыре ЗАКРЫТЫ, тела в D39.122/D39.131, а сама таблица подписи с тех пор снесена D39.158). **Развод «предложить/ратифицировать» (D39.99 п.4, после инцидента остановки S3): ПРЕДЛАГАТЬ артефакты для чужих решений — черновики контрактов, входы дизайна — фронт не только может, но обязан; не может он их РАТИФИЦИРОВАТЬ. «Не заводит своим решением» значит «финальное слово не его», а не «не смей писать черновик».** Засеян оркестратором при лендинге D39.84 — дальше правят фронт-сессии. | ID | Хвост | Вес | Источник | |---|---|---|---| -| Ф-1 | **Этапы S2–S7** (`FRONTEND_SESSION_PROMPT.md`): S2 оболочка + слой `src/ui/` · S3 слой данных/MSW/фикстуры всех состояний · S4 библиотека/загрузка/разбор/прогресс · S5 банк памяти и подпись (самый тяжёлый) · S6 читалка двух колонок и замечания · S7 метаданные/экспорт/настройки/сквозной прогон | по очереди сессий (S0/S1 — текущая) | FRONTEND_SESSION_PROMPT | +| Ф-1 | **Этапы S2–S7** (`FRONTEND_SESSION_PROMPT.md`): S2 оболочка + слой `src/ui/` · S3 слой данных/MSW/фикстуры всех состояний · S4 библиотека/загрузка/разбор/прогресс · S5 банк памяти и подпись (самый тяжёлый) · S6 читалка двух колонок и замечания · S7 метаданные/экспорт/настройки/сквозной прогон | S0–S4 пройдены и приняты (D39.135); S5 — после разморозки зоны (D39.147) | FRONTEND_SESSION_PROMPT | | Ф-2 | **React Compiler** — включить ОТДЕЛЬНЫМ шагом после заморозки интерфейса, с CI-проверкой, что вставки реально попали в бандл (в MVP выключен; совместимость держит линт eslint-plugin-react-hooks) | после заморозки UI | STACK_DECISIONS §1 | | Ф-3 | **Пересмотр TS 6 → TS 7** после выхода TS 7.1 с программным API (~октябрь 2026); решение обратимо — тайпчек не участвует в сборке | триггер: релиз TS 7.1 | STACK_DECISIONS §1 | -| ~~Ф-4~~ | ~~**Токен-гейт на отступы**~~ — **включён 04.08 в S2.** Значение теперь обязано быть последовательностью `var(--…)`; `0`, `auto` и `inherit` легальны (это не размеры), `calc()` намеренно запрещён: составное выражение из литералов проверить нечем, а если размер понадобился новый — его место в `tokens.css`. Свойства взяты регулярками, поэтому длинные формы (`border-top-left-radius`, `padding-inline`) закрыты тоже. Обкатка потребовала трёх токенов, которых не хватало: `--radius-round`, `--radius-bar`, `--row-inset`. Проверено шестью живыми нарушениями и тремя легальными формами (перечень — `FRONTEND_PLAN.md` §5.4) | закрыт | STACK_DECISIONS §3 | -| ~~Ф-8~~ | ~~Вопрос владельцу: закрывать ли `/* eslint-disable */` в TSX~~ — **закрыт 02.08 без решения владельца.** Вопрос был поставлен как выбор из двух плохих вариантов, а у него есть отраслевой третий: `@eslint-community/eslint-plugin-eslint-comments` 4.7.2 (`no-unlimited-disable` + `require-description` + `disable-enable-pair`). Голое отключение падает тремя ошибками, точечное подавление с причиной проходит. Урок: не выносить владельцу технический вопрос, не поискав отраслевую практику | закрыт | адверсариальное ревью S1 | -| ~~Ф-9~~ | ~~**Хвосты гейтов после адверсариального ревью S1**~~ — **все четыре закрыты 04.08 в S2**, каждая живым нарушением: именованные и системные цвета в TSX ловятся разрешающим списком на цветных атрибутах (`fill`/`stroke`/`color`/`stopColor`/`floodColor`/`lightingColor` — только `none`, `currentColor`, `inherit`, `transparent`, `var(--…)`) · значение CSS-переменной в `style={{…}}` обязано быть числом или токеном · носитель `style` внутри объектного литерала запрещён, и тем самым закрыт JSX-спред · системные цветовые ключевые слова закрыты `declaration-property-value-disallowed-list` по ВСЕМ свойствам, то есть и в сокращённых записях `outline`/`box-shadow`/`border`. Перечень проверенных форм (15 TSX + 14 CSS) — `FRONTEND_PLAN.md` §5.4.1. **Граница названа честно и там же:** гейт цветных атрибутов ловит литерал и шаблон, но не вычисленное значение (`const c = 'red'; `) — синтаксически это не берётся, и объявлять строку «закрытой без остатка» было бы ровно тем переобещанием, из-за которого она и заведена | закрыт для литеральных форм, граница названа | адверсариальное ревью S1 | -| ~~Ф-10~~ | ~~**Хвосты структурных тестов**~~ — **закрыты 04.08 в S2.** `cssModules.test.ts` строит пары по ИМПОРТАМ, а не по совпадению имён файлов, видит деструктуризацию `const { name } = styles` и выкидывает `url(...)` и строковые литералы до поиска классов; проверка сиротства стала «модуль, который никто не импортирует». `tokens.test.ts` получил замок полноты: каждый токен обязан попасть либо в замеры, либо в явный список подобранных, и обратно — список не может ссылаться на исчезнувший токен. Плюс новое: числа для виртуализатора живут в `src/tokens/measures.ts` и сверяются с одноимёнными токенами. Семь живых нарушений, перечень — `FRONTEND_PLAN.md` §5.4 | закрыт | адверсариальное ревью S1 | -| ~~Ф-11~~ | ~~**Контраст ниже порога WCAG на 4 узлах на маршрут**~~ — **ЗАКРЫТА 09.08 в S3.5 замером: axe печатает НОЛЬ узлов контраста на всех семи маршрутах.** Решение владельца тут не понадобилось, потому что вопрос был поставлен неверно: узлы давал не «весь приглушённый тон», а КОНКРЕТНАЯ роль — `--color-text-muted` (#707479 на панели = 3.85:1) стоял под текстом, несущим смысл. В S3.5 весь такой текст переведён на `--color-text-secondary` (5.36:1 до перекраски, 5.6:1 после), а `--color-text-muted` остался только под НЕтекстом — иконками и дорожками индикаторов, к которым порог для текста не предъявляется. Заодно палитра стала тёплой (замечание 16), и это подняло числа ещё немного. Верности референсу это не стоило ничего: приглушённая ступень как ступень осталась | закрыт | ревью S1 №2 → S3.5 | -| Ф-12 | **Масштаб списков** — **главное закрыто 04.08 в S2 замером, а не обещанием.** Заведён маршрут `/scale`: та же оболочка на настоящем масштабе — 2284 раздела и банк на 1200 терминов (`src/mock/scale.ts` через `getScaleFixture()`). Дерево и банк виртуализованы по умолчанию (RAC `Virtualizer` + `ListLayout`), и это измерено в живом браузере: на 2293 узлах дерева в DOM держится **31 строка**, `scrollHeight` 59 618px, кадр при прокрутке — медиана 22 мс, p95 31 мс; банк на 1200 терминах — 30 строк в DOM, медиана 16,7 мс. Прокрутка до конца и обратно счётчик строк не растит. **Замечания намеренно НЕ виртуализованы:** их список пер-главный, а юнитов на главу ≈1,9 (ПТ-21) и флаг у юнита один — 10³ там не бывает по построению; сводный вид по книге, если он появится, придёт с S6 и получит тот же примитив `ui/List`. **Два хвоста строки НЕ сделаны и получают диспозицию, а не зачёркивание** (нашло адверсариальное ревью S2): дерево осталось плоским без группировки по сотням — это работа экрана глав (S4), у оболочки для неё нет ни данных, ни требования; банк без разделения «подписано / ждёт решения» — это работа подписного экрана (S5), справочному виду в 320px разделение только мешает | ⚠ **Группировка ЗАКРЫТА 14.08 в S4:** дерево свыше 200 разделов складывается по СОТНЯМ (`Library.tsx`, `groupSize`/`groupFrom`), 2284 раздела дают 23 строки вместо 2284. Число не из вкуса: главу адресуют порядковым номером, и сотня — шаг, которым их считает человек; замер говорит то же с другой стороны — плоское дерево рисуется дёшево (31 строка в DOM), а вот полоса прокрутки на 2284 строках даёт 59 618px хода и треть пикселя на строку. Ниже порога дерево остаётся ПЛОСКИМ: книге из дюжины разделов складка только мешает. Группа, в которой стоит выбранный раздел, раскрывается сама — и остаётся закрываемой рукой (управляемое раскрытие, `ui/Tree.tsx`). Осталось: разделение банка — S5 | масштаб и группировка закрыты; разделение банка — S5 | ревью S1 №2 → S4 14.08 | -| Ф-14 | **Вход фронта в контракт API (единый бэклог, строка 95)** — **доведён до артефакта 04.08 в S3: [`API_CONTRACT_INPUT.md`](API_CONTRACT_INPUT.md)**, перечень с обоснованием на каждый пункт, каждое обоснование грунтовано `file:line` движка (прочитано, не пересказано). Внутри: язык кодами · идентификатор главы отдельно от номера (номер производный от чанкера, `chunker.go:101-118` — пустая глава не потребляет номер, правка исходника сдвигает нумерацию) · единица пары = ЮНИТ с ключом лидера `(chapter, firstChunkIdx)`, `export.go:88-93` · состояние у прогона, у главы выполнение · прогресс пофазно по юнитам · `run_id` · трёхзначный `auto\|draft\|approved` вместо булева `signed` · непрозрачный ключ строки банка (автоинкремент движка стабильным НЕ является — `migrate.go:172-174`, банк заменяется целиком каждый прогон) · поля `type`/`sense`/`since_ch`/`source` с точным словарём · подпись = набор решений promote/decline, стоп снимается ТОЛЬКО полным набором (`mining.go:200-202`) · продуктовый словарь вердиктов и запрет `detail` на стороне контракта · монотонный `id` + `Last-Event-ID` + ревизия на чтениях из того же счётчика · **разрешающий список полей вместо вычитания** (инвентарь протечек `StatusReport`/`ChapterPassport`: деньги · `routing`/модели · снапшот/дрифт/ре-билл) · правило эволюции словарей под Ф-22. Плюс 6 вопросов (К-1..К-6), на которые фронт ответа не имеет. **Найдено при сборке — четвёртый экземпляр класса «фикстура выдумала форму»:** титульная политика pack-13 рендерит заголовок ДЕТЕРМИНИСТИЧЕСКИ («Глава {n}» из `configs/langpacks/zh-ru/heading.txt`), вырезает исходный маркер из текста для модели (`chunker.go:110-118`) и ВКЛЕИВАЕТ титул внутрь финального текста первого юнита (`export.go:209-215`), оставляя колонку исходника без него — а фикстура рисует «Раздел N. …» в дереве и неснятый «第二节:» в оригинале. Не чинится до ответа на К-2: правка формы заголовка без контракта даст пятую выдумку. **По слову владельца тем же днём написан и ЧЕРНОВИК самого контракта — [`API_CONTRACT_DRAFT.md`](API_CONTRACT_DRAFT.md)** (12 разделов + приложение-карта вердиктов; каждое место помечено «выведено из кода» / «предложено фронтом» / «открыто»; двухфазный поток ВЫВЕДЕН из `--verify-bank`, а не спроектирован; в конце — прогон ревью-вопроса строки 95 по шести изменениям движка). Самопроверка черновика нашла дыру в самой лестнице статусов строки 95: между «разбор» и «перевод» нет состояния покоя, а библиотека обязана показывать разобранную книгу, которую не запускали ⇒ заведено `not_started`, и сегодняшние девять значений типов разводятся на десять. **Дофикс по ревью оркестратора (04.08) исполнен, форма сменена по решению владельца:** нормативная поверхность контракта — теперь **OpenAPI 3.1 `docs/api-contract/openapi.yaml`** (spectral шестым шагом `check`, openapi-typescript генерит `src/api/schema.ts`, тест дрифта + ветка неизвестного значения на шве `src/api/contract.ts`, MSW-хендлер как proof-of-form); md стал спутником — провенанс, обоснования, К-вопросы. Закрыты оба блокера ревью (состояние пары выводится из ПАРЫ «вердикт + наличие текста», а не из вердикта — флагнутый юнит легально приходит С текстом; чтение банка помечено зависимостью «канала нет»), восемь ✓-инфляций пере-размечены на ◆, внесены все фактические правки (фантом `auto` в провенансе убран по путям записи · `upstream_not_ok` добавлен · ПТ-34 заведён пунктом · стоп по потолку разведён с `failed` · правило разрыва `Last-Event-ID` · смягчён §12 про чанкер). Открытых вопросов стало девять: добавлен К-9 — **отказ прескрина (строка 94) не выразим ни одним из десяти статусов**. **Ратифицировать и перенести в `docs/architecture/14-api-contract/` — оркестратор; фронт этого не может** (шапка этого файла). **Подрезана 04.08 по слову владельца:** из описаний спеки убран провенанс и грунты `file:line` — они дублировали спутник и разошлись бы с ним при первой правке; осталось норматив плюс та оговорка «почему», что предотвращает конкретную ошибку. Спека переведена на английский, потому что её описания приезжают доккомментариями в генерённый `src/api/schema.ts` (Ф-25). Адверсариальная проверка подрезки нашла в спеке три расхождения с самим спутником (провенанс `mined` подан как «никем не подписано» — это ось `status`; ревизия кадра объявлена фактом при открытом К-4; `Run.status` берёт книжный словарь целиком) и два вопроса, которых не было: **К-10** пофазность выполнения главы и **К-11** условная обязательность полей ⇒ открытых стало одиннадцать. **Бумага сокращена тем же днём:** `API_CONTRACT_INPUT.md` под ⚠-баннером «исполнено, не поддерживается» — его 18 разделов лежат один-в-один в §2 спутника, и как живой документ он был третьим местом правки; не удалён (untracked, удаление невосстановимо), перенос в `docs/archive/` — оркестратору. Из спутника убран §1 (пины и отклонение по пиру дублировали `STACK_DECISIONS.md` §3 и Ф-23/Ф-24). **Второй пасс ревью (round-2) исполнен:** чтение выгрузки + К-12 · условие `if action=promote → dst` непустой (spectral валидирует, генератор ИГНОРИРУЕТ — записано в К-11 измерением) · `kind` nullable с правилом пустого, пометка ✓→◆ · `resume` после потолка помечен зависимостью на платформенный механизм поднятия · §2.7 переписан: «текст + замечание» это класс, но `glossary_miss` в него не входит (проверено по `memberDrops`/`waverun`), фикстура `r2-2` переведена на c-lite drop · ветка неизвестного добита до шести словарей. Открытых вопросов двенадцать. **08.08 (S3): РАТИФИЦИРОВАН и ПРАВЛЕН.** Контракт ратифицирован оркестратором как D39.99; S3 внесла в зонную копию весь список правок своего промта и бампнула версию до **0.2.0** — `paused`, `eta_seconds`, метка главы из данных, пер-книжная ревизия с правилом `>= R` и обязательным `resync_required` после полной замены, keyset-курсор на ВСЕХ списках, `X-TM-Client`, `Run.paused_reason`, `GET /usage`, `GET /books/{id}/run-options` с потолком прогона в ГЛАВАХ, same-origin. Байт-сверка с каноном намеренно даёт расхождение: канон правит оркестратор, диффом. Открытых вопросов стало **семь** (закрыты К-1/К-3/К-4/К-5/К-7/К-8/К-12, добавлен К-13 — Ф-31). Что переносить в канон — канон `docs/architecture/14-api-contract/` (патч ПРИМЕНЁН и удалён, D39.115) | правки внесены · ждут пере-ратификации диффом | ревью S1 №3 · сборка S3 04.08 · дофикс ревью 04.08 · подрезка 04.08 | -| ~~Ф-15~~ | ~~**`src/api/` синхронен, состояний загрузки и ошибки в типах нет**~~ — **ЗАКРЫТА 08.08 в S3.** `src/api/` асинхронен и разговаривает HTTP: MSW как СЕТЕВОЙ мок (а не подменённый модуль), поэтому приложение делает настоящий `fetch` и настоящий `EventSource`, и ветки ожидания/ошибки/пустоты — это код, который работает в бою, а не второй его экземпляр. Ветки живут в одном месте (`showcase/Loaded.tsx`) и приезжают ВМЕСТЕ со слоем данных, до первого продуктового экрана, как строка и требовала. Типы сведены с контрактом: `types.ts` удалён, экраны живут на суженных типах `api/contract.ts`. **Что нашёл сам скриншот-цикл, а не рассуждение:** выключенный запрос TanStack Query остаётся `pending` НАВСЕГДА, и на пустой библиотеке правая панель вечно показывала «Идёт загрузка» — различение «ждём» и «выключено» (`fetchStatus === 'idle'`) добавлено после того, как это увидели на кадре. Фикстуры всех состояний — шесть миров, по маршруту на каждый | закрыт | ревью S1 №3 | +| ~~Ф-4~~ | ~~**Токен-гейт на отступы**~~ — **включён 04.08 в S2.** Размер обязан быть последовательностью `var(--…)`; `0`, `auto` и `inherit` легальны (это не размеры), `calc()` намеренно запрещён: составное выражение из литералов проверить нечем, а новому размеру место в `tokens.css`. Свойства взяты регулярками, поэтому длинные формы (`border-top-left-radius`, `padding-inline`) закрыты тоже. Механизм — `stylelint.config.js`; перечень проверенных нарушений и легальных форм — `FRONTEND_PLAN.md` §5.4 | закрыт | STACK_DECISIONS §3 | +| ~~Ф-8~~ | ~~Вопрос владельцу: закрывать ли `/* eslint-disable */` в TSX~~ — **закрыт 02.08 без решения владельца.** Вопрос был поставлен как выбор из двух плохих вариантов, а у него есть отраслевой третий: `@eslint-community/eslint-plugin-eslint-comments` (`no-unlimited-disable` + `require-description` + `disable-enable-pair`) — голое отключение падает тремя ошибками, точечное подавление с причиной проходит. **Урок:** не выносить владельцу технический вопрос, не поискав отраслевую практику | закрыт | адверсариальное ревью S1 | +| ~~Ф-9~~ | ~~**Хвосты гейтов после адверсариального ревью S1**~~ — **все четыре закрыты 04.08 в S2**, каждая живым нарушением: цветные атрибуты TSX судятся разрешающим списком; значение CSS-переменной в `style={{…}}` обязано быть числом или токеном; носитель `style` внутри объектного литерала запрещён, и этим закрыт JSX-спред; системные цветовые ключевые слова закрыты по ВСЕМ свойствам, то есть и в сокращённых `outline`/`box-shadow`/`border`. Механизм — `eslint.config.js` и `stylelint.config.js`, перечень проверенных форм (15 TSX + 14 CSS) — `FRONTEND_PLAN.md` §5.4.1. **Граница названа честно:** гейт ловит литерал и шаблон, но НЕ вычисленное значение (`const c = 'red'; `) — синтаксически это не берётся, и объявлять строку «закрытой без остатка» было бы тем самым переобещанием, из-за которого она заведена | закрыт для литеральных форм, граница названа | адверсариальное ревью S1 | +| ~~Ф-10~~ | ~~**Хвосты структурных тестов**~~ — **закрыты 04.08 в S2**, семь живых нарушений: `cssModules.test.ts` строит пары по ИМПОРТАМ, а не по совпадению имён файлов, видит деструктуризацию `const { name } = styles` и выкидывает `url(...)` и строковые литералы до поиска классов, а сиротство стало «модуль, который никто не импортирует»; `tokens.test.ts` получил замок полноты — каждый токен обязан попасть либо в замеры, либо в явный список подобранных, и список не может ссылаться на исчезнувший токен; числа виртуализатора живут в `src/tokens/measures.ts` и сверяются с одноимёнными токенами. Перечень — `FRONTEND_PLAN.md` §5.4 | закрыт | адверсариальное ревью S1 | +| ~~Ф-11~~ | ~~**Контраст ниже порога WCAG на 4 узлах на маршрут**~~ — **ЗАКРЫТА 09.08 в S3.5 замером: axe печатает НОЛЬ узлов контраста на всех семи маршрутах.** Решение владельца не понадобилось, потому что вопрос был поставлен неверно: узлы давал не «весь приглушённый тон», а КОНКРЕТНАЯ роль — `--color-text-muted` (#707479 на панели = 3.85:1) стоял под текстом, несущим смысл. Весь такой текст переведён на `--color-text-secondary` (5.36:1 до перекраски, 5.6:1 после), а `--color-text-muted` остался только под НЕтекстом — иконками и дорожками индикаторов, к которым порог для текста не предъявляется. Верности референсу это не стоило ничего: приглушённая ступень как ступень осталась | закрыт | ревью S1 №2 → S3.5 | +| Ф-12 | **Масштаб списков** — **главное закрыто 04.08 в S2 замером, а не обещанием.** Маршрут `/scale`: та же оболочка на настоящем масштабе — 2284 раздела и банк на 1200 терминов (`src/mock/scale.ts` через `getScaleFixture()`). Дерево и банк виртуализованы по умолчанию (RAC `Virtualizer` + `ListLayout`), и это измерено в живом браузере: на 2293 узлах дерева в DOM держится **31 строка**, `scrollHeight` 59 618px, кадр при прокрутке — медиана 22 мс, p95 31 мс; банк на 1200 терминах — 30 строк в DOM, медиана 16,7 мс. Прокрутка до конца и обратно счётчик строк не растит. **Замечания намеренно НЕ виртуализованы:** их список пер-главный, а юнитов на главу ≈1,9 (ПТ-21) и флаг у юнита один — 10³ там не бывает по построению; сводный вид по книге, если он появится, придёт с S6 и получит тот же примитив `ui/List` | ⚠ **Группировка ЗАКРЫТА 14.08 в S4:** дерево свыше 200 разделов складывается по СОТНЯМ (`Library.tsx`, `groupSize`/`groupFrom`), 2284 раздела дают 23 строки. Число не из вкуса: главу адресуют порядковым номером, и сотня — шаг, которым их считает человек; замер говорит то же с другой стороны — плоское дерево рисуется дёшево (31 строка в DOM), а полоса прокрутки на 2284 строках даёт 59 618px хода и треть пикселя на строку. Ниже порога дерево остаётся ПЛОСКИМ; группа с выбранным разделом раскрывается сама и остаётся закрываемой рукой (`ui/Tree.tsx`). Осталось: разделение банка «подписано / ждёт решения» — S5 | ревью S1 №2 → S4 14.08 | +| Ф-14 | **Вход фронта в контракт API (единый бэклог, строка 95) — ИСПОЛНЕН.** Перечень входа и черновик контракта написаны S3 04.08 ([`API_CONTRACT_INPUT.md`](API_CONTRACT_INPUT.md), [`API_CONTRACT_DRAFT.md`](API_CONTRACT_DRAFT.md) — оба сегодня под ⚠-баннерами «исполнено, канон живёт не здесь»), контракт РАТИФИЦИРОВАН оркестратором (D39.99) и с тех пор ушёл на **0.9.0**. Где что живёт сегодня: норматив — `docs/architecture/14-api-contract/openapi.yaml`; провенанс ✓/◆/○, обоснования и К-вопросы — компаньон `README.md` рядом с ним; ход сборки, оба пасса ревью и дофиксы — [archive/frontend-PROGRESS-S0-S4.md](archive/frontend-PROGRESS-S0-S4.md) (записи 04.08 и 08.08). **Урок, который строка держит живым:** класс ошибки «фикстура выдумала форму» ловился в зоне ЧЕТЫРЕЖДЫ — форму берут из канона контракта, а не из движка и не из головы | закрыт (D39.99) | ревью S1 №3 · сборка S3 04.08 | +| ~~Ф-15~~ | ~~**`src/api/` синхронен, состояний загрузки и ошибки в типах нет**~~ — **ЗАКРЫТА 08.08 в S3.** `src/api/` асинхронен и разговаривает HTTP: MSW стоит СЕТЕВЫМ моком (а не подменённым модулем), поэтому приложение делает настоящий `fetch` и настоящий `EventSource`, и ветки ожидания/ошибки/пустоты — это код, который работает в бою, а не второй его экземпляр. `types.ts` удалён, экраны живут на суженных типах `api/contract.ts`; фикстуры всех состояний — шесть миров, по маршруту на каждый. **Что нашёл сам скриншот-цикл, а не рассуждение:** выключенный запрос TanStack Query остаётся `pending` НАВСЕГДА, и на пустой библиотеке правая панель вечно показывала «Идёт загрузка» — различение «ждём» и «выключено» (`fetchStatus === 'idle'`) добавлено после того, как это увидели на кадре | закрыт | ревью S1 №3 | | Ф-13 | **`eslint-plugin-jsx-a11y` не встаёт на ESLint 10** (peer `^3..^9`, проверено `--dry-run` → ERESOLVE). Статическую половину доступности держит axe в скриншот-цикле; плагин подключить, когда выйдет совместимый мажор | триггер: мажор плагина | ревью S1 №2 | -| Ф-7 | **Хвосты сведения витрины с референсом** (`FRONTEND_PLAN.md` §5.2). **Часть S2 закрыта 04.08:** `+` стоит в ряду вкладок ЛЕВОЙ панели и запускает «Добавить книгу» (решение владельца 04.08); дублирующая строка «+ Добавить книгу» из дерева убрана — одна вещь, одно место. В центре и справа `+` нет и не будет: наборы вкладок там фиксированные, пользователь их не расширяет, и кнопка вышла бы декоративной — ровно то, что эта строка запрещает. Состояния наведения перестали быть «непроверенными»: они сняты не статичным кадром, а прогоном браузера (`data-hovered`/`data-selected`/`data-focus-visible` у кнопки, вкладки, строки дерева и списка). **Осталось на S6:** приглушённая колонка оригинала — решение, а не замер, пересмотреть на настоящей читалке | ~~S2~~ · S6 | S1, сверка с fleet.png | +| Ф-7 | **Хвосты сведения витрины с референсом** (`FRONTEND_PLAN.md` §5.2). **Часть S2 закрыта 04.08:** `+` стоит в ряду вкладок ЛЕВОЙ панели и запускает «Добавить книгу» (решение владельца 04.08), дублирующая строка из дерева убрана; в центре и справа `+` нет и не будет — наборы вкладок там фиксированные, пользователь их не расширяет, и кнопка вышла бы декоративной. Состояния наведения сняты прогоном браузера (`data-hovered`/`data-selected`/`data-focus-visible` у кнопки, вкладки, строки дерева и списка), а не статичным кадром. **Осталось на S6:** приглушённая колонка оригинала — это решение, а не замер, пересмотреть на настоящей читалке | ~~S2~~ · S6 | S1, сверка с fleet.png | | Ф-16 | **Выгрузка книги (§3.11) ушла из верхней полосы.** В витрине S1 там висела иконка загрузки без действия; по правилу Ф-7 («кнопка только вместе с действием») она снята до S7, который строит экспорт. Вернуть иконку вместе с самим действием | S7 | S2 | | Ф-17 | **Закрытие вкладки с клавиатуры не объявлено скринридеру.** Крестик у вкладки-документа сознательно НЕ кнопка: у роли `tab` дети презентационные (ARIA), вложенный фокусируемый элемент выпал бы из дерева доступности, оставшись в порядке табуляции (это же ловит axe правилом `nested-interactive`). Мышь закрывает крестиком, клавиатура — `Delete` на выбранной вкладке. Объявить сочетание нечем: `aria-keyshortcuts` не входит в типизированный API `Tab`/`TabList` в react-aria-components 1.20.0. Пересмотреть при обновлении библиотеки либо при появлении палитры команд | триггер: мажор RAC или экран горячих клавиш | S2 | -| ~~Ф-19~~ | ~~**Переключение вкладки размонтирует её содержимое, и прокрутка длинного списка теряется.**~~ — **ЗАКРЫТА 09.08 в S3.5, и механизмом.** Лекарство выбрано не вслепую: решение об устройстве оболочки принято тем же паком (модель вкладок VS Code, замечание владельца 2), и под него подошёл keep-alive, а не запоминание офсета. Реализация — проп `keepAlive` у примитива `ui/Tabs`: невыбранные `TabPanel` монтируются (`shouldForceMount`), стоят в одной ячейке грида и гасятся `visibility: hidden`. Именно `visibility`, а не `display: none`: последний уничтожает бокс и вместе с ним офсет прокрутки — то есть лечил бы половину болезни. Библиотека сама вешает на невыбранную панель `inert` и `data-inert`, поэтому из дерева доступности и порядка табуляции она вынута, а axe на дублях содержимого не срабатывает (проверено прогоном на семи маршрутах). **Цена, которую пришлось заплатить и назвать:** при keep-alive все открытые вкладки живут одновременно, поэтому общий запрос глав отдал бы каждой текст АКТИВНОЙ главы — запрос переехал в `showcase/Document.tsx`, по запросу на открытую вкладку. Приёмка: `npm run scenes scroll` — прокрутка банка на `/scale` до 9000 → уход на «О книге» → возврат → офсет 9000. **Хвост, которого раньше не было:** скрытые панели остаются в DOM, поэтому глобальный локатор по роли в тестах находит строку соседней вкладки — сценарии обязаны искать внутри своей области | закрыт | адверсариальное ревью S2 → S3.5 | +| ~~Ф-19~~ | ~~**Переключение вкладки размонтирует её содержимое, и прокрутка длинного списка теряется.**~~ — **ЗАКРЫТА 09.08 в S3.5, и механизмом:** проп `keepAlive` у примитива `ui/Tabs` — невыбранные `TabPanel` монтируются (`shouldForceMount`), стоят в одной ячейке грида и гасятся `visibility: hidden`. Именно `visibility`, а не `display: none`: последний уничтожает бокс и вместе с ним офсет прокрутки, то есть лечил бы половину болезни. Библиотека сама вешает на невыбранную панель `inert`, поэтому из дерева доступности и порядка табуляции она вынута, а axe на дублях содержимого не срабатывает (проверено на семи маршрутах). **Цена, которую пришлось назвать:** при keep-alive все открытые вкладки живут одновременно, поэтому общий запрос глав отдал бы каждой текст АКТИВНОЙ главы — запрос переехал в `showcase/Document.tsx`, по запросу на открытую вкладку. Приёмка: `npm run scenes scroll` — прокрутка банка на `/scale` до 9000, уход на «О книге», возврат, офсет 9000. **Хвост, живой и сегодня:** скрытые панели остаются в DOM, поэтому глобальный локатор по роли находит строку соседней вкладки — сценарии обязаны искать внутри своей области | закрыт | адверсариальное ревью S2 → S3.5 | | Ф-20 | **Мёртвый шрифт и мёртвые токены в поставке.** `@fontsource-variable/jetbrains-mono` грузится в бандл, но `--font-mono` не выбран ни одним правилом: художественную прозу моноширинным не набирают, а во Fleet на этом месте был код. Туда же несколько токенов, которые ни разу не используются (`--color-current-line`, `--color-hint`). Замок полноты токенов (S2) требует классифицировать токен, но не требует его использовать. Решать на S6, когда читалка станет настоящей: либо моноширинный находит работу, либо импорт и токены уходят | S6 | адверсариальное ревью S2 | -| ~~Ф-22~~ | ~~**У закрытых юнионов нет ветки неизвестного значения**~~ — **ЗАКРЫТА 08.08 в S3, и механизмом, а не дисциплиной.** Ветка стоит НА ШВЕ (`src/api/vocabulary.ts` + нормализация в `contract.ts`): значение, пришедшее с провода, сужается один раз, там, где входит, и незнакомое становится `null`, а не роняет дерево на `undefined.tone`. Словарь объявлен как `Record<Значение, Смысл>`, поэтому список значений ВЫВОДИТСЯ из записей — разойтись с типом ему нечем, а новое значение контракта роняет `tsc` с именем пропавшего значения в тексте ошибки (проверено живым нарушением, `FRONTEND_PLAN.md` §5.4.3). Тем же ходом закрыт названный в строке второй дефект того же класса: незнакомая ступень замечания больше НЕ уходит в спокойную ветку — неизвестная ступень берёт ветку внимания, потому что единственное, чего ось важности делать не вправе, это прятать важное. Продуктовое слово ступени по-прежнему за владельцем (Ф-21), и оно тут не выдумано | закрыт | адверсариальное ревью S2 → ревью оркестратора 04.08 → S3 08.08 | +| ~~Ф-22~~ | ~~**У закрытых юнионов нет ветки неизвестного значения**~~ — **ЗАКРЫТА 08.08 в S3, и механизмом, а не дисциплиной.** Ветка стоит НА ШВЕ (`src/api/vocabulary.ts` + нормализация в `contract.ts`): значение, пришедшее с провода, сужается один раз, там, где входит, и незнакомое становится `null`, а не роняет дерево на `undefined.tone`. Словарь объявлен как `Record<Значение, Смысл>`, поэтому список значений ВЫВОДИТСЯ из записей — разойтись с типом ему нечем, а новое значение контракта роняет `tsc` с именем пропавшего значения в тексте ошибки (проверено живым нарушением, `FRONTEND_PLAN.md` §5.4.3). Тем же ходом закрыт второй дефект того же класса: незнакомая ступень замечания берёт ветку ВНИМАНИЯ, а не спокойную — единственное, чего ось важности делать не вправе, это прятать важное | закрыт | адверсариальное ревью S2 → S3 08.08 | | Ф-21 | **Ступень замечания различается только цветом полоски** (`attention` синяя, `glance` серая). Зрячему разница читается ещё и светлотой, но скринридеру приоритет не достаётся вовсе. Лечение — слово, как это уже сделано для состояния прогона книги; **какое именно слово, решает владелец**: словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, так что «важно/неважно» не годится | решение владельца | адверсариальное ревью S2 | | Ф-18 | **Бандл едет одним куском, и кусок растёт.** Замеры главного чанка: 545 КБ (04.08, S2) → 724 130 байт (S3.7) → **800 219 байт, 243 446 gzip (S4 14.08, `npx vite build` + `gzip -c`)**. Прирост пака S4 — +76 089 байт: четыре примитива формы (`Select` тянет слой поповера, `Slider` — свой пакет react-aria), экраны интейка и запуска, мир фикстуры и текст каталога. Для десктопного SPA за логином это не авария (индексации нет, сеть локальная), но одним куском едут и экраны, которых пользователь ещё не открыл — включая мок-сеть (Ф-29), которая в бою не нужна вовсе. Лечение прежнее — разбиение по маршрутам после S7, когда экраны появятся; ⚠ добавилось второе, дешёвое и раннее: выкинуть `src/mock/` из продакшн-сборки в день живой платформы (это Ф-29, и она теперь ещё и про вес) | после S7 либо при жалобе на первый экран | S2, замер; пере-замерено S3.7 и S4 | | Ф-23 | **`openapi-typescript` держится на точечном `overrides`, а не на честном пире.** Генератор объявляет `typescript@^5.x` (7.13.0 вышла 11.02.2026, до релиза TS 6), у нас намеренный TS 6 — Ф-3. `--legacy-peer-deps` **отвергнут проверкой исполнением:** после него обычный `npm install` падает, то есть дерево ломается у каждой следующей сессии и у `prepare`-инсталлера pre-commit хука. Взят `overrides` на ОДИН пакет (`{"openapi-typescript":{"typescript":"$typescript"}}`) вместо глобального ослабления проверки пиров; работоспособность доказана: `npm ci` с нуля зелёный (0 уязвимостей), генератор запускается на TS 6, тест дрифта проходит. Снять, когда генератор расширит пир либо когда приедет TS 7.1 и Ф-3 закроется | триггер: пир генератора расширен ИЛИ TS 7.1 (Ф-3) | S3, дофикс контракта 04.08 | | Ф-24 | **AsyncAPI рассмотрен и отложен.** Он описал бы SSE-канал строже, чем нынешняя таблица «имя события → схема» в описании `EventEnvelope`. Отложен, потому что это второй нормативный артефакт и второй линтер ради ОДНОГО эндпоинта, при том что полезные нагрузки уже описаны схемами OpenAPI и попадают в генерённые типы. Пересмотреть, когда каналов станет больше одного либо когда понадобится генерировать клиент потока, а не разбирать кадры руками | триггер: второй канал ИЛИ генерация клиента потока | S3, дофикс контракта 04.08 | -| ~~Ф-25~~ | ~~**`src/` смешанный по языку комментариев.**~~ — **ЗАКРЫТА 10.08 в S3.7, и шире, чем строка просила.** Правило владельца по ходу пака ужесточено до «в КОДЕ никакого русского»: комментарии, имена тестов, сообщения ошибок, логи скриптов и служебные строки сценариев — английские. Свод сделан одним проходом по ВСЕМУ дереву зоны (54 файла: `src/**`, `scripts/**`, `eslint.config.js`, `stylelint.config.js`, `vite.config.ts`, `tsconfig.json`, `index.html`), а не по списку тронутых сессией. Держит это не дисциплина, а гейт: `src/i18n/catalogue.test.ts` подметает кириллицу построчно по ts/tsx/css/mjs/py (идентификаторы вида Ф-49 исключены явно — это имена строк бэклога, а не проза). Граница прежняя и теперь машинная: русский легален ровно в двух местах, и оба — данные (каталог сообщений `src/i18n/ru.ts` и фикстуры `src/mock/`). Комментариев, оказавшихся ВРАНЬЁМ против кода, свод не нашёл ни одного — агенты сверяли каждый по коду | закрыт | слово владельца 04.08 → S3.7 10.08 | -| Ф-26 | **Загрузка книги ПОСТРОЕНА (S4), выгрузка ждёт S7.** Исходная формулировка: «выгрузка и загрузка книги в слое данных не заведены — намеренно». `POST /books` (multipart с прогрессом отправки) и пара `POST/GET /books/{id}/exports` (опрос по `Retry-After`) несут МЕХАНИЗМ, а не поверхность: прогресс загрузки — часть экрана S4, цикл опроса — часть экрана S7. Строить их сейчас значило бы построить механизм без задачи, что проектный норматив прямо запрещает, и выбросить его при первом же настоящем экране. Контракт обе поверхности объявляет, генерённые типы для них есть — недостающего кода ровно на десяток строк, когда появится, кому их звать | ⚠ **Половина загрузки ЗАКРЫТА 14.08 в S4:** `POST /books` живёт в `src/api/upload.ts` — XHR, а не `fetch`, потому что стандарт Fetch не отдаёт прогресс ТЕЛА запроса вовсе (поток в запросе — только Chromium и только HTTP/2), а книга это минуты тишины. Порядок частей формы — правило провода (PD-172, `file` последним) и держится тестом; отказы 400/404/408/413 — состояния формы. Экспорт (`POST/GET /books/{id}/exports`) по-прежнему ждёт S7 | S7 (экспорт) | S3 08.08 → S4 14.08 | +| ~~Ф-25~~ | ~~**`src/` смешанный по языку комментариев.**~~ — **ЗАКРЫТА 10.08 в S3.7, и шире, чем строка просила.** Правило владельца ужесточено до «в КОДЕ никакого русского»: комментарии, имена тестов, сообщения ошибок, логи скриптов и служебные строки сценариев — английские. Свод сделан одним проходом по ВСЕМУ дереву зоны (54 файла, включая корневые конфиги), а не по списку тронутых сессией. Держит это не дисциплина, а гейт: `src/i18n/catalogue.test.ts` подметает кириллицу построчно по ts/tsx/css/mjs/py (идентификаторы вида Ф-49 исключены явно — это имена строк бэклога, а не проза). **Граница машинная:** русский легален ровно в двух местах, и оба — данные (каталог `src/i18n/ru.ts` и фикстуры `src/mock/`). Комментариев, оказавшихся ВРАНЬЁМ против кода, свод не нашёл ни одного | закрыт | слово владельца 04.08 → S3.7 10.08 | +| Ф-26 | **Загрузка книги ПОСТРОЕНА (S4), выгрузка ждёт S7.** `POST /books` живёт в `src/api/upload.ts` — XHR, а не `fetch`, потому что стандарт Fetch не отдаёт прогресс ТЕЛА запроса вовсе (поток в запросе — только Chromium и только HTTP/2), а книга это минуты тишины. Порядок частей формы — правило провода (PD-172, `file` последним) и держится тестом; отказы 400/404/408/413 — состояния формы. Экспорт (`POST/GET /books/{id}/exports`) не строился намеренно: цикл опроса по `Retry-After` — часть экрана S7, а механизм без задачи проектный норматив прямо запрещает; контракт обе поверхности объявляет, типы сгенерены, недостающего кода ровно на десяток строк | S7 (экспорт) | S3 08.08 → S4 14.08 | | Ф-27 | **`GET /usage` читается слоем данных, но ни один экран его не показывает.** Так и задумано: страница статуса использования живёт в настройках (ПТ-35/П-5), а настройки строит S7. Функция и её тест существуют, чтобы поверхность контракта была покрыта целиком и чтобы S7 не пришлось трогать шов; если S7 сдвинется далеко, строку пересмотреть — непрочитанный код тоже стоит денег | S7 | S3 08.08 | | Ф-28 | **Три high-уязвимости в дереве, все транзитивные и все в инструментах сборки** (замер 08.08): `js-yaml` через `@redocly/openapi-core` ← `openapi-typescript`, `js-yaml` через `cosmiconfig` ← `stylelint`, `nanoid` через `postcss` ← `stylelint`. **К новому пину S3 отношения не имеют** — проверено `npm ls`, `@tanstack/react-query` ни одну из них не тянет; это адвайзори, опубликованные после 04.08, когда журнал фиксировал «0 уязвимостей». В браузер не едет ничего из перечисленного. Пины ратифицированы (`STACK_DECISIONS` §3), и двигать их посреди сессии, которая на этих же инструментах держит гейт, было бы разменом безопасности сборки на риск красного дерева у всех следующих сессий — поэтому измерено и передано, а не пропатчено молча | решение оркестратора/владельца: бампнуть пины отдельным проходом | S3 08.08, `npm audit` | | Ф-29 | **Мок-сеть едет в продакшн-бандл.** `startMocking()` подтягивает `src/mock/` динамическим импортом, но скриншот-цикл гоняет `vite build` + `preview`, то есть продакшн-режим, и без мока там нечего показывать. Пока платформы на том же origin нет, это цена работоспособности; в день, когда фронт заговорит с настоящей платформой, папка `mock/` удаляется целиком и импорт уходит вместе с ней — ровно так, как обещал шов. Строка нужна, чтобы это удаление кто-то сделал ОСОЗНАННО, а не обнаружил мок в бою | триггер: живая платформа на том же origin | S3 08.08 | -| Ф-30 | **Метка главы без заголовка — голый порядковый номер.** Контракт запретил синтез «Глава {n}» (К-3: метка из ДАННЫХ книги, легальна книга без номеров и глав вовсе), и фикстуры содержат такие главы намеренно. Когда у главы нет ни `heading`, ни `number`, в дереве остаётся позиция в порядке чтения — на кадре это строка «12» среди названий. Врать словом фронт не стал: словарь продуктовый, и это ровно вопрос В-4, который уже стоит владельцу. Строка держит хвост до его ответа | решение владельца (В-4) | S3 08.08, кадр `/scale` | -| Ф-31 | **К-13 — новый вопрос контракта, заведённый этой сессией.** Достижение СОБСТВЕННОГО потолка прогона (`ceiling_chapters`) и исчерпание кредита аккаунта сегодня неразличимы: `paused_reason` имеет ровно одно значение, `credit_exhausted`, и оно про кредит. Механически они могут совпасть — платформа берёт холд на сумму потолка до спавна, — но человек, упершийся в свой колпак, и человек, у которого кончились деньги, находятся в разных положениях, и фраза у них разная. Передано оркестратору в канон `docs/architecture/14-api-contract/` (патч применён и удалён, D39.115) §4; в спеке помечено в `EventCeiling` | владелец + платформа | S3 08.08 | +| Ф-30 | **Метка главы без заголовка — голый порядковый номер.** ⚠ **Прежнее основание строки СНЯТО каноном 0.3.0:** запрет синтезировать «Глава {n}» на клиенте отменён — когда у главы нет ни `heading`, ни `number`, клиент ОБЯЗАН сам нарисовать порядковый в локали интерфейса (строка учила обратному по К-3). Сегодня в дереве на таком месте стоит голая позиция в порядке чтения — строка «12» среди названий; правка экрана — при разморозке. НАСТОЯЩИЕ названия глав — движковая половина (строка 160 единого бэклога), а продуктовое слово о том, чем подписывать 2284 одинаковых «Глава N», — по-прежнему В-4 на владельце | разморозка + владелец (В-4) | S3 08.08, кадр `/scale` → канон 0.3.0 | +| ~~Ф-31~~ | ~~**К-13: достижение СОБСТВЕННОГО потолка прогона и исчерпание кредита неразличимы** — `paused_reason` имел ровно одно значение, `credit_exhausted`, и оно про кредит~~ — **ЗАКРЫТА D39.132 п.2а** (`null` на проводе ратифицирован), а кадр `EventCeiling`, на который строка опиралась, снят каноном 0.3.0 целиком. Разведение живёт сегодня в самом каноне: `resume` отвечает разными `cause.code` — `ceiling_reached` («прогон закончен, нужен новый») против `credit_unavailable` («пополнить счёт») | закрыт | S3 08.08 → D39.132 | | Ф-32 | **Замечания читаются пер-КНИЖНО, а показываются пер-главно.** Контракт даёт `GET /books/{id}/notes` и не даёт пер-главной ручки; панель контекста фильтровала по открытой главе на клиенте — **с S3.5 фильтр снят, сводка пер-книжная (замечание владельца 8, строка Ф-38), то есть пер-книжное чтение теперь и показывается пер-книжно**. Пока это дёшево — дефолт страницы 500, а на книге с сотнями замечаний клиент тянет весь список ради трёх строк, и с курсором дотянет ВЕСЬ. Ф-12 обосновывал отказ от виртуализации замечаний тем, что список пер-главный и мал; после этой правки он пер-главный на ЭКРАНЕ, но пер-книжный в ПАМЯТИ. Решать не фронту: либо пер-главная ручка (вопрос автору контракта), либо явный потолок с честным «показаны не все». Обнаружено вторым адверсариальным проходом 08.08 при починке того, что панель «О читаемой главе» показывала замечания всей книги | S6 (читалка) либо раньше, если появится книга с тысячами замечаний | второй проход ревью S3 | | Ф-5 | **Tauri 2.x вторым шагом** — по явным триггерам (трей · глобальные горячие клавиши · распространяемый .exe · офлайн · хранилище учёток ОС); до того — установимое PWA, только браузерные API | триггеры названы | STACK_DECISIONS §6, D39.84 | | Ф-6 | **`@tanstack/react-virtual`** — в резерве, подключать только по замеру (дефолт виртуализации — RAC Virtualizer). **Замер сделан 04.08 (S2) и триггер НЕ наступил:** RAC `Virtualizer` держит 2284 раздела на 31 строке в DOM при медиане кадра 22 мс и 1200 терминов на 30 строках при 16,7 мс. Вторая библиотека виртуализации сейчас была бы платой без выигрыша; пересматривать — если появится список с переменной высотой строки (читалка S6) и `estimatedRowSize` не справится | по замеру; замер 04.08 отрицательный | STACK_DECISIONS §2 | @@ -40,30 +40,30 @@ | Ф-34 | **Светлая тема: цена честно замерена, дёшево не выходит.** Проверено по коду, а не на глаз. Дёшево — только половина: значения цвета лежат в одном месте (`tokens.css`), второй набор под `:root[data-theme='light']` механичен. Дорого — вторая половина: подпись оболочки, снятая с Fleet, это «карточки на почти чёрном фоне», и она не инвертируется значениями — в светлой теме отношения поверхностей приходится ПЕРЕПРИДУМАТЬ (что светлее, панель или геп; чем отделять карточку без теней; что делать с вырезом активной вкладки, который держится на паре «хром ↔ полотно»). Плюс переключатель и его персист, плюс пере-прогон axe по семи маршрутам на второй теме, плюс замок токенов на второй набор. Оценка: отдельный проход масштаба S7, а не хвост фикс-пака. Пункт назван в каркасе настроек честно: «сейчас доступна только тёмная» | S7 либо отдельный пакет | S3.5, замечание 15 | | Ф-35 | **Язык интерфейса: код-половина ЗАКРЫТА 10.08 в S3.7, продуктовая — на владельце.** Сделано: словарь строк `src/i18n/ru.ts` — единственный дом слов интерфейса; механизм отраслевой, не самописка — `@internationalized/string` 3.2.10 (i18n-слой Adobe, на котором react-aria отдаёт свои строки; сравнение с react-intl и lingui — `STACK_DECISIONS.md` §1); ключ типизирован ИЗ каталога, поэтому опечатка роняет `tsc`; выбор языка — стор `src/i18n/language.ts` с персистом, от него же берут локаль `I18nProvider` и весь `Intl` (`showcase/format.ts`); второй язык = второй файл того же вида плюс строка в `language.ts`, ни один компонент не трогается. Гейт против нового хардкода стоит и проверен живым нарушением ДВАЖДЫ: ESLint запрещает текст в разметке по МЕСТУ (ловит и `title="Настройки"`, и `title="Settings"` — алфавит ни при чём), `catalogue.test.ts` держит «код зоны английский» по комментариям, CSS, python и корневым конфигам. ⚠ **Числа приведены к ЗАМЕРУ 14.08 (S4), арбитраж двух носителей:** ключей каталога было **157** (прогон `node -e "import('./src/i18n/ru.ts')…"` на дереве ДО пака — совпало с этой строкой и с живым `ru.ts`; «147» записи S3.7 неверно, но записи хроники не правятся); бандл до пака — **724130 байт** (`npx vite build`, главный чанк), то есть верна запись S3.7 (+7999 от 716131), а стоявшее здесь «723102 (+6971)» ошибочно. После пака S4: каталог **202 ключа**, главный чанк **800 219 байт**, 243 446 gzip (+76 089 к до-пакному — новые примитивы формы, интейк, фикстура мира и сам текст). **Осталось владельцу:** какие языки продукт обещает, и переводы на них — их фронт не выдумывает | решение владельца: список языков | S3.5 → S3.7 10.08 → числа сверены S4 14.08 | | Ф-36 | **Физический масштаб интерфейса на мониторе — ручки нет.** Замечание владельца 1 говорит «великоваты, особенно на 27″ 2K». Замер при вьюпорте референса 1280×764 расхождения не подтвердил: промежутки, ширины панелей, шаг строки и высота вкладки совпадают с Fleet (FRONTEND_PLAN §5.5). Значит речь про ФИЗИЧЕСКИЙ размер на конкретном экране, а он зависит от масштабирования ОС, и лечится он не сдвигом токенов вслепую (это разъедет витрину с референсом), а ручкой «плотность интерфейса» в настройках. Цена ручки названа честно: все размеры сегодня в px, для одного множителя их надо перевести в rem и задать `font-size` на корне — механически, но трогает КАЖДЫЙ размерный токен и все замеренные числа, поэтому отдельным проходом с пере-замером. ⚠ **Дополнено 10.08:** предел меры набора (`--reader-width`), заведённый S3.5 «пока нет ручки», владелец отверг словами «жёстко приклеен к центру, до этого было намного лучше» — токен удалён, набор снова во всю ширину панели. Значит длина строки на 2K остаётся открытой ровно до ручки плотности, и подпирать её геометрией больше нельзя | владелец (В-7) + отдельный проход | S3.5, замечание 1 | -| Ф-37 | **Пометка черновика висит на статусе КНИГИ, а не главы.** Замечание 24 сделано данными контракта: пока прогон не довёл книгу до `ready`, текст помечен как черновой. Но у главы есть своё выполнение (`units_done/units_total`), и глава, уже прошедшая обе волны в незавершённой книге, получает ту же пометку, что и непереведённая. Точнее было бы по главе, и для этого в контракте не хватает одного: `units_done` не разведён по фазам (открытый вопрос К-10 спутника). До ответа взята безопасная сторона — предупредить лишний раз дешевле, чем не предупредить | контракт (К-10) → S6 | S3.5, замечание 24 | +| Ф-37 | **Пометка черновика висит на статусе КНИГИ, а не главы.** Замечание 24 сделано данными контракта: пока прогон не довёл книгу до `ready`, текст помечен как черновой. Но у главы есть своё выполнение (`units_done/units_total`), и глава, уже прошедшая обе волны в незавершённой книге, получает ту же пометку, что и непереведённая. ⚠ **Открытый вопрос К-10, на который строка ждала ответа, ОТВЕЧЕН «НЕ строить» (D39.138):** фазы уходят с провода, и у главы тоже — пометка черновика остаётся КНИЖНОЙ. Хвост строки пере-диспозиционировать при разморозке: безопасная сторона (предупредить лишний раз) осталась взятой намеренно | пере-диспозиция при разморозке | S3.5, замечание 24 → D39.138 | | Ф-38 | **Вкладка «Замечания» защищена сводкой по книге — слово за владельцем.** Замечание 8 («непонятно зачем, всё рисуется в тексте») по пер-ГЛАВНОМУ списку было верным: он дублировал читалку. Вкладка переделана в пер-КНИЖНУЮ сводку со ссылкой в раздел — сценарий «пройтись по всем местам, требующим внимания», который без неё не живёт и который не стоит ни одного лишнего запроса (контракт даёт замечания пер-книжно, Ф-32). Если владелец сценария не признает — вкладка снимается одной строкой в `Context.tsx`, вместе с ней уходит и `notesQuery` с экрана | решение владельца | S3.5, замечание 8 | -| Ф-39 | **Банку не хватает полей для проверки термина, и выдумывать их фронт не стал.** Замечание 7/13 просит на термин род/пол, описание-обоснование и варианты перевода; в контракте (`BankTerm`) их нет — есть `src`, `dst`, `kind`, `status`, `origin`, `sense`, окно `since_chapter`/`until_chapter`. Показано всё, что есть; недостающее оформлено ЧЕРНОВИКОМ правки спеки с пометкой «предложено» (`frontend-PROGRESS.md`, запись S3.5) и не заведено ни в фикстуру, ни в типы — класс ошибки «фикстура выдумала форму» ловился четырежды (Ф-14). Ратифицирует оркестратор | оркестратор + владелец | S3.5, замечания 7 и 13 | -| ~~Ф-40~~ | ~~**Вид поля ввода описан в двух модулях.**~~ — **ЗАКРЫТА 10.08 в S3.7.** Общий модуль `src/ui/Field.module.css` несёт `.input`, `::placeholder`, `:focus` и модификатор `.framed` (граница в покое — отличие формы от фильтра); примитивы остались РАЗНЫМИ, у каждого свой модуль со своей ролью (`TextField` — подпись и подсказка, `FilterField` — раскладка фильтра и гашение родной кнопки очистки). Обе половины лежат в ОДНОМ модуле намеренно: порядок между двумя CSS-модулями — это порядок их импортов, а модификатор обязан идти после базы | закрыт | S3.5 → S3.7 10.08 | -| Ф-41 | **Спойлер банка раскрывается только наведением.** Клавиатура и тач его не раскроют, а перевод остаётся в DOM (скринридер прочитает). Кнопка раскрытия внутри строки списка невозможна: у роли `option` дети презентационные, и вложенный интерактив роняет гейт axe (та же ловушка, что Ф-17). Настоящее лечение — раскрытие на уровне СПИСКА (один переключатель «показывать термины из будущих разделов» над ним) либо экран подписи S5, где строка перестаёт быть строкой списка. ⚠ **Уточнено 10.08 (S3.6):** спойлер снят с ПЕРЕВОДА и поставлен на описание (`sense`) — закрытый перевод делал строку непроверяемой, а таблица существует ради проверки. Второй хвост строки («состояние замещается объяснением с N-го») ЗАКРЫТ: в таблице окно применимости и состояние — разные столбцы. Остался ровно один: раскрытие только наведением | S5 либо раньше по слову владельца | S3.5, ревью точности | +| ~~Ф-39~~ | ~~**Банку не хватает полей для проверки термина, и выдумывать их фронт не стал**~~ (замечания 7/13 просили род/пол, описание-обоснование, варианты перевода; в `BankTerm` их нет) — **ЗАКРЫТА словом владельца 15.08 (D39.136 п.4б): полей `BankTerm` не заводить, `sense` хватает**; его калибровка — строка 178 единого бэклога. Показано было всё, что даёт контракт, а недостающее оформлено черновиком правки спеки и НЕ заведено ни в фикстуру, ни в типы — класс ошибки «фикстура выдумала форму» (Ф-14) | закрыт | S3.5, замечания 7 и 13 → D39.136 | +| ~~Ф-40~~ | ~~**Вид поля ввода описан в двух модулях.**~~ — **ЗАКРЫТА 10.08 в S3.7.** Общий модуль `src/ui/Field.module.css` несёт `.input`, `::placeholder`, `:focus` и модификатор `.framed` (граница в покое — отличие формы от фильтра); примитивы остались РАЗНЫМИ, у каждого свой модуль со своей ролью (`TextField` — подпись и подсказка, `FilterField` — раскладка фильтра). Обе половины лежат в ОДНОМ модуле намеренно: порядок между двумя CSS-модулями — это порядок их импортов, а модификатор обязан идти после базы | закрыт | S3.5 → S3.7 10.08 | +| Ф-41 | **Спойлер банка раскрывается только наведением.** Клавиатура и тач его не раскроют, а перевод остаётся в DOM (скринридер прочитает). Кнопка раскрытия внутри строки списка невозможна: у роли `option` дети презентационные, и вложенный интерактив роняет гейт axe (та же ловушка, что Ф-17). Настоящее лечение — раскрытие на уровне СПИСКА (один переключатель «показывать термины из будущих разделов» над ним) либо экран подписи S5, где строка перестаёт быть строкой списка. Сегодняшнее устройство: спойлер стоит на описании (`sense`), а не на переводе — закрытый перевод делал строку непроверяемой, а таблица существует ради проверки; окно применимости и состояние — разные столбцы | S5 либо раньше по слову владельца | S3.5, ревью точности | | Ф-42 | **У keep-alive нет потолка.** Вкладки центра не размонтируются (в этом и лекарство Ф-19), поэтому каждая закреплённая глава держит смонтированную читалку и свой запрос: замерено адверсариальным ревью — семь вкладок дают семь запросов и семь читалок. На фикстурных главах это ничего не стоит, на настоящих (глава бывает одним блоком в тысячи знаков) — стоит. Лечение из отраслевой практики: вытеснять самые давние закреплённые вкладки сверх N смонтированных, оставляя их в ряду. Порог называть по замеру на настоящей книге, а не на глаз | ⚠ **ЗАМЕРЕНО 14.08 в S4, потолок НЕ ставится.** Прогон в браузере с принудительной сборкой мусора (`page.requestGC()`), `/scale`, 12 закреплённых вкладок: куча 34,4 → 36,7 МБ (**+2,3 МБ, ≈190 КБ на вкладку**), узлов в DOM 1351 → 1571, чтений юнитов 2 → 11. Первый замер без GC давал +62 МБ и был мусором аллокаций — назван, чтобы никто не повторил. Значит память не авария: на настоящей книге (юнит в сотни раз длиннее фикстурного) это сотни килобайт на вкладку. Настоящая цена — СЕТЕВАЯ: каждая открытая вкладка перечитывается на границе стадии (`useRunStream`, кадр `status`), то есть 20 вкладок это 20 чтений на кадр. Потолок ставить не стал: он меняет поведение модели вкладок VS Code (закреплённая вкладка молча теряет содержимое — новый класс сюрприза), а порог всё равно называть по замеру на НАСТОЯЩЕЙ книге, которого у фикстуры нет. Пересмотр — при первой жалобе на память или когда прогон пойдёт на живой платформе | замерено, отложено с числами | S3.5, ревью корректности → S4 14.08 | -| Ф-43 | **У чтения банка нет канала — вся витрина банка стоит на моке.** Банк живёт в приватном SQLite движка, платформе читать его запрещено (D39.85), артефакта экспорта банка нет; компаньон контракта (`14-api-contract/README.md` §3) прямо пишет «строки нет — заводит оркестратор», и в живом `docs/PROGRESS.md` я её не нашёл. Пока канала нет, таблица банка (замечание владельца второго круга 7) работает только на фикстуре, а подписной экран S5 строить не на чем | **оркестратор: завести строку единого бэклога** → бэкенд (экспорт банка) → платформа (проекция) | S3.6 10.08, разбор движка | -| Ф-44 | **Действие «подписать банк» не нарисовано — намеренно.** Модель владельца и движка совпадают: подписывается банк ЦЕЛИКОМ, стоп снимается только когда решён каждый термин (`pipeline/mining.go:201`), в контракте это `POST /runs/{id}/resume` с `409` при неполном наборе. Но за действием стоит механизм подписи (S5), а под ним — отсутствующий канал чтения (Ф-43). Кнопка без действия запрещена нормой зоны, поэтому в S3.6 построен ВИД и честное состояние. ⚠ **Уточнено 10.08 по слову владельца:** счётчики «подписано N / ждут решения N» с экрана СНЯТЫ вместе со столбцом состояния строки — они и были той самой путаницей «подписываем банк или термины» из замечания 20 первого круга. В подвале — размер банка, при сужении «показано N из M», и состояние ВСЕГО банка («банк ждёт подписи») из статуса книги. Рисовать действие — когда есть чем его исполнить | S5, после Ф-43 | S3.6 10.08 | +| Ф-43 | ⚠ **Строка ПРОТУХЛА ОБЕИМИ половинами (сверено деревом 30.08, пинг №20 п.2).** Было: «у чтения банка нет канала, витрина стоит на моке, подписной экран S5 строить не на чем». Стало: движковый сайдкар банка ПОСТРОЕН (`backend/internal/pipeline/bankexport.go`), платформа его декодирует и отдаёт проекцией, у контракта есть `GET /books/{bookId}/bank`. Значит основание «S5 непостроим» и зависимости Ф-44/Ф-46 «после Ф-43» больше не держатся. Осталось: перевести витрину банка с фикстуры на живое чтение при разморозке | разморозка: снос мока банка | S3.6 10.08 → пинг №20 п.2 | +| Ф-44 | **Действие «подписать банк» не нарисовано — намеренно.** Модель ратифицирована D39.144 (слово владельца 16.08): подписывается ВЕСЬ банк ОДНИМ действием — пользователь смотрит черновой банк, правит только то, что захотел, и жмёт один ОК (`POST /runs/{runId}/resume`); нерешённые термины едут как предложил сервис. ⚠ Гейт полноты СНЯТ: `awaiting_bank` с ЛЮБЫМ состоянием решений отвечает `202`, `pending_decisions`/`complete` информационные, пер-термная масс-подпись НЕ строится — движковый флаг стопа прямо об этом говорит (`pipeline/mining.go`, коммент «THE FLAG», D39.42 п.5 · D39.144). Прежняя редакция строки учила обратному («стоп снимается только когда решён каждый термин, 409 при неполном наборе»). Счётчики «подписано N / ждут решения N» сняты с экрана словом владельца 10.08 — они и были путаницей «подписываем банк или термины»; в подвале остались размер банка, «показано N из M» при сужении и состояние ВСЕГО банка из статуса книги. Экран действия — при разморозке | S5 | S3.6 10.08 · пере-читано по D39.144 | | Ф-45 | **Править термин в таблице нельзя — только читать.** Владелец: «разумеется в банке можно подправить какой то термин». Контракт это выражает (`BankDecision`: `promote` с непустым `dst` либо `decline`, частичное накопление на сервере), то есть редактирование строки — это отправка решения, а не PATCH. Экран решений = S5; в S3.6 таблица читающая, и это названо честно, а не замаскировано неактивными полями | S5 | S3.6 10.08, замечание 7 | | Ф-46 | **У таблицы банка нет сортировки, и она не различает канон от предложенного.** Первое: сортировки по столбцу нет — примитив `ui/Table.tsx` на RAC `Table` её допускает (`sortDescriptor`), но проектировать навигацию по банку на 60 фикстурных терминах нельзя. Второе, найдено при снятии столбца подписи (10.08): в банке лежат и уже канонические термины, и свежепредложенные — подписывают, по сути, вторые, а таблица их не различает. Возвращать это как «состояние подписи строки» нельзя (владелец снял его прямо), значит нужен другой различитель, и он продуктовый. Сегодня путей два: поиск по трём полям и фильтр по типу со счётчиками. Оба вопроса упираются в настоящий банк книги (Ф-43) и в слово владельца | после Ф-43 + владелец | S3.6 10.08, замечание 7 | -| ~~Ф-47~~ | ~~**Дефект контракта: `sense` не обязателен, хотя объявлен частью ключа уникальности.**~~ — **ЗАКРЫТА 10.08 в S3.7 по ратификации оркестратора.** В зонной копии `openapi.yaml` поле в `required`, правило пустой строки («различителя нет») записано в описании, `info.version` 0.2.1 → 0.2.2, spectral зелёный, типы перегенерены. ⚠ Поправка к прежней формулировке этой же строки: `?? ''` было НЕ в трёх местах, а в одном (`showcase/Bank.tsx`) — счёт был завышен, проверено грепом. Дифф в канон переносит оркестратор при лендинге | закрыт | S3.6 10.08 → S3.7 10.08 | +| ~~Ф-47~~ | ~~**Дефект контракта: `sense` не обязателен, хотя объявлен частью ключа уникальности.**~~ — **ЗАКРЫТА 10.08 в S3.7 по ратификации оркестратора:** в зонной копии `openapi.yaml` поле в `required`, правило пустой строки («различителя нет») записано в описании, версия 0.2.1 → 0.2.2, spectral зелёный, типы перегенерены | закрыт | S3.6 10.08 → S3.7 10.08 | | Ф-48 | **Перетаскивание границ столбцов снято по замеру, а не по вкусу.** Отраслевая норма таблиц данных (MUI X `flex`+`minWidth`+resize, AG Grid) — колонки тянут рукой; в react-aria это `ResizableTableContainer` + `ColumnResizer`, и оно работает (проверено сценарием: столбец растянут 194→274px). Но A/B-замер драга ПАНЕЛИ с открытым банком на dev-сервере: без ресайза p90 33 мс и 7 длинных кадров, с ресайзом — **p90 83 мс и 44 длинных кадра**: контейнер пересчитывает ширины на каждый кадр изменения своей ширины. Владелец дважды жаловался именно на драг, поэтому выбрана плавность: остались текучие доли (`fr`) с минимумами на столбец. Возвращать — когда банк станет поверхностью РЕДАКТИРОВАНИЯ (S5, там ширины важнее) либо когда у библиотеки появится способ не пересчитывать ширины на ресайз контейнера. ⚠ **База замера УСТАРЕЛА (10.08, четвёртый круг):** «без ресайза p90 33 мс» на текущем дереве не воспроизводится — драг держит **p90 17 мс, 1 длинный кадр из 84**, и A/B по блюру и `contain` числа не двигает. Значит и цену ресайза (83 мс) надо мерить заново от новой базы, а не наследовать: возвращая ресайз, начинать с замера, а не со ссылки на эту строку | S5 либо новая версия библиотеки | S3.6 10.08, A/B-замер; база пере-замерена 10.08 | -| ~~Ф-49~~ | ~~**Текст открытой главы не подписан ни на один сигнал изменения.**~~ — **ЗАКРЫТА 10.08 в S3.7.** Кадр `chapter` инвалидирует чтение юнитов ИМЕННО той главы, которую называет (`keys.units`), кадр `status` — юниты всей книги (`keys.bookUnits`): границы стадий это единственный сигнал, пока `units_done` остаётся единым счётчиком (К-10). Тест — `showcase/useRunStream.test.tsx`, два случая: «кадр главы перечитывает ТУ главу и не трогает соседнюю» (счёт запросов к MSW) и «кадр статуса перечитывает текст всех открытых глав»; оба падают на до-фиксном коде | закрыт | S3.6 10.08 → S3.7 | -| ~~Ф-50~~ | ~~**Кадр потока, пришедший до появления чтения в кэше, теряется молча.**~~ — **ЗАКРЫТА 10.08 в S3.7, обе половины.** (1) Кадр, не нашедший снимка, зовёт `resetQueries` по своему ключу. Именно reset, и это ЗАМЕР, а не вкус: `invalidateQueries` на запросе без данных ничего не делает — библиотека перезапускает чтение в полёте только когда данные уже есть (`Query.fetch`: `state.data !== undefined && cancelRefetch`), то есть возвращает промис того самого устаревшего чтения. (2) Ревизия многостраничного списка — МИНИМУМ из страниц (`client.ts`): рваный список свеж настолько, насколько стара его первая страница. Тесты: «кадр без снимка оставляет след» (счёт запросов) и «рваный список штампуется старшей страницей» (две страницы 1800/2000 → 1800, и `freshest` роняет его против кадра 1900); оба падают на до-фиксном коде | закрыт | S3.6 10.08 → S3.7 | -| ~~Ф-51~~ | ~~**`resync` сбрасывает один гард из двух.**~~ — **ЗАКРЫТА 10.08 в S3.7.** На `resync_required` зовётся `resetQueries` по скоупу КНИГИ (`bookScope` — предикат по второму элементу ключа: id книги стоит там у каждого книжного чтения и больше нигде), то есть отметка ревизии уходит вместе со снимком, а библиотека, usage и другие книги не трогаются. Тесты: «resync выбрасывает книгу из кэша вместе с отметкой» (новая эпоха с НИЗШИМ номером принимается — на до-фиксном коде гард её ронял) и «resync одной книги не трогает чужие скоупы»; оба падают на до-фиксном коде | закрыт | S3.6 10.08 → S3.7 | -| ~~Ф-52~~ | ~~**`--color-danger` не дотягивает до порога нетекста**~~ — **ЗАКРЫТА 14.08 в S4 по слову владельца, и не одним числом, а ПРАВИЛОМ.** Тон поднят минимальным сдвигом при сохранённых тоне и насыщенности (350°, S 0.60): `#b82e45` → **`#bd2f47`**, на полотне панели `#181818` это **3.10:1** против прежних 2.97 (порог нетекста 3:1; для сравнения `--color-note` стоит на 3.07). Токен переехал из «замеренных» в «подобранные по референсу» с числом референса в комментарии — тем же ходом, что `--color-note` в S3.7. Сверх задания поставлен гейт: `tokens.test.ts` считает контраст КАЖДОГО тона состояния (`note`/`ok`/`warn`/`danger`) о полотно и требует 3:1 — axe этого не ловит вовсе (контраст нетекста он не проверяет), и обе просадки нашлись руками, с разницей в пакет. Проверено живым нарушением: на прежнем значении гейт краснеет | закрыт | S3.7 10.08, замер → S4 14.08 | +| ~~Ф-49~~ | ~~**Текст открытой главы не подписан ни на один сигнал изменения.**~~ — **ЗАКРЫТА 10.08 в S3.7.** Кадр `chapter` инвалидирует чтение юнитов ИМЕННО той главы, которую называет (`keys.units`), кадр `status` — юниты всей книги (`keys.bookUnits`): границы стадий это единственный сигнал, пока `units_done` остаётся единым счётчиком. Тест — `showcase/useRunStream.test.tsx`, два случая, оба падают на до-фиксном коде | закрыт | S3.6 10.08 → S3.7 | +| ~~Ф-50~~ | ~~**Кадр потока, пришедший до появления чтения в кэше, теряется молча.**~~ — **ЗАКРЫТА 10.08 в S3.7, обе половины.** (1) Кадр, не нашедший снимка, зовёт `resetQueries` по своему ключу. Именно reset, и это ЗАМЕР, а не вкус: `invalidateQueries` на запросе без данных ничего не делает — библиотека перезапускает чтение в полёте только когда данные уже есть (`Query.fetch`: `state.data !== undefined && cancelRefetch`), то есть возвращает промис того самого устаревшего чтения. (2) Ревизия многостраничного списка — МИНИМУМ из страниц (`client.ts`): рваный список свеж настолько, насколько стара его первая страница. Оба теста падают на до-фиксном коде | закрыт | S3.6 10.08 → S3.7 | +| ~~Ф-51~~ | ~~**`resync` сбрасывает один гард из двух.**~~ — **ЗАКРЫТА 10.08 в S3.7.** На `resync_required` зовётся `resetQueries` по скоупу КНИГИ (`bookScope` — предикат по второму элементу ключа: id книги стоит там у каждого книжного чтения и больше нигде), то есть отметка ревизии уходит вместе со снимком, а библиотека, usage и другие книги не трогаются. Оба теста («новая эпоха с НИЗШИМ номером принимается» и «resync одной книги не трогает чужие скоупы») падают на до-фиксном коде | закрыт | S3.6 10.08 → S3.7 | +| ~~Ф-52~~ | ~~**`--color-danger` не дотягивает до порога нетекста**~~ — **ЗАКРЫТА 14.08 в S4 по слову владельца, и не одним числом, а ПРАВИЛОМ.** Тон поднят минимальным сдвигом при сохранённых тоне и насыщенности (350°, S 0.60): `#b82e45` → **`#bd2f47`**, на полотне панели `#181818` это **3.10:1** против прежних 2.97 (порог нетекста 3:1; для сравнения `--color-note` стоит на 3.07). Токен переехал из «замеренных» в «подобранные по референсу» с числом референса в комментарии. Сверх задания поставлен гейт: `tokens.test.ts` считает контраст КАЖДОГО тона состояния (`note`/`ok`/`warn`/`danger`) о полотно и требует 3:1 — axe этого не ловит вовсе (контраст нетекста он не проверяет), и обе просадки нашлись руками, с разницей в пакет. Проверено живым нарушением | закрыт | S3.7 10.08, замер → S4 14.08 | | Ф-53 | **Находка приёмки 8.1 (HIGH) НЕ ВОСПРОИЗВОДИТСЯ — оставлен сторож, а не механизм.** Утверждение «под `content-visibility: hidden` геометрия нулевая, замер ширин таблицы молча проваливается» проверено прямым замером в браузере: таблица, смонтированная в скрытой keep-alive вкладке `/scale`, держит 116 ячеек и ширины `121·122·133·232` — БАЙТ В БАЙТ те же, что у вкладки, с которой не уходили, и на до-фиксном коде тоже. Причина: запрос геометрии внутри пропущенного поддерева заставляет браузер посчитать раскладку, поэтому `read()` набирает столбцы. Первый заход сессии добавил под эту находку `IntersectionObserver` — механизм СНЯТ, потому что чинить нечего. Сценарий `npm run scenes hidden` (уход с вкладки ДО прихода банка и возврат) оставлен: сам путь реален и не был покрыт ничем. Если владелец увидит дефект руками — здесь лежит и замер, и готовый сценарий, куда его повторить | закрыто замером; пересмотр — по факту от владельца | S3.7 10.08, замер в браузере | | Ф-54 | **При зуме от 125% центральная панель обрезает СВОЁ содержимое, а не сжимается.** Замер сцены `zoom` (вьюпорт в CSS-пикселях = зум браузера): при 1152×720 центр режет 55px своего содержимого, при 960×600 — 98px, при 720×450 — 170px; окно при этом не переливается, три панели стоят в ряд, боковые честно сжимаются (левая доходит до 141px при своём минимуме 220). То есть карточка центра держит ширину, которой у слота уже нет, и первой уходит вторая колонка читалки — перевод. Кадр `--size 720x450 --dpr 1` это показывает глазом. Не чинил и не считаю это своим решением: ширина центра — часть вопроса «плотность интерфейса» (Ф-36, В-7 на владельце), а менять раскладку сверх задания пака — та самая самодеятельность, за которую аудит S3.6 уже наказал. Сцена ЧИСЛО печатает, но не утверждает: гейт, красный по построению, учит пропускать команду | владелец (В-7) + Ф-36 | S3.7 10.08, замер сцены `zoom` | | Ф-55 | **Стор языка есть, переключателя нет — и это не забывчивость.** `src/i18n/language.ts` держит выбор с персистом и отдаёт локаль каталогу, `I18nProvider` и `Intl`; `choose()` при этом не зовёт ни один экран, потому что содержимое настроек строит S7, а промт пака прямо запрещает его трогать. Пункт «Язык интерфейса» в каркасе настроек честно говорит «сейчас интерфейс только русский». Строка держит хвост, чтобы S7 не завёл ВТОРОЕ хранилище выбора рядом с этим | S7 | S3.7 10.08 | -| Ф-56 | **Конца интейка никто не объявляет — клиент опрашивает.** Живой канал принадлежит ПРОГОНУ (`GET /runs/{runId}/events`), а книга в `uploading`/`parsing` прогона не имеет вовсе: значит «разбор закончился» не приходит ничем. S4 закрыла дыру опросом (`src/api/queries.ts`, `intakePollMs = 3000`, только пока книга в интейке — вместе со статусом останавливается и опрос) плюс перечитыванием дерева на выходе из интейка (`useIntakeEnd.ts`). Это лечение, а не решение: правильным был бы кадр (или отдельный канал) о конце разбора, и это вопрос владельцу контракта, а не фронту. Пока опрос стоит, у него есть цена: чтение библиотеки раз в три секунды на всё время разбора (минуты на настоящей книге) | оркестратор → контракт | S4 14.08, построено на опросе | -| Ф-57 | **Какие пары языков продукт принимает — контракт не говорит, и форма загрузки предлагает свой список.** `src/showcase/languages.ts` держит `zh·ja·en → ru` как ДАННЫЕ клиента, имена считает `Intl.DisplayNames`. Это честная слабость интейка: что деплой умеет переводить — свойство деплоя (движок читает пару из langpack), и список на клиенте соврёт в тот день, когда деплой поедет с другим набором. Кода на замену немного (одна ручка + чтение вместо константы), но ручки в контракте нет: `POST /books` принимает любой `LangCode`, а отказ по неподдерживаемой паре не выражен ни одним классом ответа. Черновик правки спеки не писала, потому что решение продуктовое: обещать пары списком или отвечать отказом | оркестратор + владелец | S4 14.08, форма интейка | +| Ф-56 | **Конца интейка никто не объявляет — клиент опрашивает.** В контракте 0.2.3 живой канал принадлежал ПРОГОНУ (`GET /runs/{runId}/events`), а книга в `uploading`/`parsing` прогона не имеет вовсе: «разбор закончился» не приходило ничем. S4 закрыла дыру опросом (`src/api/queries.ts`, `intakePollMs = 3000`, только пока книга в интейке) плюс перечитыванием дерева на выходе (`useIntakeEnd.ts`). Цена опроса названа: чтение библиотеки раз в три секунды на всё время разбора — минуты на настоящей книге. ⚠ **ОТВЕТ ПРИШЁЛ (Б-6а, канон 0.3.0+):** поток перевешен на КНИГУ — `GET /books/{bookId}/events` с кадрами конца разбора и конца прогона и `204` на завершённом. Опрос снимается при разморозке | разморозка: снять опрос, перейти на книжный поток | S4 14.08 → канон | +| Ф-57 | **Какие пары языков продукт принимает — форма загрузки предлагает свой список.** `src/showcase/languages.ts` держит `zh·ja·en → ru` как ДАННЫЕ клиента, имена считает `Intl.DisplayNames`. Это честная слабость интейка: что деплой умеет переводить — свойство деплоя (движок читает пару из langpack), и список на клиенте соврёт в тот день, когда деплой поедет с другим набором. ⚠ **ОТВЕТ ПРИШЁЛ (Б-2, канон 0.3.0+):** `GET /capabilities` отдаёт пары со статусом, порог интейка, форматы, размеры страниц и версию контракта — `languages.ts` при разморозке становится ЧТЕНИЕМ, а не константой | разморозка: `languages.ts` на `/capabilities` | S4 14.08 → канон | | Ф-58 | **RTL не проверен ни разу, а триггер наступил.** Условие, записанное S1 («когда появится `I18nProvider`»), исполнилось в S3.7: провайдер стоит, локаль берётся из стора, `` следует выбору. Чего нет — ни одного прогона с языком письма справа налево: примитивы react-aria логическое направление поддерживают, а НАША раскладка (фиксированное место бейджа, обрезка вкладок, инсеты панелей, стрелка `zh → ru` в статус-полосе) написана в физических терминах местами. Цена вопроса неизвестна до замера; сам замер дешёвый — второй каталог-заглушка плюс `dir="rtl"` и прогон сцен. Делать, когда владелец назовёт второй язык (В-9): до того это проверка обещания, которого продукт не давал | владелец (В-9) → отдельный проход | аудит-свип 14.08 → S4 | -| Ф-59 | **Спойлер банка достаётся поиском по странице — ЗАМЕРЕНО.** Размытие глифов (`filter: blur`) прячет описание от глаза, но текст остаётся текстом страницы. Прогон в браузере (`/showcase`, вкладка «Банк»): закрытое описание «главный герой» лежит в `document.body.innerText`, и `window.find()` его находит — то есть Ctrl+F подсвечивает закрытую ячейку. Скринридер отрезан честно (`aria-hidden`), выделение мышью пусто, и под `@media print` размытие СОХРАНЯЕТСЯ (замер: `blur(3px)`), так что глазами в PDF описание тоже закрыто — но текстовый слой PDF строится из DOM, и там строка остаётся (это следствие, а не мой замер). Настоящее лечение продуктовое: спойлер это ЗАЩИТА (тогда описание не должно приезжать в DOM до раскрытия — нужен признак «показывать смыслы» на чтении банка, то есть правка контракта) или ВЕЖЛИВОСТЬ (тогда всё правильно, и цена названа). Вопрос владельцу: **В-10** | владелец (В-10) → контракт | аудит-свип 14.08 → замер S4 14.08 | +| ~~Ф-59~~ | ~~**Спойлер банка достаётся поиском по странице — ЗАМЕРЕНО.**~~ — **ЗАКРЫТА решением владельца 16.08 (D39.138 п.2к): спойлер это ВЕЖЛИВОСТЬ, сделанного достаточно, цена названа.** Сам замер (S4, браузер, `/showcase`, вкладка «Банк»): размытие глифов (`filter: blur`) прячет описание от глаза, но текст остаётся текстом страницы — закрытое описание лежит в `document.body.innerText`, `window.find()` его находит, то есть Ctrl+F подсвечивает закрытую ячейку; скринридер отрезан честно (`aria-hidden`), выделение мышью пусто, под `@media print` размытие СОХРАНЯЕТСЯ (`blur(3px)`), но текстовый слой PDF строится из DOM, и там строка остаётся | закрыт | замер S4 14.08 → D39.138 | | Ф-60 | **Канвас-замер ширин не знает языка строки.** Ширины столбцов банка меряются канвасом одним шрифтом на весь набор (`ui/Table.tsx`), а при ДВУХ CJK-лицах в стеке один и тот же код-пойнт рисуется по-разному: японская и китайская гарнитуры дают разную метрику для общего иероглифа. Сегодня это спит: пара одна (`zh→ru`), лицо одно. Сработает в день, когда в наборе окажутся и `zh`, и `ja` — то есть ровно на второй паре, которую движок уже умеет. Лечение: мерить с тем же `lang`, что у ячейки (канвасу `lang` не передашь — придётся мерить в DOM либо держать по замерщику на язык), и это отдельный проход с пере-замером цены | триггер: вторая пара в одной библиотеке | аудит-свип 14.08 → S4 | -| Ф-61 | **Платформа отвечает по-английски, а интерфейс русский.** Контракт кладёт фразу отказа на СЕРВЕР (`Problem.title`/`detail` — «product language», клиент показывает как пришло), и клиент так и делает. Но живая платформа шлёт `"The file is larger than this service accepts"`, а рядом стоит наш русский совет — на одном экране два языка. Ни языка запроса (`Accept-Language`), ни требования локализовать эти фразы контракт не несёт. Три пути, и выбор не фронта: (а) платформа локализует по `Accept-Language`; (б) контракт даёт машинный код класса отказа, а фразу пишет клиент (как уже сделано для `RejectReason`); (в) продукт принимает английские фразы отказов. Фронт временно берёт свою фразу только когда `problem+json` не пришёл вовсе | оркестратор + владелец | S4 14.08, интейк | -| Ф-62 | **Переименовать книгу нечем — у книги на всей поверхности контракта нет ни одной записи.** `POST /books` кладёт название один раз (рукой или из имени файла, 0.2.3), дальше только чтения: `GET /books/{bookId}` и ни одного `PATCH`/`PUT`. Найдено адверсариальным ревью пака S4: подсказка формы обещала «название можно будет поправить позже» — обещание, которого контракт не даёт; фраза исправлена, обещание снято. Вопрос не фронта: нужна ли правка метаданных книги вообще (название, жанр, пара языков) и в какой форме — ручка или пере-загрузка. Пока её нет, ошибку в названии лечит только удаление и повторная загрузка, а ручки удаления в контракте тоже нет | оркестратор + владелец | S4 14.08, адверсариальное ревью | +| Ф-61 | **Платформа отвечает по-английски, а интерфейс русский.** Контракт 0.2.3 клал фразу отказа на СЕРВЕР (`Problem.title`/`detail` — «product language», клиент показывает как пришло), и живая платформа слала `"The file is larger than this service accepts"` рядом с русским советом — два языка на одном экране. ⚠ **РЕШЕНО владельцем, вариант B (D39.138 §8 п.4; требование интернациональности подтверждено D39.176 п.4):** на проводе машинный `code` + `request_id`, `title`/`detail` developer-facing, а ФРАЗУ рисует клиент по словарю кодов и локали — таблица `AddBook.tsx:282-288` становится нормой, а фолбэки `AddBook.tsx:289` (`problem?.detail ?? problem?.title`), `Loaded.tsx:66` и `RunStart.tsx:188` при разморозке СНИМАЮТСЯ. Карта «код → фраза» — приложение А компаньона контракта | разморозка: словарь кодов вместо серверной фразы | S4 14.08 → D39.138 | +| Ф-62 | **Переименовать книгу нечем.** В 0.2.3 `POST /books` клал название один раз (рукой или из имени файла), дальше только чтения. Найдено адверсариальным ревью S4: подсказка формы обещала «название можно будет поправить позже» — обещание, которого контракт не давал; фраза исправлена, обещание снято. ⚠ **ОТВЕТ ПРИШЁЛ (Б-5, канон 0.3.0+):** `PATCH /books/{bookId}` ТОЛЬКО с `title` (409 при живом прогоне) и `DELETE`; правка — отображение, до движкового брифа не доезжает. Плюс слово владельца 16.08 (Б-23): жанр из продукта УХОДИТ совсем — поле формы снять при разморозке | разморозка: правка названия и удаление на живых ручках | S4 14.08 → канон | | Ф-63 | **Рамка поля ввода на модальной поверхности — 1.20:1, ниже порога 1.4.11.** Замер S4 (найдено адверсариальным ревью на новом чекбоксе, у которого рамка — единственный носитель границы; там починено локально на `--color-text-muted`, 3.29:1): `--color-border` `#2e2e2e` о `--color-elevated` `#202020` даёт **1.20:1** при пороге 3:1 для границ элементов управления. Тот же токен несёт рамку ВСЕХ полей формы (`ui/Field.module.css`, модификатор `.framed`), рамку самого модального окна и разделители секций, поэтому одним значением это не чинится: поднять токен — перекрасить всю оболочку, принятую владельцем по референсу (S3.6). Правильный ход — отдельный токен границы для того, что является КОНТРОЛОМ (поле, чекбокс), и он у него уже есть в частном виде. Отложено сознательно: перекраска рамок — это вид, а вид принимает владелец | владелец + отдельный проход | S4 14.08, адверсариальное ревью | diff --git a/frontend/docs/FRONTEND_PLAN.md b/frontend/docs/FRONTEND_PLAN.md index 5de59768..842217c4 100644 --- a/frontend/docs/FRONTEND_PLAN.md +++ b/frontend/docs/FRONTEND_PLAN.md @@ -1,23 +1,20 @@ # План фронта — как пишется интерфейс TextMachine -> Этап **S0** сессии фронта: план до первой строки кода (`FRONTEND_SESSION_PROMPT.md` §7.2). -> Ландится отдельным коммитом. Правят следующие фронт-сессии; при конфликте с -> `STACK_DECISIONS.md` побеждает STACK_DECISIONS (там ратифицированы пины), при конфликте -> с промтом сессии — промт (там ратифицирован продукт). +> Как пишется фронт: пины, карта `src/`, правила в проверяемой форме, протокол скриншот-цикла, +> замеры референса. Правят фронт-сессии; при конфликте с `STACK_DECISIONS.md` побеждает +> STACK_DECISIONS (там ратифицированы пины), при конфликте с промтом сессии — промт (там +> ратифицирован продукт). Статус этапов — `frontend-PROGRESS.md`, очередь этапов — `BACKLOG.md` Ф-1. ## 0. Границы -Эта сессия делает **S0 (план) и S1 (инструменты, скриншот-цикл, `tokens.css`, витрина)** и -останавливается на витрине. Экраны — S2–S7, по одной сессии на этап (`BACKLOG.md` Ф-1). - Зона записи — только `frontend/`. `backend/`, `platform/`, `docs/`, `eval/` — read-only. ### 0.1. Канон: что читать перед чем Фронт живёт в проекте с ратифицированным контрактом, и половина ответов на вопросы «как правильно» -уже написана — не здесь. Опыт S0/S1: два дефекта пришли ровно из непрочитанного канона (мокался -не тот уровень контракта; смысл двух вердиктов взят по имени причины вместо доккоммента), оба -нашлись бы за десять минут чтения. Ссылки, а не пересказ — пересказ протухает: +уже написана — не здесь. Таблица заведена после того, как два дефекта зоны пришли ровно +из непрочитанного канона (разбор — `frontend-PROGRESS.md`, запись про карту канона). +Ссылки, а не пересказ — пересказ протухает: | Читать | Зачем фронту | Когда | |---|---|---| @@ -25,7 +22,7 @@ | [../../docs/README.md](../../docs/README.md) | карта: где что лежит и что чем перекрыто | первым делом | | [../../docs/product-requirements.md](../../docs/product-requirements.md) | реестр ПТ-1..ПТ-34; **ПТ-33 и ПТ-34 — жёсткие инварианты интерфейса**, ПТ-21 задаёт якорь чтения | до первого экрана | | [../../docs/research/23-engine-platform-seam.md](../../docs/research/23-engine-platform-seam.md) | форма шва движок↔платформа; `docs/README.md` требует читать его **перед любым кодом стыка** | до любого кода данных | -| CURRENT-STATE + единый бэклог в [../../docs/PROGRESS.md](../../docs/PROGRESS.md) | что движок обязан отдать фронту и чего ещё нет. ⚠ **Актуализировано S4 14.08:** строка 95 (контракт API) ЗАКРЫТА ратификацией D39.99, строки 99/100/103 построены (D39.122/131) — таблица ниже больше не подаёт их как «чего нет»; живые для фронта — **101** (машиночитаемая таблица подписи, вход S5) и **169** (экспорт банка) | перед планированием этапа | +| CURRENT-STATE + единый бэклог в [../../docs/PROGRESS.md](../../docs/PROGRESS.md) | что движок обязан отдать фронту и чего ещё нет. ⚠ Номера строк из прежних редакций этого плана (95, 99–103) закрыты и в бэклоге больше не стоят — искать грепом по слову, а не по номеру | перед планированием этапа | | [STACK_DECISIONS.md](STACK_DECISIONS.md) §5 | транспорт до фронта и правила стрима | до слоя данных | | [api-contract/openapi.yaml](api-contract/openapi.yaml) + компаньон рядом с каноном (`../../docs/architecture/14-api-contract/README.md`) | **ратифицированный контракт** — форма данных, коды ответов, словари. ⚠ Заменил `API_CONTRACT_INPUT.md`: тот под баннером «исполнено» и живым входом больше не является | до слоя данных и при каждой правке формы | | [../../docs/glossary.md](../../docs/glossary.md) | жаргон проекта (D-номер, банк, голден, юнит) | при первом непонятном слове | @@ -39,10 +36,9 @@ **С движком — никогда.** Это не стилистика, а ратифицированный анти-паттерн: движок — CLI-процесс на прогон под эксклюзивным локом, его SQLite платформой не читается, HTTP внутрь него не тащится -(`research/23` §4, §0). Между фронтом и движком стоит платформа. ⚠ **Актуализировано S4 14.08:** -«у платформы пока ноль строк кода» устарело — P5 принята D39.130, живы вход OIDC, библиотека, -кредитный леджер, раннер и `POST /books`; чего у неё нет, смотреть в `../../platform/BACKLOG.md`, -а не здесь. +(`research/23` §4, §0). Между фронтом и движком стоит платформа. Что в ней построено и чего ещё +нет — `../../platform/BACKLOG.md` и `../../platform/docs/platform-PROGRESS.md`; копии её состояния +здесь нет намеренно, она отставала на паки. Проводов два, и их легко перепутать: @@ -66,12 +62,9 @@ Строка «= latest» означает: на 02.08.2026 эта версия и есть последняя опубликованная. Единственное намеренное отставание от latest — TypeScript (см. сноску). -**S2 (04.08) поставила три ратифицированных пина, новых не заводила:** `react-aria-components@1.20.0`, -`react-resizable-panels@4.12.2`, `zustand@5.0.14` — версии перепроверены `npm view` в день установки, -все три по-прежнему latest, `npm audit` — 0 уязвимостей. API `react-resizable-panels` сверен -не по статьям, а по `.d.ts` в поставке: `Group`/`Panel`/`Separator` и `useDefaultLayout` на месте, -`onLayoutChange` помечен deprecated в пользу `onLayoutChanged` — берём второй, как и записано -в `STACK_DECISIONS.md` §2. +⚠ API `react-resizable-panels` сверен по `.d.ts` в поставке, а не по статьям: `Group`/`Panel`/`Separator` +и `useDefaultLayout` на месте, `onLayoutChange` помечен deprecated в пользу `onLayoutChanged` — +берём второй (`STACK_DECISIONS.md` §2). ### Рантайм @@ -80,7 +73,7 @@ | `react` | `19.2.8` | 2026-07-21 | ядро; = latest, React 20 не существует | | `react-dom` | `19.2.8` | 2026-07-21 | обязан совпадать с `react` версия-в-версию | | `react-router` | `8.3.0` | 2026-07-22 | библиотечный (data) режим: маршрут на экран — несущая правила «каждый экран открывается в изоляции» (§3.10). Требует Node ≥ 22.22 | -| `@tanstack/react-query` | `5.101.4` | 2026-07-21 | всё серверное состояние: прогон, книги, банк. Мажор 5 актуален. **Поставлен S3 08.08**, версия ратифицирована ещё в `STACK_DECISIONS` §1 и перепроверена live в день установки — по-прежнему latest, «v6» на npm это svelte-адаптер. Зачем нам: серверное состояние живёт ровно в одном месте, и поток пишет в тот же кэш, что читает экран — иначе прогресс существовал бы в двух копиях. Рефетч по возврату фокуса оставлен ВКЛЮЧЁННЫМ намеренно: это ровно та гонка, ради которой контракт завёл ревизию, и выключить его значило бы спрятать гонку вместо того, чтобы её обработать | +| `@tanstack/react-query` | `5.101.4` | 2026-07-21 | всё серверное состояние: прогон, книги, банк (мажор и ловушка «v6» — `STACK_DECISIONS.md` §1). Зачем нам: серверное состояние живёт ровно в одном месте, и поток пишет в тот же кэш, что читает экран — иначе прогресс существовал бы в двух копиях. Рефетч по возврату фокуса оставлен ВКЛЮЧЁННЫМ намеренно: это ровно та гонка, ради которой контракт завёл ревизию, и выключить его значило бы спрятать гонку вместо того, чтобы её обработать | | `zustand` | `5.0.14` | 2026-05-28 | состояние интерфейса (свёрнутость панелей, активные вкладки). Только с селектором или `useShallow` | | `react-aria-components` | `1.20.0` | 2026-07-31 | ЕДИНСТВЕННАЯ библиотека примитивов: `Table`+`Virtualizer` (банк), `Tree` (главы), `Tabs/Menu/Dialog/Popover/Tooltip` (оболочка), `I18nProvider` (русские служебные строки). Стилей не навязывает. Импорт разрешён только внутри `src/ui/` | | `react-resizable-panels` | `4.12.2` | 2026-07-12 | три панели Fleet. API v4: `Group`/`Panel`/`Separator` — старые имена `PanelGroup`/`PanelResizeHandle` мертвы | @@ -120,8 +113,8 @@ CJK-шрифт не ставим: иероглифы отдаём системн | `happy-dom` | `20.11.1` | 2026-07-22 | DOM для контракт-теста токенов: в отличие от jsdom считает каскад CSS-переменных | | `@testing-library/react` | `16.3.2` | 2026-01-19 | рендер компонентов в тестах | | `@playwright/test` | `1.62.1` | 2026-07-30 | скриншот-цикл и сверка вычисленных токенов в настоящем Chromium | -| `msw` | `2.15.0` | 2026-07-08 | моки сети. Пин ратифицирован при D39.84, **установлен 04.08** ради proof-of-form контракта; **в S3 (08.08) развёрнут в полный набор хендлеров и в браузерный воркер.** Замер, сделанный ДО того, как на нём что-то построили: сервис-воркер MSW перехватывает `EventSource` в живом Chromium и отдаёт потоковое тело кадр за кадром, `lastEventId` заполняется. В тестовом DOM `EventSource` нет вовсе — отсюда развод «разбор кадра отдельно от транспорта» (`src/api/stream.ts`). `public/mockServiceWorker.js` — вывод `msw init`, вендорный файл: исключён из prettier и eslint | -| `@stoplight/spectral-cli` | `6.16.3` | 2026-08-03 | линт контракта шестым шагом `npm run check`: битые `$ref`, дубли `operationId`, ответы без описания. Порог строгий (`--fail-severity=warn`) — предупреждение валит команду так же, как ошибка | +| `msw` | `2.15.0` | 2026-07-08 | моки сети, пин ратифицирован при D39.84. Замер, сделанный ДО того, как на нём что-то построили: сервис-воркер MSW перехватывает `EventSource` в живом Chromium и отдаёт потоковое тело кадр за кадром, `lastEventId` заполняется. В тестовом DOM `EventSource` нет вовсе — отсюда развод «разбор кадра отдельно от транспорта» (`src/api/stream.ts`). `public/mockServiceWorker.js` — вывод `msw init`, вендорный файл: исключён из prettier и eslint | +| `@stoplight/spectral-cli` | `6.16.3` | 2026-08-03 | линт контракта шагом `npm run check` (внутри `npm run contract`): битые `$ref`, дубли `operationId`, ответы без описания. Порог строгий (`--fail-severity=warn`) — предупреждение валит команду так же, как ошибка | | `openapi-typescript` | `7.13.0` | 2026-02-11 | генерит `src/api/schema.ts` из `docs/api-contract/openapi.yaml`. Типы перестают быть авторским пересказом контракта: расходиться им физически нечем. ⚠ Пир `typescript@^5.x` против нашего TS 6 — снят точечным `overrides`, обоснование и проба — `BACKLOG.md` Ф-23 | | `globals` | `17.8.0` | 2026-07-26 | наборы глобалов для flat-config | @@ -197,7 +190,7 @@ CJK-шрифт не ставим: иероглифы отдаём системн | 15 | Доступность проверяется машиной, а не глазами владельца | **машинно**: axe-core прогоняется в `npm run shot` по каждому маршруту и валит команду; список маршрутов скрипта сверяется с `routes.tsx` тестом (`src/routes.test.ts`) — иначе новый экран просто не попадал в прогон, а команда возвращала успех. Контраст вынесен в отчёт: палитра снята с Fleet замером и принята владельцем, менять её под порог WCAG — отдельное решение (`BACKLOG.md` Ф-11) | | 17 | Пара языков живёт в данных, а не в разметке | **машинно**: `src/generality.test.ts` запрещает литерал `lang="…"` в TSX. Это ревью-вопрос канона в исполняемой форме («заработает ли пара, которой в репо ещё нет, без правки кода?» — `CLAUDE.md` §2). Найдено ревью S1 в живом коде: `lang="zh"` пережил бы приезд ja→ru молча, и кандзи отрисовались бы китайскими начертаниями | | 16 | Плавающий промис не проходит | **машинно**: `tseslint.configs.recommendedTypeChecked` с `projectService`. Проверено: `load()` без `await` роняет линт | -| 13 | Инлайновый стиль — только литерал объекта прямо в атрибуте, и только с ключами-CSS-переменными | **машинно**: `no-restricted-syntax` запрещает сам атрибут `style` и разрешает единственную форму. Перечислять формы записи оказалось бесполезно: из одиннадцати способов записать то же самое ловилось три. ~~Остаточная дыра — JSX-спред~~ — закрыта в S2 с другой стороны: запрещено само свойство `style` в объектных литералах, то есть носитель ловится там, где он собран. Плюс значение CSS-переменной обязано быть числом или `var(--…)` | +| 13 | Инлайновый стиль — только литерал объекта прямо в атрибуте, и только с ключами-CSS-переменными | **машинно**: `no-restricted-syntax` запрещает сам атрибут `style` и разрешает единственную форму. Перечислять формы записи оказалось бесполезно: из одиннадцати способов записать то же самое ловилось три. JSX-спред закрыт с другой стороны — запрещено само свойство `style` в объектных литералах, то есть носитель ловится там, где он собран. Плюс значение CSS-переменной обязано быть числом или `var(--…)` | | 18 | Библиотека примитивов видна ровно из одного места | **машинно**: `no-restricted-imports` на `react-aria-components` с исключением для `src/ui/**`. Две библиотеки примитивов означали бы два focus-scope и два портальных менеджера (`STACK_DECISIONS.md` §2), а один вход делает замену библиотеки правкой одной папки | | 19 | Отступы, промежутки и радиусы — только из токенов | **машинно** (Ф-4, включён в S2): stylelint `declaration-property-value-allowed-list` на `/^(padding\|margin)(-\|$)/`, `/^(gap\|row-gap\|column-gap)$/`, `/^border-.*radius$/`. Значение — последовательность `var(--…)`; `0`, `auto`, `inherit` легальны, `calc()` намеренно нет | | 20 | Числа, которые нужны коду, а не стилям, не заводят вторую истину | **машинно**: `src/tokens/measures.ts` держит их одним объектом (высота строки для виртуализатора, ширины панелей для `react-resizable-panels`), а `tokens.test.ts` сверяет каждое значение с одноимённым токеном. Без этого высота строки существовала бы в двух местах и разъезжалась молча | @@ -206,8 +199,8 @@ CJK-шрифт не ставим: иероглифы отдаём системн добавление нового состояния главы или нового вида замечания правит **один** файл. Правит три — структура неверна, переделать до движения дальше. -Ответ этой структуры (⚠ актуализировано S4 14.08 — `src/api/types.ts` удалён ещё в S3, типы -приходят из спеки): состояние книги живёт **одной записью в `src/api/vocabulary.ts`** — там же его +Ответ этой структуры (типы приходят из спеки, авторского файла типов у зоны нет): +состояние книги живёт **одной записью в `src/api/vocabulary.ts`** — там же его слово, тон и то, что с ним можно делать (`startable`, `intake`); экраны эту запись читают и ничего о списке значений не знают. Цвет — токен. Новый вид замечания — запись в карте видов, компонент выноски не трогается. Проверено S4: добавление свойства «книгу в этом состоянии можно запустить» @@ -240,11 +233,11 @@ SSE-соединение открытым ПО ПОСТРОЕНИЮ — сеть на давно сошедшемся экране. У маршрута `/loading` финальная картинка — само ожидание, поэтому он перечислен в `PENDING_ROUTES` скрипта и снимается на `loading`. -**Маршрутов стало семь, и каждый — свой мир фикстур** (S3): `/showcase` трудный случай · -`/scale` длинный хвост · `/empty` пусто · `/loading` ожидание · `/error` отказ платформы · -`/offline` живой поток потерян при рабочих чтениях · `/partial` отказал ОДИН рид при рабочих -остальных. Каждый получает свой кадр и свой прогон axe: -ветка, которую не открыть по прямой ссылке, не проверяется ничем. +**У каждого маршрута свой мир фикстур.** Список маршрутов выводится из `src/api/scenarios.ts` +и замкнут со вторым списком `scripts/shot.mjs` тестом `src/routes.test.ts`; что означает каждый +мир — в шапке `src/mock/worlds.ts`. Прозаической копии списка здесь нет намеренно: она отставала +на этап. Каждый маршрут получает свой кадр и свой прогон axe: ветка, которую не открыть +по прямой ссылке, не проверяется ничем. **Что делаю я после команды — обязательная часть цикла, а не опция:** открываю PNG инструментом чтения файлов, смотрю на него, кладу рядом `references/fleet.png`, называю расхождения словами, @@ -378,20 +371,18 @@ rm -f *.deb && cd .. не стали: снимок должен показывать то, что рисует настоящий браузер. 2. **Колонка оригинала приглушена** (`--color-text-secondary`) — это решение, а не замер: во Fleet на этом месте подсвеченный код. Пересмотреть на S6, когда читалка будет настоящей. -3. ~~**`+` в конце рядов вкладок** у Fleet есть, у нас нет~~ — **закрыто в S2 (04.08).** `+` стоит - в ряду вкладок левой панели и запускает «Добавить книгу» (решение владельца 04.08); в центре - и справа его нет, потому что наборы вкладок там фиксированные и кнопка вышла бы декоративной. -4. ~~**Наведение и прочие интерактивные состояния** витриной не проверены~~ — **закрыто в S2:** - состояния сняты прогоном браузера, а не статичным кадром. `--color-raised` подтверждён - на наведении кнопки, вкладки и строки списка. -5. **Боковые панели — пиксели, центр — доля.** Решено в S2 замером и продуктом: боковые получают +3. **`+` стоит только в ряду вкладок левой панели** и запускает «Добавить книгу» (решение + владельца 04.08); в центре и справа его нет — наборы вкладок там фиксированные, и кнопка + вышла бы декоративной. Интерактивные состояния снимаются прогоном браузера, а не статичным + кадром: `--color-raised` подтверждён на наведении кнопки, вкладки и строки списка. +4. **Боковые панели — пиксели, центр — доля.** Решено в S2 замером и продуктом: боковые получают `groupResizeBehavior: "preserve-pixel-size"` и стартуют с замеренных 320px, центр — `preserve-relative-size`. На 1920 всю лишнюю ширину забирает читалка, а справочные колонки остаются той ширины, под которую нарисованы. Библиотека требует хотя бы одну панель с долевым поведением — ею и оказывается центр. Минимум и максимум боковой — новые токены `--panel-side-min`/`--panel-side-max`; минимум центра — та же ширина, что у боковой панели (читалка уже боковой панели бессмысленна). -6. **Свёрнутая панель уходит из раскладки вместе со своим разделителем**, а не сжимается +5. **Свёрнутая панель уходит из раскладки вместе со своим разделителем**, а не сжимается `collapsible`-ом до нуля: при сжатии на её месте оставался бы промежуток разделителя, и поле оболочки переставало быть замеренными 8px. Размеры при этом не теряются — `useDefaultLayout` хранит раскладку по СОСТАВУ видимых панелей (ключ storage = id группы + id панелей), и это @@ -426,163 +417,94 @@ rm -f *.deb && cd .. - **`import.meta.glob` по `*.module.css` брать с `?raw`, а не `?inline`:** `?inline` отдаёт уже скомпилированный CSS с хешированными именами (`._shell_1abc_1`), и сверять с ним имена из TSX бессмысленно. -### 5.4. Что именно проверено живым нарушением +### 5.4. Гейты: что проверено живым нарушением и чего они не видят + +> Прежние **§5.4.1–5.4.3** (перечни форм по сессиям) слиты СЮДА: ссылки на них из кода +> (`src/generality.test.ts`) и из зонного журнала резолвятся в этот раздел. Формулировка «гейты проверены живым нарушением» без перечня форм — ловушка: она звучит как -машинная гарантия, а покрывает ровно те входы, которые придумал автор. Ниже полный список -проверенных форм; каждая роняла проверку, после отката дерево зелёное. +машинная гарантия, а покрывает ровно те входы, которые придумал автор. Поэтому граница названа +в обе стороны. **Метод, обязательный к повторению:** формы прогоняются разом одним временным +файлом, ожидание записано против каждой строки, файл уносится после прогона, откат — копией +файла, а НЕ `git checkout` (в дереве лежат незакоммиченные правки чужих зон); после отката +дерево зелёное. -**CSS:** hex-литерал в модуле · hex в сокращённой записи `border` · `rgb()` · `color()` -в сокращённой записи · `font-size` числом · именованное отключение правила -(`/* stylelint-disable color-no-hex */`) · **голое** `/* stylelint-disable */` в шапке файла. +**Что ловится — по швам** (полные перечни форм каждого захода — в истории git этого файла; +здесь классы, потому что список форм растёт, а классы держат): -**TSX:** hex всех четырёх длин (`#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`) · цветовые функции -включая `color()` и `light-dark()` · одиннадцать форм инлайнового стиля (литерал, спред, -вынесенная переменная, приведение `as`, свойство объекта, вызов фабрики, тернарник, -вычисляемый ключ, JSX-спред, смешанный литерал) · импорт глобального CSS мимо `main.tsx` · -предупреждение линта при `--max-warnings 0`. +- **цвет и размер в CSS:** hex во всех записях, включая сокращённую `border`; `rgb()`/`color()`; + системный и именованный цвет в `outline`/`caret-color`/`box-shadow`; `font-size` числом; + `padding`/`margin`/`gap`/`border-radius` литералом; `calc()` — намеренно тоже; отключение + правила комментарием, включая **голое** `/* stylelint-disable */` в шапке файла; +- **цвет и размер в TSX:** hex всех четырёх длин, цветовые функции включая `light-dark()`, + именованные и системные цвета в `fill`/`stroke`/`color`, одиннадцать форм инлайнового стиля + (литерал, спред, вынесенная переменная, приведение `as`, свойство объекта, вызов фабрики, + тернарник, вычисляемый ключ, JSX-спред, смешанный литерал), тернарник и шаблонная строка + в значении CSS-переменной, импорт глобального CSS мимо `main.tsx`; +- **сеть:** все восемь транспортов (`fetch` · `EventSource` · `WebSocket` · `XMLHttpRequest` · + те же через `window`/`globalThis`/`self` · `navigator.sendBeacon`), в `src/api/**` все восемь + проходят; +- **швы импорта:** `react-aria-components` вне `src/ui/` и моки вне `src/api/` — статическим + И динамическим импортом. Причина дубля селектором: `no-restricted-imports` разбирает только + ОБЪЯВЛЕНИЕ, а `await import(…)` — выражение; +- **структурные тесты:** опечатка в имени класса CSS Module (в том числе через соседний модуль + и через деструктуризацию), опечатка в имени токена в CSS и в данных TS, число в `measures.ts`, + разошедшееся с одноимённым токеном, модуль стилей, который никто не импортирует, маршрут, + забытый в `scripts/shot.mjs`, литерал `lang="zh"` в разметке и `:lang(zh)` в стилях; +- **контракт API:** правка спеки без пере-генерации типов · правка генерённого файла руками · + битый `$ref` · значение словаря, выпавшее из перечня ветки неизвестного (`tsc` роняет TS2344 + «does not satisfy the constraint 'never'») · мажор спеки, разошедшийся с `supportedMajor` + клиента — без него спека, бампнутая до 1.x, отвергала бы каждый живой поток в рантайме + вместо падения на сборке. -**Сеть (все восемь форм ловятся, в `src/api/**` все восемь проходят):** `fetch` · `EventSource` · -`WebSocket` · `XMLHttpRequest` · `window.fetch` · `globalThis.fetch` · `self.fetch` · -`navigator.sendBeacon`. Проверялось файлом на диске, а не через `--stdin`: тип-осведомлённый -линт отказывается разбирать stdin («was not found by the project service»). +**Ловушки, найденные исполнением, — не переоткрывать:** -**Структурные тесты:** опечатка в имени класса CSS Module · то же при переименованном импорте -модуля · опечатка в имени токена в CSS · опечатка в имени токена в данных TS · маршрут добавлен -в `routes.tsx` и забыт в `scripts/shot.mjs` · литерал `lang="zh"` возвращён в разметку. - -**Что НЕ ловилось и осталось дырой после S1:** `/* eslint-disable */` в TSX (см. правило 2) · -JSX-спред `
` со `style` внутри объекта · именованные цвета CSS в TSX -(`fill="red"`). Две последние закрыты в S2 (Ф-9), первая закрыта правилами -`@eslint-community/eslint-comments` ещё в S1 — см. правило 2. - -### 5.4.1. Что добавила S2 (04.08) — перечень форм - -Гейты прогонялись разом: один временный файл со всеми формами, один прогон линтера, ожидание -записано против каждой строки. Файлы уносились после прогона, дерево после отката зелёное. - -**Цвет в TSX (Ф-9), 15 форм.** Падают: именованный цвет в `fill` · именованный цвет в `color` -иконки · системный цвет в `stroke` через выражение · цвет в CSS-переменной `style={{'--x':'red'}}` · -системный цвет там же · размер литералом там же (`'10px'`) · hex-литерал · цветовая функция · -обычное свойство в инлайновом стиле · `{ style: {...} }` в объектном литерале · `{ 'style': {...} }` -строковым ключом · импорт `react-aria-components` вне `src/ui/`. -Проходят (и обязаны): `fill="none"` · `fill="currentColor"` · число в CSS-переменной -(`'--progress': 0.5`) · токен в CSS-переменной (`var(--space-4)`). - -⚠ Ловушка, найденная исполнением: селектор `Property[key.value=/^--/] > Literal[…]` ловил -СОБСТВЕННЫЙ ключ (имя переменной — тоже прямой потомок Property) и падал на легальном -`style={{ '--progress': x }}`. Поэтому в списке разрешённых значений стоит и префикс `--`. - -**Отступы и системные цвета в CSS (Ф-4, Ф-9), 14 форм.** Падают: `padding: 8px` · -`margin: 0 4px` · `gap: 4px` · `border-radius: 6px` · `border-top-left-radius: 4px` · -`calc(var(--space-4) + 2px)` · системный цвет в `outline` · в `caret-color` · в `box-shadow` · -именованный цвет · hex в сокращённой записи `border`. -Проходят: `padding: var(--space-4)` · `padding-top: 0` · `margin-left: auto`. - -⚠ **Обход, найденный адверсариальным ревью и НЕ прошедший:** объявить в модуле свою переменную -(`--local-pad: 13px`) и подставить её (`padding: var(--local-pad)`). Форму обёртки stylelint -принимает — но падает контракт-тест токенов, который требует, чтобы каждое имя `var(--…)` было -объявлено в `tokens.css`. То есть гейт держит вторым слоем, а не первым; проверено исполнением. - -**Структурные тесты (Ф-10), 7 форм — каждая роняет `vitest`.** Опечатка в классе у компонента, -импортирующего СОСЕДНИЙ модуль (прежде выпадала из проверки) · опечатка в классе через -деструктуризацию `const { x } = styles` · расширение из `url(./x.woff2)`, притворяющееся -объявленным классом · новый токен, не попавший ни в замеры, ни в подобранные · токен, оставшийся -в списке подобранных после удаления из `tokens.css` · число в `measures.ts`, разошедшееся -с одноимённым токеном · модуль стилей, который никто не импортирует. - -**Хвосты ревью оркестратора (04.08), 11 форм — один прогон линтера, 7 падений и 4 прохода.** -Падают: динамический `import('../mock/book')` · динамический `import('../tokens/tokens.css')` · -динамический `import('react-aria-components')` вне `src/ui/` · то же из `src/api/` (фикстуры ему -можно, примитивы — нет) · динамический импорт моков из `src/ui/` · тернарник в значении -CSS-переменной (`{'--x': flag ? 'red' : 'blue'}`) · шаблонная строка там же. -Проходят (и обязаны): динамический импорт `*.module.css` · идентификатор в значении переменной · -`import('react-aria-components')` внутри `src/ui/` · `import('../mock/book')` внутри `src/api/`. -Причина всей пачки одна: `no-restricted-imports` разбирает только ОБЪЯВЛЕНИЕ импорта, а -`await import(…)` — выражение, поэтому каждый шов продублирован селектором и точечные послабления -у обеих половин совпадают по имени шва. - -**Языковая ветка в CSS (`generality.test.ts`, вторая половина).** Падает `:lang(zh) .source { … }` -в любом файле стилей — проверено живым нарушением с восстановлением файла из памяти, не из git. -Комментарии из CSS снимаются перед проверкой: правило объясняется в том же файле, где действует, -и объяснение содержит запрещённую форму дословно. - -### 5.4.2. Замки контракта API (04.08) — четыре формы, каждая проверена нарушением - -Гейт контракта — шестой шаг `npm run check` плюс тесты `src/api/contract.test.ts`. -Каждая форма роняла проверку, после отката дерево зелёное; откат — копией файла, не `git`. - -Падают: **правка спеки без пере-генерации типов** (в `openapi.yaml` добавлено поле, тест дрифта -сравнил свежую генерацию с закоммиченной и упал) · **правка генерённого файла руками** -(в `schema.ts` вписан лишний тип — тот же тест) · **битый `$ref` в спеке** -(`invalid-ref` от spectral, `npm run contract` = 1) · **значение словаря, выпавшее из перечня -ветки неизвестного** (`awaiting_bank` убран из `bookStatuses` → `tsc` роняет TS2344 -«Type '"awaiting_bank"' does not satisfy the constraint 'never'»). - -⚠ Ложное срабатывание, найденное этим же заходом и починенное по классу: контракт-тест токенов -собирает имена по кавычкам вокруг `--…`, и флаг движка в доккомментарии генерённого файла -(`` `--verify-bank` ``) для него неотличим от имени токена. Генерённый файл из скана исключён -с названной причиной; остаточная граница честная — обратные кавычки вокруг `--флаг` -в АВТОРСКОМ комментарии всё ещё дадут ложное падение, но падение громкое, а не тихое. - -### 5.4.3. Замки слоя данных (S3, 08.08) — семь форм, каждая проверена нарушением - -Прогон одним заходом, откат — копией файла, не `git checkout` (в дереве лежат незакоммиченные -правки чужих зон). После отката `npm run check` зелёный. - -**Падают:** - -1. **Значение словаря выброшено из `src/api/vocabulary.ts`** — `tsc` роняет `TS2345`, называя - пропавшее значение по имени (`Property 'paused' is missing`). Это и есть механизм «новое - состояние правит ОДИН файл»: словарь объявлен как `Record<Значение, Смысл>`, поэтому список - значений выводится из записей и разойтись с типом физически не может. -2. **Спека правлена без пере-генерации типов** — тест дрифта (`contract.test.ts`). -3. **Мажор спеки разошёлся с мажором, который говорит клиент** — новый тест: `info.version` - спеки читается из файла и сверяется с `supportedMajor` в `stream.ts`. Без него спека, бампнутая - до 1.x, отвергала бы каждый живой поток в рантайме вместо падения на сборке. -4. **Новый маршрут в `routes.tsx`, забытый в `scripts/shot.mjs`** — `routes.test.ts`. -5. **`EventSource` прямо в экране** — `no-restricted-globals` с проектным сообщением. -6. **`window.fetch` прямо в экране** — `no-restricted-properties`. -7. **Импорт `../mock/*` в экране, статический И динамический** — `no-restricted-imports` плюс - селектор на `ImportExpression`. - -**Проходят (и обязаны):** `new EventSource(...)` внутри `src/api/**`. - -**Что этот слой гейтом НЕ закрыт, названо честно:** соответствие MSW-хендлеров контракту держится -типами тел (`json`), но ПУТЬ хендлера — обычная строка, и опечатка в нём даёт -`onUnhandledRequest`, а не падение сборки; в тестах это ловится (`'error'`), в браузере хендлер -просто не сработает. Второе: пагинация проверена на мире `scale`, где страницы настоящие, но -гейта, требующего страничности от каждого нового мира фикстур, нет. +- селектор `Property[key.value=/^--/] > Literal[…]` ловит СОБСТВЕННЫЙ ключ (имя переменной — тоже + прямой потомок `Property`) и падал на легальном `style={{ '--progress': x }}`; поэтому в списке + разрешённых значений стоит и префикс `--`; +- обход «объявить свою переменную в модуле (`--local-pad: 13px`) и подставить её» форму обёртки + stylelint принимает — его ловит контракт-тест токенов, требующий, чтобы каждое имя `var(--…)` + было объявлено в `tokens.css`. Гейт держит ВТОРЫМ слоем, а не первым; +- контракт-тест токенов собирает имена по кавычкам вокруг `--…`, и флаг движка в доккомментарии + генерённого файла (`` `--verify-bank` ``) для него неотличим от имени токена. Генерённый файл + из скана исключён; остаточная граница честная — обратные кавычки вокруг `--флаг` в АВТОРСКОМ + комментарии всё ещё дадут ложное падение, но падение громкое, а не тихое; +- тип-осведомлённый линт отказывается разбирать `--stdin` («was not found by the project + service») — проверять нарушение только файлом на диске; +- комментарии из CSS снимаются перед проверкой языковой ветки: правило объясняется в том же + файле, где действует, и объяснение содержит запрещённую форму дословно. **Чего гейты по-прежнему не видят — перечислено, потому что «закрыто» без границ и есть та самая -ловушка, о которой этот раздел** (найдено адверсариальным ревью S2, каждая граница воспроизведена): +ловушка, о которой этот раздел** (найдено адверсариальным ревью, каждая граница воспроизведена): - **соответствие мок-хендлера контракту по ПУТИ.** Тело типизировано (`json`), путь — обычная строка: опечатка даёт `onUnhandledRequest`, а не падение сборки. В тестах она ловится (`'error'`), в браузере хендлер просто молчит; -- **вычисленное значение цвета.** Гейт на цветных атрибутах ловит литерал и шаблонную строку; +- **страничность нового мира фикстур.** Пагинация проверена на мире `scale`, где страницы + настоящие, но гейта, требующего страничности от каждого нового мира, нет; +- **вычисленное значение цвета.** Гейт ловит литерал и шаблонную строку; `const c = 'red'; `, тернарник, вызов функции и JSX-спред проходят. Синтаксически - это не ловится, а типовое правило под такую проверку в `typescript-eslint` отсутствует; + это не ловится, а типового правила под такую проверку в `typescript-eslint` нет; - **значение CSS-переменной, собранное вызовом или вынесенное в идентификатор** (`style={{'--x': compute()}}`, `style={{'--x': value}}`) — проверять статически нечего, а запрет - идентификатора убил бы само исключение. Тернарник и шаблонная строка с 04.08 ловятся: это - носители литерала, и обход через них был буквальным; + идентификатора убил бы само исключение; - **значения стилей в тесте общности.** `generality.test.ts` видит разметку и селекторы, но не значения: именованная гарнитура письменности в токене (`--font-cjk: 'Noto Sans CJK SC', …`) - действует как `:lang(zh)`, только молча. Ровно такой токен и приехал в S2 — снял его человек + действует как `:lang(zh)`, только молча. Ровно такой токен однажды и приехал — снял его человек на ревью, не гейт; список именованных фейсов в правиле был бы хрупким по построению; - **императивный стиль** (`node.style.padding = '13px'`, `setAttribute('style', …)`) — в коде такого нет ни одного случая, но правилом это не запрещено; - **отключение TSX-гейта комментарием.** Оно обязано называть правило и нести причину, но проходит; - в CSS-половине `reportDisables` роняет даже именованное. Асимметрия честно записана в комментарии - конфига, а не выдана за симметрию, как было раньше; -- **`width`/`height`/`top`/`left` литералами в CSS:** Ф-4 по STACK_DECISIONS §3 закрывает + в CSS-половине `reportDisables` роняет даже именованное. Асимметрия записана в комментарии + конфига, а не выдана за симметрию; +- **`width`/`height`/`top`/`left` литералами в CSS:** гейт отступов закрывает `padding`/`margin`/`gap`/`border-radius`, а собственные размеры компонента (точка 6px, полоска 24×3) остаются на ревью глазами; - **ложные срабатывания, которые мы принимаем:** строка вида `#1234` объявляется цветом (это четырёхзначный hex), а поле объекта с именем `style` — инлайновым стилем. Оба случая лечатся переименованием и стоят дешевле, чем пропущенный литерал. + ### 5.5. Пере-замер S3.5 (09.08) — что нашёл замер, а не глаз Замечания владельца 1 и 5 требовали работать замером. Инструмент замера теперь живёт в репозитории @@ -607,8 +529,7 @@ CSS-переменной (`{'--x': flag ? 'red' : 'blue'}`) · шаблонна на 0 · 328 · 944 · 1272. Это и есть ответ на «особенно заметно по тексту снизу»: полосы у Fleet прижаты к краям окна, а не отступают от них на поле оболочки, и текст в них центрируется по всей полосе. Поле оболочки поэтому принадлежит области ПАНЕЛЕЙ, а не окну целиком — иначе поле полосы -складывается с полем оболочки, и текст уезжает вдвое дальше от края, чем в референсе (эту ошибку -первый заход S3.5 и совершил: было 14, стало бы 20 при цели 12.5; поймало адверсариальное ревью). +складывается с полем оболочки, и текст уезжает вдвое дальше от края, чем в референсе. После правки замер сходится с референсом по обеим осям: текст статус-полосы `x 12.5…1267.0`, `y 744.5…756.0` против `12.5…1266.5`, `744.5…756.0` у Fleet. @@ -620,12 +541,11 @@ CSS-переменной (`{'--x': flag ? 'red' : 'blue'}`) · шаблонна 1x: «штрих 2 при 16» и «размер 18, штрих 2» дают жирнее, но по-прежнему мыльно; полупиксельный сдвиг SVG даёт целые линии. Взят он (`reset.css`). -**Мера набора читалки.** На кадре 2560×1440 строка перевода вырастала до ~150 знаков: боковые панели -держат функциональную ширину в пикселях, и вся лишняя ширина доставалась центру. S3.5 ввела -`--reader-width: 1040px` с центрированием блоков — ~75 знаков в колонке. ⚠ **Отменено 10.08:** -владелец назвал это «жёстко приклеен к центру», токен удалён, набор снова во всю ширину панели; -на 2560×1440 это ~120 знаков в строке. Ограничивать ширину повторно нельзя — решение владельца. Функциональную ширину -боковых панелей намеренно НЕ переводили в долю кадра: 25% у Fleet это следствие окна 1280, а +**Мера набора читалки.** ⚠ **Ограничивать ширину набора нельзя — решение владельца 10.08:** +попытку центрировать колонку фиксированным `--reader-width` он назвал «жёстко приклеен к центру», +токен удалён. Набор идёт во всю ширину панели; на 2560×1440 это ~120 знаков в строке (боковые +панели держат функциональную ширину в пикселях, и вся лишняя ширина достаётся центру). +Функциональную ширину боковых панелей намеренно НЕ переводили в долю кадра: 25% у Fleet это следствие окна 1280, а не инвариант — 640px под дерево разделов были бы пустой ширины. --- @@ -644,17 +564,16 @@ CSS-переменной (`{'--x': flag ? 'red' : 'blue'}`) · шаблонна | выбранная строка | `#184176` (срез строки CardPicker.tsx) | — | `--color-selected: #1c4478` | | синий/зелёный акценты | — | `#2964ad` / `#3c7d4f` (NOTE/TIP) | `--color-note` / `--color-ok` | -Главное, что дал замер: **оба референса строго нейтральны, R=G=B**. То есть тёплый подтон, -внесённый S3.5 «по рекомендациям про тёмные фоны», противоречил тем самым кадрам, на которые -сессия ссылалась. Второй вывод — про вкладки: срез по вертикали через активную вкладку fleet_2 -(x=790, y=36‥68) даёт `#181818` без единой разделительной линии до содержимого, тогда как -соседняя неактивная лежит на `#292929`. +Главное, что дал замер: **оба референса строго нейтральны, R=G=B** — тёплый подтон «по +рекомендациям про тёмные фоны» противоречил тем самым кадрам, на которые ссылались. Второй +вывод — про вкладки: срез по вертикали через активную вкладку fleet_2 (x=790, y=36‥68) даёт +`#181818` без единой разделительной линии до содержимого, тогда как соседняя неактивная лежит +на `#292929`. -⚠ **Поправка S3.7 (10.08): вывод про вкладки в коде НЕ стоит.** Модель «вырез в хроме» была -построена по этому замеру и тем же паком ОТКАЧЕНА по слову владельца — в `Tabs.module.css` -сегодня пилюля по `fleet.png`: полоса вкладок это полотно панели, активная вкладка лежит НА нём -скруглённой подложкой (`--color-raised`, у документа `--color-tab-active`). Замер остаётся верным -про сам референс; описание кода в этом абзаце было неверным. +⚠ **Этот вывод про вкладки в коде НЕ воплощён, и воплощать его не надо:** модель «вырез в хроме» +была построена по нему и ОТКАЧЕНА по слову владельца. В `Tabs.module.css` пилюля по `fleet.png` — +полоса вкладок это полотно панели, активная вкладка лежит НА нём скруглённой подложкой +(`--color-raised`, у документа `--color-tab-active`). Замер верен про сам референс, не про наш код. Способ повторить (проверено исполнением 10.08 в S3.7, числа сошлись): `scripts/measure.py` для поверхностей и промежутков, `PIL.Image` + `collections.Counter` по `im.getdata()` для общей @@ -681,66 +600,45 @@ CSS-переменной (`{'--x': flag ? 'red' : 'blue'}`) · шаблонна | линия в 1px на x=515 | — | есть | не дефект: жёлоб читалки покрашен в цвет оболочки (замечание второго круга 5) | | полосы по вертикали | верхняя 0‥36, статус 735.5‥764 | 0‥37 и 735‥764 | сошлось (полпикселя — округление среза) | -То есть за два пака поехали ровно те числа, которые двигали намеренно, и ни одного сверх того; -разошлось бы что-то ещё — эта таблица и была бы местом, где это видно. +## 6. Форма данных — из контракта, не из пересказа -## 6. Что уже известно про движок — учтено в форме данных +⚠ **Здесь стоял пересказ того, что движок отдаёт фронту, и он отстал от канона.** Пересказ снят: +единственный носитель формы — ратифицированный контракт `docs/architecture/14-api-contract/` +(спека + спутник с провенансом, обоснованиями и К-вопросами). Разделы спутника по номерам: +язык кодом — §2.1, непрозрачные идентификаторы — §2.2, заголовок главы — §2.3, состояние прогона +против выполнения главы — §2.4, прогресс — §2.5, состояние пары — §2.7, подпись банка как набор +решений — §2.9, разрешающий список полей — §2.12, карта «причина → продуктовая фраза» — +приложение А. Цена расхождения замерена на этой же зоне: редакция 0.3.0 сняла пофазный +сплит с провода (К-10 закрыт вердиктом «НЕ строить», D39.138), а здешний абзац ещё учил строить +две пары счётчиков. -Из `FRONTEND_SESSION_PROMPT.md` §9 и `STACK_DECISIONS.md` §8. Влияет на типы в `src/api/` -уже сейчас, поэтому записано в плане, а не откладывается на S3. +Что остаётся правилом ФРОНТА, а не контракта: -1. **Прогресс — пофазный, не «готово N из M».** Юнит становится `done` только когда есть и - черновые строки всех членов, и строка редактуры: во время черновой волны сквозной счётчик - стоит на нуле почти всё время. Тип прогресса — две пары `{ done, total }` (черновик ∥ редактура), - а не одно число. Пользователю всё равно показывается одна полоса без названий фаз (§4.1). - ⚠ Это правило было записано здесь ещё в S0 — и витрина S1 всё равно вышла с наивным - счётчиком глав, потому что фикстура выдумала поле `translatedChapters`, которого read-модель - не отдаёт. Лечение сильнее правила: фикстура поставлена в состояние «идёт черновая волна», - где наивный счётчик дал бы ноль, и скриншот-цикл видит трудный случай, а не удобный. -1а. **Состояние — у ПРОГОНА над книгой, у главы его нет.** Подпись банка — один стоп между - черновой и редакторской волнами на всю книгу (`mining.go:199`), поэтому «глава ждёт подписи, - пока соседняя финализируется» — картина, которую движок породить не может. У главы есть - выполнение: `ChapterPassport` (`status.go:37-55`) несёт `units_total/done/in_progress/pending` - и вердикт. В интерфейсе: лестница из девяти состояний — на строке книги, полоса выполнения — - на строке главы. Девять состояний точкой не различаются (проверено снимком: три пары - совпадали) — метка словом, цвет только дублирует и только для внимания и отказа. -1б. **Язык — код, а не слово.** Движок держит `source_lang`/`target_lang` кодами (`book.go:26-27`), - ключ пары — `zh-ru`; контракт отдаст то же самое. Человеческое имя считается на экране - через `Intl.DisplayNames`, `lang` на элементе берётся из данных книги (правило 17). -2. **Подпись термина — не UPDATE строки.** Банк пересобирается из файлов на каждом прогоне, - прямая запись стирается следующим прогоном. Контракт подписи — отправка набора решений, а не - правка записи; фикстуры S3 моделируют именно это. -3. **Байтовых смещений у замечаний нет.** Замечание адресует блок или главу — и только. - Тип замечания не имеет полей `offset`/`length`; подчёркиваний внутри текста не будет. -4. **Единица пары — edit-unit, ~1.9 на главу.** Местами вся глава окажется одним блоком. - Читалка обязана нормально выглядеть в этом случае; выравнивание грубое, по единицам экспорта. -5. **Одна вертикальная прокрутка на читалку.** Список строк-пар, внутри строки +1. **Единица пары — edit-unit, ~1.9 на главу** (замер движка, ПТ-21): местами вся глава окажется + одним блоком, и читалка обязана нормально выглядеть в этом случае. +2. **Одна вертикальная прокрутка на читалку.** Список строк-пар, внутри строки `grid-template-columns: 1fr 1fr`. Синхронизации двух прокруток нет и не будет — колонки - физически не могут разъехаться. - -Денежных полей в интерфейсе нет нигде (§4.8): ни сумм, ни потолков, ни оценки, ни остатка. + физически не могут разъехаться (`STACK_DECISIONS.md` §2). +3. **Денежных полей в интерфейсе нет нигде** (§4.8 промта): ни сумм, ни потолков, ни оценки, + ни остатка. +4. **Фикстура ставится в ТРУДНЫЙ случай, а не в удобный.** Правило «прогресс не строить на + „готово N из M“» было записано здесь ещё на S0 — и витрина всё равно вышла с наивным + счётчиком глав, потому что фикстура выдумала поле, которого read-модель не отдаёт. Лечение + сильнее правила: мир фикстур поставлен в состояние, где наивный счётчик дал бы ноль, + и скриншот-цикл смотрит на трудный случай. --- -## 7. Порядок S1 и что в него НЕ входит +## 7. Pre-commit хук зоны -Порядок работ S1: каркас → `npm run check` → скриншот-цикл и проверка, что он живой → -`tokens.css` → витрина → контракт-тест токенов → сверка витрины с референсом. +Хук один — pre-commit (запрос владельца 02.08; CI в репозитории отсутствует, до его появления +хук — единственный машинный рубеж). Зонный фрагмент `scripts/githooks/pre-commit` зовёт тот же +`npm run check` (один список инструментов, не дубль) только когда в коммите есть frontend-пути; +плюс блок файлов «никогда не коммитить» и блок смеси `frontend/` с чужой зоной (картина +инцидентов D39.88). Ставится сам: `npm install` через `prepare` кладёт диспетчер +в `.git/hooks/pre-commit` (идемпотентно, чужой хук не перетирает). Осознанный обход — +`git commit --no-verify`. -`npm run check` = `prettier --check` → `eslint` → `stylelint` → `tsc --noEmit` → `vitest run`. -Шагов пять, а не четыре: `STACK_DECISIONS.md` §3 перечисляет четыре, но там же требует, чтобы -обе половины гейта цвета жили в одной команде, а CSS-половину гоняет именно stylelint. -`npm run check:full` = `check` → `vite build` → `shot`. Отдельного e2e-набора в S1 нет: -единственная браузерная проверка — скриншот-цикл, он и стоит в `check:full`. -Git-хук один — pre-commit (запрос владельца 02.08, вторая фронт-сессия; отменяет прежнее -«хуков нет»: CI в репозитории отсутствует, до его появления хук — единственный машинный рубеж). -Зонный фрагмент `scripts/githooks/pre-commit` зовёт тот же `npm run check` (один список -инструментов, не дубль) только когда в коммите есть frontend-пути; плюс блок файлов -«никогда не коммитить» и блок смеси frontend/ с чужой зоной (картина инцидентов D39.88). -Ставится сам: `npm install` через prepare кладёт диспетчер в `.git/hooks/pre-commit` -(идемпотентно, чужой хук не перетирает). Осознанный обход — `git commit --no-verify`. +Состав `npm run check` и `check:full` — `STACK_DECISIONS.md` §3 (там он сверен по `package.json`); +копии состава здесь нет намеренно, она отставала на два пака. -**Не входит в S1** (и не должно появиться раньше срока): экраны, оболочка трёх панелей, -слой данных и MSW, React Compiler (Ф-2), токен-гейт на отступы (Ф-4 — включается, когда -шкала отступов устоится, иначе мешает подбору), эталонные скриншоты в тестах (отложены -решением `STACK_DECISIONS.md` §3), светлая тема, мобильная раскладка. diff --git a/frontend/docs/FRONTEND_SESSION_PROMPT.md b/frontend/docs/FRONTEND_SESSION_PROMPT.md index 9ffef568..181aed70 100644 --- a/frontend/docs/FRONTEND_SESSION_PROMPT.md +++ b/frontend/docs/FRONTEND_SESSION_PROMPT.md @@ -1,32 +1,12 @@ -> ⚠ **S0–S3 ОТРАБОТАНЫ. Файл остаётся ЖИВЫМ, но не как задание, а как носитель норм зоны** — на него -> так ссылаются `S3_SESSION_PROMPT.md` и `frontend/README.md`. **Живое здесь:** §«Зона и git», §3 -> сценарий, §4 жёсткие ограничения, §7 «как работать». **Историческое:** §«Область ЭТОЙ сессии» -> (там сказано «этапы S0 и S1, дальше не идти» — это уже неверно) и таблица этапов, где S3 стоит -> как «хвост S2 или отдельная». **Статус этапов — ТОЛЬКО `frontend-PROGRESS.md`**, здесь его нет. +> ⚠ **ЭТО НЕ ЗАДАНИЕ, а носитель норм зоны** — референсы с измеренными значениями, продуктовый +> сценарий экранов, жёсткие ограничения владельца и порядок работы. На него так ссылается +> `frontend/README.md`. **Статус этапов и что уже построено — ТОЛЬКО `frontend-PROGRESS.md`**, +> здесь этого нет намеренно (копия отставала); очередь этапов S2–S7 — `BACKLOG.md` Ф-1. > Поставлено оркестратором №15 08.08 по слепому онбординг-замеру: холодная сессия открывала файл -> первым по указателям и читала запрет «дальше не идти». +> первым по указателям и читала запрет «дальше не идти», адресованный давно отработавшей сессии. # Промт: сессия ФРОНТ TextMachine — MVP веб-интерфейса -## Область ЭТОЙ сессии — этапы S0 и S1, дальше не идти - -Фронт режется на шесть сессий. Ты — первая. **Твоя работа кончается на витрине токенов, до единого -экрана приложения.** Это не осторожность: без проверяемой петли обратной связи и сошедшихся токенов -все следующие этапы слепы, а смешивание инструментальной работы с интерфейсной сжигает контекст. - -| Этап | Что | Сессия | -|---|---|---| -| **S0** | Планирование: как писать фронт. Пины версий, карта файлов, правила, протокол проверки | **твоя** | -| **S1** | Инструменты, скриншот-цикл, `tokens.css`, витрина | **твоя** | -| S2 | Оболочка (три панели, вкладки, статус-полоса) + слой `src/ui/` | отдельная | -| S3 | Слой данных, MSW, фикстуры всех состояний | хвост S2 или отдельная | -| S4 | Библиотека, загрузка, разбор, прогресс перевода | отдельная | -| S5 | Банк памяти и подпись (самый тяжёлый) | отдельная | -| S6 | Читалка двух колонок и замечания | отдельная | -| S7 | Метаданные, экспорт, настройки, сквозной прогон | отдельная | - -**S0 лендится отдельным коммитом до первой строки кода** — иначе план останется словами. - ## Обязательное чтение перед работой 1. `frontend/docs/STACK_DECISIONS.md` — **пины версий и ловушки**. Все версии сверены 02.08.2026 @@ -57,17 +37,20 @@ Веб-сервис перевода книг (веб-новеллы, ранобэ) с одного языка на другой через ИИ. Пользователь загружает книгу, запускает перевод, подписывает словарь терминов, читает результат, выгружает готовое. -**Бэкенда пока нет** — точнее, движок перевода есть, но HTTP-API к нему ещё не построено. Ты работаешь на моках (см. §6). Это не помеха: контракт данных зафиксирован ниже, замена моков на реальные запросы будет точечной. +⚠ **API у ПЛАТФОРМЫ, а не у движка; движок фронту не виден вообще** (D39.85, `research/23`). +Ошибка дорогая: моки, снятые не с того уровня контракта, разойдутся с API. Прежде чем писать слой +данных, прочитай `FRONTEND_PLAN.md` §0.1–0.2 (карта канона и два провода) и первоисточник +`docs/research/23-engine-platform-seam.md`. -> ⚠ **Поправка канона №2 (оркестратор, 04.08, D39.99/100/101):** контракт API v0 РАТИФИЦИРОВАН (`docs/architecture/14-api-contract/` — OpenAPI 3.1, типы генерятся из спеки), строка 95 закрыта; слой данных S3 строится ПО КОНТРАКТУ, не по гипотезам. -> ⚠ **Поправка S4 (14.08):** «скелет платформы P0 существует, ждёт приёмки» устарело — **P5 принята (D39.130)**, живой `POST /books` построен, и путь загрузки книги S4 стоит на нём. -> ⚠ **Поправка канона (фронт-сессия S1, 02.08).** Формулировка выше читается как «однажды у движка появится HTTP-API». Это не так, и ошибка дорогая: **API будет у платформы, не у движка**, а движок фронту не виден вообще. Прежде чем писать слой данных, прочитай `FRONTEND_PLAN.md` §0.1–0.2 (карта канона и два провода) и первоисточник `docs/research/23-engine-platform-seam.md`. Там же цена ошибки: моки, снятые не с того уровня контракта, разойдутся с API — ровно то, ради предотвращения чего заведена строка 95 единого бэклога. +Контракт API v0 РАТИФИЦИРОВАН (D39.99/100/101; канон `docs/architecture/14-api-contract/` — +OpenAPI 3.1, типы генерятся из спеки), и слой данных строится ПО КОНТРАКТУ, а не по гипотезам. +Что у платформы уже живо и чего ещё нет — `platform/BACKLOG.md` и её зонный журнал. --- ## 1. Референсы — копировать, а не вдохновляться -В `frontend/references/` лежат три скриншота. **Открой их своим инструментом чтения файлов — ты умеешь смотреть изображения.** Это обязательный первый шаг, до единой строки кода. +В `frontend/references/` лежат скриншоты-референсы (кладёт владелец, в git они не едут). **Открой их своим инструментом чтения файлов — ты умеешь смотреть изображения.** Это обязательный первый шаг, до единой строки кода. - `fleet.png` — **главная основа макета**. Копируем оболочку целиком: как устроены панели, отступы, скругления, плотность, палитра. - `antigravity_main.png` — пустое состояние (ничего не выбрано). @@ -86,7 +69,7 @@ | Фон оболочки: промежутки, верхняя полоса, статус-полоса | `#090909` | 10.5% площади | | Заливка панели (все три панели одинаковы) | `#17191a` | 80.5% площади | | Приподнятая поверхность / наведение | `#27292b` | | -| Выбранная строка, активная вкладка | `#353739` | | +| Выбранная строка дерева, клавиша-чип | `#353739` | ⚠ **не активная вкладка:** пере-замер развёл роли, вкладка документа — `#142f4c` (`FRONTEND_PLAN.md` §5.1) | | Подсветка текущей строки (синеватая) | `#152945` | | | Выделение | `#164e8d` | | | Основной текст | `#dfe1e3` | | @@ -148,37 +131,15 @@ ## 2. Раскладка нашего приложения -> ⚠ Раскладка частично устарела — перевешивают «Решения владельца по продукту» в `frontend-PROGRESS.md`; метки глав — из ДАННЫХ книги, зашитого «Глава N» не существует (D39.100 К-3). +Оболочка Fleet, наполнение наше: три сворачиваемые панели, вкладки в шапке каждой, верхняя полоса +и статус-полоса. -Оболочка Fleet, наполнение наше. +⚠ **Схемы раскладки здесь больше нет: оболочка ПОСТРОЕНА, и её описывал устаревающий рисунок.** +Действующая раскладка — код `src/shell/` (`Shell.tsx`, `Panel.tsx`, `layout.ts`) плюс наполнение +панелей; продуктовые решения по составу вкладок и панелей перевешивают из «Решений владельца +по продукту» в `frontend-PROGRESS.md`. -``` -┌─────────────────────────────────────────────────────────────────┐ -│ верхняя полоса: сворачивание панелей · название книги · действия │ -├──────────────┬────────────────────────────────┬─────────────────┤ -│ ЛЕВАЯ │ ЦЕНТР │ ПРАВАЯ │ -│ │ │ │ -│ вкладки: │ вкладки открытых глав │ вкладки: │ -│ Книги · │ │ О книге · │ -│ Банк · │ содержимое: │ Замечания │ -│ Поиск │ — две колонки оригинал/перевод │ │ -│ │ — или таблица банка памяти │ метаданные, │ -│ дерево: │ — или пустое состояние │ выноски с │ -│ книга │ │ неточностями │ -│ ├ глава 1 │ │ │ -│ ├ глава 2 │ │ │ -│ └ + глава │ │ │ -│ │ │ │ -│ ⚙ Настройки │ │ │ -├──────────────┴────────────────────────────────┴─────────────────┤ -│ статус-полоса: путь · состояние · прогресс │ -└─────────────────────────────────────────────────────────────────┘ -``` - -- **Левая панель** — вкладки в шапке (`Книги` / `Банк памяти` / `Поиск`), дерево книг и глав, `Настройки` внизу. Клик по книге открывает её состояние, клик по главе открывает главу в центре. -- **Центр** — вкладки открытых глав, как файлы в Fleet. -- **Правая панель** — вкладки `О книге` (метаданные) и `Замечания` (места, требующие внимания). -- Все три панели сворачиваются кнопками в верхней полосе. +⚠ Метки глав — из ДАННЫХ книги; зашитого «Глава N» не существует (D39.100 К-3). --- @@ -201,7 +162,7 @@ Кнопка запуска перевода. Пока идёт: - **у каждой главы в дереве — свой индикатор выполнения** (крутящийся/прогресс), главы обрабатываются не по порядку и это нормально; -- общий прогресс по книге в статус-полосе (+ ETA: решение владельца 04.08, D39.100 К-5 — поле `eta_seconds` добавит в спеку правка S3, опциональное); +- общий прогресс по книге в статус-полосе (+ ETA — решение владельца, D39.100 К-5; поле в спеке есть); - ⚠ **не рассказывать, что происходит внутри.** Никаких «сейчас работает редактор», «идёт добыча глоссария», никаких названий моделей. Пользователь видит «идёт перевод» и прогресс. Всё. ### 3.5. Банк памяти — подпись @@ -276,60 +237,32 @@ ## 5. Технический стек **Стек зафиксирован в `frontend/docs/STACK_DECISIONS.md` — бери пины оттуда, не выбирай сам.** -Коротко: React 19.2.8 · TypeScript 6.0.3 (не 7 — там нет программного API) · Vite 8.2.0 (внутри -Rolldown, не Rollup) · react-router 8.3.0 в библиотечном режиме · TanStack Query 5 + Zustand 5 · -CSS Modules + `tokens.css` · **react-aria-components 1.20.0 как ЕДИНСТВЕННАЯ библиотека -примитивов** · react-resizable-panels 4.12.2 (API v4: `Group`/`Panel`/`Separator`). +Перечня пинов здесь нет намеренно: третий экземпляр списка версий отставал бы первым. Три вещи, которые чаще всего делают неправильно, — прочитай про них в STACK_DECISIONS: раздел про гейт «одно место для цвета», раздел про читалку без синхронизации прокруток, -и §7 «Ловушки». +и §7 «Ловушки». Карта `src/` с назначением каждой папки и правилом зависимостей — +`FRONTEND_PLAN.md` §2. **Никакого редактора текста** в этом MVP: тексты только читают и сравнивают. -Структура: - -``` -frontend/ - references/ скриншоты (уже лежат) - docs/ этот промт и последующие заметки - src/ - tokens/ tokens.css — ЕДИНСТВЕННЫЙ источник цвета и размера - api/ слой данных: сейчас моки, потом HTTP. Один вход. - mock/ фикстуры - shell/ оболочка: панели, вкладки, статус-полоса - features/ books, chapters, bank, reader, settings - ui/ кнопки, поля, таблица, дерево, выноска -``` - --- ## 5.1. Поддерживаемость — требование владельца, не пожелание -Владелец сформулировал прямо: **«главное, чтоб это было очень легко поддерживаемо»**. Этот код будут править другие сессии, не ты. Правила ниже — проверяемые, не лозунги. +Владелец сформулировал прямо: **«главное, чтоб это было очень легко поддерживаемо»**. Этот код будут править другие сессии, не ты. -1. **У каждой вещи одно место.** Цвет и размер — только `tokens.css`. Данные — только `src/api/`. **Слова интерфейса — только каталог `src/i18n/ru.ts`** (с S3.7; в разметке их ловит гейт ESLint, в комментариях, CSS и скриптах — `src/i18n/catalogue.test.ts`). Если значение появилось во втором месте, это ошибка, а не удобство. -2. **Плоско и скучно.** Никаких абстракций «на будущее», фабрик, слоёв ради слоёв. Проектный норматив: механизм строится там, где несёт ценность, а не ради архитектурной красоты. Дублирование двух строк лучше преждевременного обобщения. -3. **Файл = один компонент** плюс его `.module.css` рядом. Больше ~150 строк — делить. -4. **Компоненты глупые.** Данные приходят пропсами. Запросы живут на уровне экрана, не в листьях дерева. -5. **Состояние ровно в двух местах:** TanStack Query — всё серверное, Zustand — состояние интерфейса. Копий серверных данных в локальном состоянии нет. Контекстов «на всякий случай» нет. -6. **Глобальных стилей два файла:** сброс и `tokens.css`. Всё остальное — CSS Modules, они скоупятся сами. -7. **Никаких обёрток над обёртками** и никаких UI-китов. -8. **Имена человеческие, без аббревиатур** — проектная норма. -9. **Комментарии короткие:** одна-две строки «почему», не «что». Простыни не нужны. -10. **Каждый экран открывается в изоляции** — свой маршрут и своя фикстура, чтобы его можно было снять скриншотом и проверить отдельно от остальных. +**Сами правила — в `FRONTEND_PLAN.md` §3, и там у каждого стоит его проверка** (машинный гейт либо ревью-вопрос с однозначным ответом). Здесь их перечня нет намеренно: правило без проверки — лозунг, а два экземпляра списка расходятся. **Проверка на поддерживаемость, которую применяй к своему коду:** добавление нового состояния главы или нового вида замечания должно требовать правки **одного** файла. Если требует трёх — структура неверная, переделай до того, как двинешься дальше. ## 6. Данные: моков придерживаться, API не выдумывать -HTTP-слоя ещё нет. **Весь доступ к данным — через `src/api/`, внутри которого сейчас моки.** Компоненты не знают, что данные поддельные. Когда появится сервер, меняется один модуль. - -Заведи фикстуры на одну книгу целиком: метаданные, 5–8 глав, у каждой главы оригинал и перевод по несколько абзацев, несколько помеченных мест, таблица банка на 20–30 терминов в разных состояниях, состояние прогона. +**Весь доступ к данным — через `src/api/`, внутри которого пока моки.** Компоненты не знают, что данные поддельные; когда появится сервер, меняется один модуль. Мир фикстур на маршрут — карта в `FRONTEND_PLAN.md` §2 и шапка `src/mock/worlds.ts`. **Материал для фикстур бери реальный** — китайский текст с русским переводом, имена и термины. Выдуманный «Lorem ipsum» даст неверную плотность: кириллица длиннее латиницы, иероглифы короче всего, и колонки поедут. Пример реального фрагмента лежит в `backend/example/chapter1-zh.txt` (читать можно, править нельзя). -Состояния фикстур — по enum `BookStatus` контракта (`docs/architecture/14-api-contract/openapi.yaml`: десять значений; `paused` добавит правка S3 — D39.99/100 К-8); исторический список: книга не начата · разбирается · переводится (первый проход) · ждёт подписи банка · переводится (финал) · готова · остановлена пользователем · отклонена (не удалось разобрать файл) · прервана ошибкой. +Состояния фикстур — по словарю КОНТРАКТА, а не по списку в прозе: перечень значений живёт в канонической спеке `docs/architecture/14-api-contract/openapi.yaml` и приезжает в код генерацией. Прозаический список снят намеренно — он отставал от спеки на редакции. --- @@ -341,59 +274,11 @@ HTTP-слоя ещё нет. **Весь доступ к данным — чер Без этого цикла ты пишешь вслепую: код валиден, вид случаен. Это единственный способ выполнить требование «скопировать Fleet». -**Установка проверена на этой машине — делай ровно так, sudo не нужен ни на одном шаге.** Среда: WSL2, Ubuntu 24.04, Node 22, пароль root недоступен. +**Стенд ставится без sudo** (WSL2/Ubuntu, пароль root недоступен): Chromium Playwright плюс четыре системные библиотеки локально в `.tooling/`. Процедура — `FRONTEND_PLAN.md` §4, второго её экземпляра здесь нет намеренно: команды уже разошлись бы (там же CJK-шрифт стенда, без которого весь китайский в кадре — квадраты). -```bash -npm i -D playwright -npx playwright install chromium # БЕЗ --with-deps: он требует sudo и ставит лишнее -``` +Проверка, что цикл живой, если ставишь его заново: отрисуй страницу с панелью `#17191a` на фоне `#090909`, радиус 6, отступ 8 — сними, открой, убедись, что видишь именно это. Браузер headless, окна нет, графический сервер не нужен. -Chromium запустится не сразу: не хватает четырёх системных библиотек (`libnss3.so`, `libnssutil3.so`, `libnspr4.so`, `libasound.so.2`). Ставятся локально, без root: - -```bash -mkdir -p .tooling && cd .tooling -apt-get download libnss3 libnspr4 libasound2t64 -for d in *.deb; do dpkg -x "$d" root; done -rm -f *.deb && cd .. -``` - -Путь к ним — `.tooling/root/usr/lib/x86_64-linux-gnu`. Пропиши его прямо в команду, чтобы никто о нём больше не думал: - -```json -"scripts": { - "shot": "LD_LIBRARY_PATH=$PWD/.tooling/root/usr/lib/x86_64-linux-gnu node scripts/shot.mjs" -} -``` - -В `.gitignore`: `.tooling/` и `.shots/` — бинарники и снимки в репозиторий не едут. - -Проверка, что цикл живой (сделай её до всякого интерфейса): отрисуй страницу с панелью `#17191a` на фоне `#090909`, радиус 6, отступ 8 — сними, открой, убедись, что видишь именно это. Работает браузер headless, окна нет, графический сервер не нужен. - -### 7.2. Порядок твоей сессии - -**S0 — план (кода нет).** Прочитай всё из блока обязательного чтения. Напиши -`frontend/docs/FRONTEND_PLAN.md`: таблица пинов (пакет · версия · дата релиза · зачем именно нам, -сверенная по npm), карта `src/` с назначением каждой папки, правила поддерживаемости в проверяемой -форме, протокол скриншот-цикла и критерий сравнения с референсом. **Заленди отдельным коммитом.** - -**S1 — инструменты и токены.** - -1. Проверь `node -v` — нужно **≥ 22.22**. Установи Playwright по процедуре из §7.1 (без sudo). -2. Каркас: Vite + React + TS, ESLint + stylelint + prettier, единая команда `npm run check`. -3. Скриншот-цикл и проверка, что он живой (страница с панелью на фоне, снять, посмотреть). -4. `tokens.css` — значения из `STACK_DECISIONS`/§1.1 этого промта, с комментарием об источнике. - Задай `@layer` явно: порядок каскада между стилями React Aria, токенами и модулями — источник - тихих расхождений. Сразу стилизуй скроллбары: дефолтные на Windows толстые и светлые, они - ломают вид на трёх панелях. -5. **Витрина**: страница из панелей, вкладок, строки дерева и строки таблицы. Сними, положи рядом - с `fleet.png`, **посмотри обе**, сведи. -6. Контракт-тест токенов: рендерим корень, сверяем вычисленные значения CSS-переменных с - замеренными числами. Это заменяет визуальный гейт с эталонами. - -**На витрине остановись.** Если токены не сошлись — это и есть результат сессии, честно описанный; -дальше не иди. Экраны пишут следующие сессии. - -### 7.3. Ревью исполнением — обязательно +### 7.2. Ревью исполнением — обязательно Проектная норма: любая сессия, пишущая код, обязана **проверять себя исполнением**, а не заявлением. Для тебя это значит: @@ -404,7 +289,7 @@ rm -f *.deb && cd .. Отчёт в конце: что построено, что видно на скриншотах, где отошёл от референса и почему, что осталось. -### 7.4. Чего не делать +### 7.3. Чего не делать - Не изобретать API и не ходить в `backend/` за данными — только моки. - Не тащить UI-киты и не «улучшать» референс своим вкусом. @@ -414,36 +299,24 @@ rm -f *.deb && cd .. --- -## 8. Готово — это когда (для ТВОЕЙ сессии, S0+S1) +## 8. Критерий сравнения с референсом -- `FRONTEND_PLAN.md` заленден отдельным коммитом до кода. -- `npm run check` зелёный и делает все четыре шага. -- `npm run shot` снимает PNG, ты его открывал и смотрел. -- `tokens.css` — единственный источник цвета и размера; гейт против хардкода включён и **проверен - на живом нарушении** (вставь `#fff` в компонент, убедись, что проверка падает, убери). -- Витрина снята и сведена с `fleet.png`; расхождения названы вслух. -- Контракт-тест токенов проходит. +⚠ **Не растровое совпадение.** JetBrains Fleet закрыт (скачивание прекращено 22.12.2025), референс +снят на macOS при 2x, наша цель — Windows-Chromium с другим хинтингом шрифтов. Пиксель-в-пиксель +недостижим и целью не является. Сходиться обязаны: **замеренные цвета, промежутки 8px, радиусы +6px, плотность строк и общее впечатление**. Кегли и интерлиньяж — подбором на глаз до похожести, +потому что померить их больше негде. -⚠ **Критерий сравнения — не растровое совпадение.** JetBrains Fleet закрыт (скачивание прекращено -22.12.2025), референс снят на macOS при 2x, наша цель — Windows-Chromium с другим хинтингом -шрифтов. Пиксель-в-пиксель недостижим и не является целью. Сходиться обязаны: **замеренные цвета, -промежутки 8px, радиусы 6px, плотность строк и общее впечатление**. Кегли и интерлиньяж — подбором -на глаз до похожести, потому что померить их больше негде. +Гейт против хардкода цвета и размера обязан быть **проверен живым нарушением** (вставить `#fff` +в компонент, убедиться, что проверка падает, убрать) — а не объявлен зелёным. Что именно уже +проверено этим способом и чего гейты НЕ видят — `FRONTEND_PLAN.md` §5.4. -## 9. Что уже известно про движок — учесть в плане, не открывать заново +## 9. Форма данных — из контракта -Разобрано по коду 02.08. Влияет на форму данных, поэтому знать надо уже на S0. +⚠ **Пересказ того, что движок отдаёт фронту, снят: он отставал от канона.** Форму задаёт +ратифицированный контракт `docs/architecture/14-api-contract/` (спека + спутник с провенансом +и К-вопросами), а что достраивается в движке — `STACK_DECISIONS.md` §8. Цена расхождения +замерена на этой же зоне: редакция 0.3.0 сняла с провода пофазный прогресс, который здесь ещё +предписывалось строить (К-10, вердикт «НЕ строить», D39.138). -- **Индикатор прогресса нельзя строить на «готово N из M»**: unit становится `done` только когда - есть и черновые строки всех его членов, и строка редактуры. Во время черновой волны это ноль - почти всё время. Правильно — **пофазно**: `черновик N/M` ∥ `редактура N/M`. -- **Подпись термина — не UPDATE строки.** Банк пересобирается из файлов на каждом прогоне, прямая - запись в таблицу молча стирается следующим прогоном. Форма контракта подписи это учитывает. -- **Байтовых смещений у замечаний нет** — только счётчики. Поэтому подсветка сомнительных мест - идёт **на уровне блока и главы**, а не внутри текста. Владелец это принял 02.08 (§4.9), - вопрос закрыт. -- **Гранулярность пар грубее ожидаемой**: финальный текст существует на уровне edit-unit, замерено - ~1.9 блока на главу — местами вся глава окажется одним блоком. Читалка обязана нормально - выглядеть и в этом случае. - -Полный список того, что достраивается в движке — `STACK_DECISIONS.md` §8. +Что остаётся правилом ФРОНТА, а не контракта, — `FRONTEND_PLAN.md` §6. diff --git a/frontend/docs/STACK_DECISIONS.md b/frontend/docs/STACK_DECISIONS.md index 4a47400b..a03add0f 100644 --- a/frontend/docs/STACK_DECISIONS.md +++ b/frontend/docs/STACK_DECISIONS.md @@ -64,7 +64,6 @@ «текста нет в разметке» держит ESLint по МЕСТУ (буква в `JSXText` или в читаемом атрибуте), поэтому оно ловит и русский литерал, и английский — алфавит тут ни при чём; правило «код зоны английский» держит `src/i18n/catalogue.test.ts`, потому что комментарии, CSS и python в AST линтера не входят. -Первая редакция держала оба правила алфавитом и в двух носителях сразу — переделано по ревью. Легальных квартир русского ровно две, и обе — данные: сам каталог и фикстуры `src/mock/`. **Почему React Router, а не TanStack Router.** Оба выбора обоснованы: TanStack даёт типобезопасность @@ -111,11 +110,13 @@ | Браузер | `@playwright/test 1.62.1` (точный пин) | | Контракт API | `@stoplight/spectral-cli 6.16.3` + `openapi-typescript 7.13.0` | -**Контракт API — машинный артефакт, а не проза (04.08, дофикс ревью оркестратора).** -Нормативная поверхность — OpenAPI 3.1 в `docs/api-contract/openapi.yaml`: spectral линтует её -шестым шагом `npm run check`, openapi-typescript генерит из неё `src/api/schema.ts`, тест -дрифта не даёт спеке и типам разъехаться. Прозаический контракт расходится с кодом ровно тем -способом, ради предотвращения которого заведена строка 95 единого бэклога. +**Контракт API — машинный артефакт, а не проза.** Спека OpenAPI 3.1 в +`docs/api-contract/openapi.yaml`: spectral линтует её шагом `npm run check` (внутри +`npm run contract`), openapi-typescript генерит из неё `src/api/schema.ts`, тест дрифта не даёт +спеке и типам разъехаться. ⚠ **На время фриза зоны нормативен ТОЛЬКО канон +`docs/architecture/14-api-contract/` (D39.142 п.5): зонная копия — отстающее зеркало, +байт-равенства сегодня НЕТ.** Прозаический контракт расходится с кодом ровно тем способом, +ради предотвращения которого контракт и заводили. ⚠ `openapi-typescript` объявляет пир `typescript@^5.x` при нашем намеренном TS 6 — `--legacy-peer-deps` ОТВЕРГНУТ проверкой (после него ломается обычный `npm install`), взят точечный `overrides` на один пакет; `npm ci` с нуля проходит. Хвост — `BACKLOG.md` Ф-23. @@ -145,16 +146,15 @@ npm run check # prettier → eslint → stylelint → spectral (контр npm run check:full # + vite build + npm run shot (маршруты и axe) + npm run scenes (сценарии) ``` -⚠ Состав сверен S4 14.08 по `package.json` (`node -e` по `scripts.check`): **шесть** шагов у -`check` и три добавочных у `check:full`. Раньше здесь стояли четыре — список отстал на два пака. -⚠ Промт S4 §7 назвал семь; это ошибка промта, и первая редакция этой правки её повторила. +⚠ Состав сверен по `package.json` (`node -e` по `scripts.check`): **шесть** шагов у `check` +и три добавочных у `check:full`. Число шагов проверять этой командой, а не по прозе доков: +здесь и в промтах оно уже отставало. CI вызывает **именно их**, а не дублирует список инструментов. Path-фильтры на уровне job'ов (правка CSS не должна гонять тесты Go) плюс агрегирующий job с явной проверкой -`contains(needs.*.result,'failure')||contains(needs.*.result,'cancelled')`. ~~Git-хуков в MVP нет~~ — -пересмотрено 02.08 запросом владельца: CI ещё не поднят, и до него pre-commit — единственный -машинный рубеж. Хук зовёт те же `npm run check`-команды, не дубль списка (`scripts/githooks/`, -детали — `FRONTEND_PLAN.md` §7). +`contains(needs.*.result,'failure')||contains(needs.*.result,'cancelled')`. **Pre-commit хук есть** +(запрос владельца 02.08): CI ещё не поднят, и до него хук — единственный машинный рубеж. Он зовёт +те же `npm run check`-команды, не дубль списка (`scripts/githooks/`, детали — `FRONTEND_PLAN.md` §7). **Визуальный гейт с эталонными скриншотами — ОТЛОЖЕН.** Он флейкует между платформами, а наш референс снят на macOS при 2x, целевая платформа — Windows. Вместо него **контракт-тест токенов**: @@ -176,23 +176,13 @@ CI вызывает **именно их**, а не дублирует списо ссылок на прочтение. Причины две: авторские права на исходники и политика поисковиков в отношении машинно-сгенерированного контента. -## 5. Платформа +## 5. Платформа — только то, что видно фронту -| Что | Пин | Заметка | -|---|---|---| -| Go | `1.26.4` в `go.mod`, ⚠ тулчейн сборки платформы **≥1.26.6** (`platform/Makefile` `GO_MIN_VERSION`, ратифицирован D39.130; сверено S4 14.08 по самому Makefile); пин живёт в Makefile, здесь только указатель | | -| HTTP | стандартный `net/http` + `ServeMux` | роутер-библиотеку не тянуть | -| Postgres | `pgx v5.10.0` | нижняя допустимая граница 5.9.2, **не** 5.9.0 | -| Миграции | `goose v3.27.3` | как библиотека, `embed.FS`, `WithLocker` | -| Очередь | `river v0.42.0` | на том же Postgres | -| Безопасность | `govulncheck` | обязательный гейт CI | - -**Redis не заводим нигде** — зафиксировано как архитектурное «нет», иначе он приползёт по частям. - -**Конкурентность по книге.** Одна книга = один процесс-воркер; сериализация в очереди по `book_id` -плюс пиннинг книги к одному хосту на MVP. Лизы `book_leases` с heartbeat — поверх, ради вежливого -ожидания вместо аварии. Глобальный брокер исходящих LLM-вызовов — отдельный компонент, включаемый -по гейту «до второго параллельного пользователя». +⚠ **Пины и внутреннее устройство платформы здесь БОЛЬШЕ НЕ ДУБЛИРУЮТСЯ** — их дом +`platform/docs/STACK_DECISIONS.md` (Go/тулчейн, pgx, goose, river, govulncheck, «Redis нет», +конкурентность по книге и лизы), и зонная копия отставала: она называла тулчейн старой версии +против ратифицированной D39.130. Ниже остаются два шва, на которых стоит КОД ФРОНТА, — стрим +и предъявление сессии. **Прогресс — SSE, и события ПУШИТ воркер**, а не фронт опрашивает read-model (см. §7, риск про ре-ингест). Браузер — нативный `EventSource` на cookie; десктоп и CLI — обычный GET с @@ -244,17 +234,26 @@ CSRF только на cookie-пути: `Sec-Fetch-Site` → фолбэк на ` ## 8. Что придётся достроить в движке -Через оркестратора, в зону `backend/`, в порядке критичности. Обосновано разбором кода. +Через оркестратора, в зону `backend/`. Обосновано разбором кода. -1. **Пофазный прогресс** `draft N/M` ∥ `edit N/M` поверх `chunk_status`. Сейчас unit объявляется - `done` только когда есть и все draft-строки его членов, и edit-строка — то есть во время черновой - волны индикатор показывал бы 0% почти всё время. -2. **Персист манифеста глав и чанков** (+ `chunker_version` и хеш источника). Нужен под экран - разбора и дерево глав, и снимает ре-ингест: сейчас каждый read-вызов заново читает и режет - исходник — замерено 1.42–1.51 с процессорного времени на книге 23 МБ, и это умножается на число - книг в библиотеке. -3. **JSON-выход таблицы подписи банка** — сейчас только человеческий текст с ограничением 20 строк. -4. **Приём внешнего `TRACEPARENT`** в `cmd/tmctl`, чтобы прогон был одной трассой. +**Живое:** + +1. **Приём внешнего `TRACEPARENT`** в `cmd/tmctl`, чтобы прогон был одной трассой. Не построено: + грепа `traceparent` по `*.go` — ноль хитов (положительный контроль: греп по `trace` в тех же + каталогах хиты даёт). +2. **Машиночитаемая таблица ПОДПИСИ.** Весь банк JSON-сайдкаром уже есть + (`internal/pipeline/bankexport.go`, D39.122; единый бэклог, строка 169), а стоп-таблица подписи + по-прежнему человеческий текст с капом (`cmd/tmctl/render.go`, константа `bankStopStdoutCap`). + ⚠ Дверь правок банка при этом смонтирована — `tmctl bank-apply` (D39.162), см. спутник контракта + §2.19. + +**Закрыто — не переоткрывать:** + +- **Пофазный прогресс** `draft N/M` ∥ `edit N/M` — вердикт **«НЕ строить»** (D39.138; спутник + контракта, К-10). Фаз на проводе нет: одна полоса до ближайшей остановки. +- **Персист манифеста глав и чанков** (+ `chunker_version`, хеш источника) — **построено** + (`backend/internal/pipeline/manifest.go`). Он же снял ре-ингест: прежде каждый read-вызов + заново читал и резал исходник, замерено 1.42–1.51 с процессорного времени на книге 23 МБ. Спаны для подсветки **внутри** текста — **не делаем** (владелец решил 02.08): у дешёвых гейтов нет ни одного байтового смещения, только счётчики, а подсветка на уровне блока признана достаточной. diff --git a/frontend/docs/frontend-PROGRESS.md b/frontend/docs/frontend-PROGRESS.md index 899f1896..7fdd49ae 100644 --- a/frontend/docs/frontend-PROGRESS.md +++ b/frontend/docs/frontend-PROGRESS.md @@ -5,22 +5,18 @@ Зона заморожена, работы не заказываю: перечисленное ждёт разморозки и правится ТВОЕЙ сессией (её журнал и бэклог — твоя зона). 1. **В-3 (каким словом называть ступень замечания) — с листа владельца СНЯТ.** D39.176 п.4 (30.08): фразы В-3 · К-6 · Приложение А ДЕЛЕГИРОВАНЫ проекту, подписи владельца не ждём. Требование ИНЖЕНЕРНОЕ: фраза не литерал ни в Go платформы, ни в TSX фронта, а ДАННЫЕ по машинному коду причины и локали; словарь — один на продукт. То же снимает развилку **Ф-61** («платформа отвечает по-английски») — направление задано, выбор «кто и чем» больше не открыт. 2. **Ф-43 протухла обеими половинами** (сверено деревом 30.08): движковый сайдкар банка построен (`backend/internal/pipeline/bankexport.go`), платформа его декодирует и отдаёт проекцией. Основание «S5 непостроим» и зависимости Ф-44/Ф-46 «после Ф-43» больше не держатся. Первый пинг об этом был 16.08 и не исполнен — потому повторяю. -3. **В-1** утверждает «платформы ещё нет ни строки» — платформа живёт с 04.08 и прошла паки P0–P11 плюс sqlc; половина вопроса снята фактом. +3. **В-1** утверждал «платформы ещё нет ни строки» — платформа живёт с 04.08 и прошла паки P0–P11 плюс sqlc; половина вопроса снята фактом (текст строки В-1 поправлен 02.09 при чистке журнала). 4. **Состояние зеркала контракта — 0.2.3 против канона 0.9.0** (0.9.0 ЗАЛЕНДЖЕН 31.08 с паком P12, D39.180): СЕМЬ миноров, первый из них ломающий, 16 операций против 20. Синк зеркала + перегенерация типов — первое касание при разморозке, ОДНОЙ перегенерацией. -> **Файл переименован 04.08 по слову владельца: был `PROGRESS.md`, стал `frontend-PROGRESS.md`.** -> Причина — путаница с `docs/PROGRESS.md` оркестратора: два файла с одним именем в одном -> дереве, и ссылка «PROGRESS.md» без пути читалась по-разному в разных доках. -> -> **Оркестратору:** прогресс фронта пишется СЮДА же, в этот файл, а не в `docs/PROGRESS.md` — -> так у зоны один журнал и конфликтов правок в общем файле не возникает (слово владельца 04.08). -> -> Зонный журнал: сессии фронта пишут сюда сами и работают независимо (решение владельца 02.08). -> Хвосты — `BACKLOG.md`, план и замеры — `FRONTEND_PLAN.md`, ратифицированные пины — -> `STACK_DECISIONS.md`, контракт API — спека `api-contract/openapi.yaml` и спутник -> `API_CONTRACT_DRAFT.md` (`API_CONTRACT_INPUT.md` — исполненный вход, под ⚠-баннером). -> Сверху — текущее состояние, ниже — хроника по сессиям. +> **Зонный журнал** (решение владельца 02.08): сюда пишут и фронт-сессии, и оркестратор; в +> `docs/PROGRESS.md` фронт не пишет (роль-таблица `CLAUDE.md`). Хвосты — `BACKLOG.md`, план и +> замеры — `FRONTEND_PLAN.md`, ратифицированные пины — `STACK_DECISIONS.md`. Контракт: канон — +> `docs/architecture/14-api-contract/` (спека + компаньон-README), зонное зеркало — +> `api-contract/openapi.yaml` (отстаёт РАТИФИЦИРОВАННО, см. пинг №20 п.4); `API_CONTRACT_DRAFT.md` +> и `API_CONTRACT_INPUT.md` — исполненные входы под ⚠-баннерами, не поддерживаются. +> Сверху — состояние и пинги, ниже — хроника. Записи сессий S0–S4 и их приёмок (02–14.08) вынесены +> в [archive/frontend-PROGRESS-S0-S4.md](archive/frontend-PROGRESS-S0-S4.md) (слово владельца 16.08). ## 📌 ПИНГ ОРКЕСТРАТОРА (29.08, D39.169) — новый терминальный кадр SSE `session_ended`, зоне при разморозке @@ -42,175 +38,6 @@ ## Текущее состояние -> **Запись оркестратора №15, 07.08.** Абзацы ниже — состояние на 04.08, и первый из них с тех пор -> УСТАРЕЛ: «замок на входе закрыт, контракта API v0 нет» больше не так. Контракт ратифицирован -> **D39.99**, канон — `docs/architecture/14-api-contract/` (спека + компаньон-README), зонная копия -> `api-contract/openapi.yaml` — байт-зеркало канона. Вторая сторона контракта, платформа, **написана, -> принята и залендена** (D39.109): вход через OIDC, сессии, кредитный леджер, админ-CLI. Строка 95 -> единого бэклога закрыта. -> -> **S3 ИСПОЛНЕН и ПРИНЯТ D39.115** (`60595f6`); промт отработан и заархивирован. Было в этой строке до 08.08: «запускается по слову владельца, промт готов» (`S3_SESSION_PROMPT.md`) и с 07.08 несёт три -> новых пункта списка правок спеки: **А-нуль** — шапка `info` спеки исправлена мной (три дня она -> называла себя нератифицированным черновиком S3 и ссылалась на несуществующий рядом компаньон; -> S3 остаётся бампнуть `version`) · **В-трис** — управляемый потолок прогона, решение владельца -> 07.08 (шкала в ИНТЕРФЕЙСЕ от минимума до доступного остатка, единица — ГЛАВЫ, не доллары, иначе -> это амендмент D39.84; максимум = баланс минус открытые холды ⚠ ФОРМУЛА ОШИБОЧНА, superseded D39.115 п.2(а): максимум = Balance КАК ЕСТЬ, холд уже дебет (вычитание дважды резало шкалу; испр. D39.125); носитель пересчёта «главы → деньги» -> — платформа, ручка объявляется правкой спеки) · **В-кватер** — фронт живёт на ТОМ ЖЕ origin, что -> платформа: CORS-слоя в ней нет вовсе, preflight `OPTIONS` с чужим `Origin` получает 401 от гарда -> сессии (замер на живом бинаре, PD-96 регистра платформы) ⇒ в деве ходить через прокси -> dev-сервера, кросс-origin не проектировать. -> -> **Мелочь зоны, которую стоит закрыть при следующем касании:** `API_CONTRACT_DRAFT.md` — второй -> экземпляр компаньона; канон компаньона теперь `docs/architecture/14-api-contract/README.md`, -> поэтому зонному файлу нужен баннер-указатель, иначе это второй носитель одного состояния. - - -> **Запись оркестратора №15, 08.08 (после приёмки S3) — СЛОВО ВЛАДЕЛЬЦА О КАЧЕСТВЕ, гейт на S4.** -> Владельцу НЕ нравится текущее качество фронта; названная претензия — «логика спама вкладками»: -> вкладки обязаны работать **ровно как в VS Code** — одиночный клик открывает PREVIEW-вкладку, -> следующий одиночный клик ЗАМЕЩАЕТ её содержимое (новая вкладка не плодится), пин вкладки — по -> двойному клику/взаимодействию с содержимым. Полный список претензий владелец сформулирует -> отдельным входом — этот абзац лишь первый пункт, НЕ весь список. **Диспозиция оркестратора:** -> S4 НЕ стартует до списка; когда список придёт — сначала «S3.5 фикс-пак оболочки» (вкладки -> по VS Code-модели + Ф-19 прокрутка + список владельца), затем S4 на починенной оболочке. -> Промт S3.5 выдаёт оркестратор по слову владельца. - -> **Запись оркестратора №16, 09.08 — список владельца ПРИШЁЛ, промт S3.5 ВЫДАН.** Абзац выше -> исполнен: полный список претензий (24 замечания, снятые владельцем с живого прогона витрины) -> получен 09.08 и завёрнут в промт **`S35_SESSION_PROMPT.md`** (эта папка): VS Code-модель -> вкладок + Ф-19 + все 24 номера блоками, приёмка кадрами ДО/ПОСЛЕ и playwright-сценариями, -> референс драг-ручки — vojo (`src/app/components/page/Page.tsx:191-300`, `style.css.ts:11-70`). -> Запуск сессии — по слову владельца; S4 остаётся гейчен до приёмки S3.5. Попутное: замечание -> (10) владельца независимо подтвердило Ф-21 — полоска замечания нечитаема без слова; само -> слово ступени — по-прежнему В-3 на владельце. Референс `antigravity_add_folder.png` докинут -> владельцем 09.08 (модал добавления книги, замечание (12)). - -> **Запись фронт-сессии S3, 08.08.** Абзац «S3 НЕ сделана: замок закрыт» ниже — состояние на -> 04.08, и он УСТАРЕЛ целиком: замок открыт, **S3 сделана**. Хроника — первым разделом ниже. -> Коротко: `src/api/` асинхронен и разговаривает HTTP; MSW стоит СЕТЕВЫМ моком, поэтому приложение -> делает настоящий `fetch` и настоящий `EventSource`; фикстур шесть миров, по маршруту на каждый; -> живой прогресс идёт по SSE и двигает счётчик в браузере (замерено, не предположено); `types.ts` -> удалён, экраны живут на типах контракта; ветка неизвестного значения стоит на шве. Спека -> бампнута до **0.2.0** со всем списком правок промта — канон правит оркестратор по -> канон `docs/architecture/14-api-contract/` (патч ПРИМЕНЁН и удалён, D39.115). Ф-15 и Ф-22 закрыты, заведены Ф-26..Ф-31. -> `npm run check` — 7 шагов, **108 тестов** на 08.08 (число живёт в батарее, а не здесь: сверяется прогоном), зелёный; `npm run shot` — шесть маршрутов, гейт -> доступности зелёный на всех. - -> **Запись фронт-сессии S3.5, 09.08.** Фикс-пак оболочки по 24 замечаниям владельца ИСПОЛНЕН — -> прогон по всем номерам первым разделом хроники ниже. Коротко: вкладки живут по модели VS Code -> (предпросмотр/закрепление), Ф-19 закрыта keep-alive панелей, полоска ресайза больше не залипает -> (причина найдена в коде библиотеки), настройки и добавление книги открываются модальными окнами, -> лупа стала палитрой быстрого перехода, банк перепридуман со спойлерами по окну применимости, -> кнопка-заглушка «Подписать банк» снята в пользу честного состояния, палитра ушла в тёплый подтон -> (три варианта кадрами), причина «шакальных» иконок найдена в сетке набора и вылечена. Гейты -> зелёные, **контраст axe — ноль узлов на всех семи маршрутах** (Ф-11 закрыта замером). -> Адверсариальное ревью тремя линзами исполнено, находки и диспозиции — там же; две из них были -> моими дефектами по существу (правка полос увела ОТ референса; тронуто слово паузы, закреплённое -> за владельцем) и откачены. -> Ждут владельца: В-7 (масштаб интерфейса), В-8 (слово состояния в дереве), Ф-38 (защита вкладки -> «Замечания»), выбор палитры. Ждёт оркестратора: два черновика правки спеки — поля банка и -> `BookIntake.title` (в конце записи S3.5). - -> **Запись фронт-сессии S3.6, 10.08.** Второй круг замечаний владельца (10 пунктов, снятых -> с прогона после S3.5) отработан. Коротко: палитра переведена на НЕЙТРАЛЬНЫЙ ряд, снятый -> замером с `fleet_2.png` и `antigravity_*.png` (тёплый подтон S3.5 был моей отсебятиной -> ПРОТИВ этих же референсов); вкладки перестроены по замеру fleet_2 — полоса вкладок это ХРОМ, -> активная вкладка вырезана из него цветом полотна; найдена и починена причина обрезки коротких -> названий; ручка ресайза перенесена из vojo дословно, включая числа упоров; линия жёлоба -> читалки — в цвет оболочки, ширина набора отпущена по окну; **банк памяти переделан в таблицу** -> и открывается вкладкой в центре, спойлер снят с ПЕРЕВОДА и поставлен на описание; иконки книги -> и главы разведены и несут состояние цветом. Первым разделом записи — АУДИТ всех 24 буллетов -> S3.5 по коду (просьба владельца), с честными вердиктами второго круга. -> Ждут не меня: две дыры платформы/контракта под банк (ниже, «Жалобы») и подписной механизм S5. - -> **Сессия S3.6 ЗАКРЫТА 10.08 (разделы 12–15 хроники).** Дерево готово к лендингу, фронт не -> коммитит. Гейты: `npm run check:full` зелёный — 160 тестов, 7 маршрутов, 7 сценариев, ноль -> блокирующих нарушений axe, драг панели p90 17 мс. Новые строки бэклога Ф-49…Ф-51 (свежесть -> текста главы · потерянные кадры потока · resync сбрасывает один гард из двух) заведены по -> ресёрчу отдельным агентом и проверены по коду; два моих собственных предложения тот же ресёрч -> опроверг, и это записано в разделе 15 вместе с долгами сессии. - -> **Запись оркестратора, 14.08 — ГЕЙТ S4 ПАЛ (платформа P5 принята, D39.130): живой `POST /books` -> существует; S4-промт выдаёт оркестратор по слову владельца** (вход: экраны библиотеки/загрузки + -> находки приёмки S3.7 + спек-правка 0.2.3). Попутно аудит-свип корпуса 14.08 — стейлы зонных доков, -> закрыть S4-сессией (или раньше): `FRONTEND_PLAN.md:42-43` «платформа = ноль строк кода» и `:207-208` -> ссылка на удалённый `src/api/types.ts`; `STACK_DECISIONS.md:179` «тулчейн ≥1.26.5» → ратифицирован -> **1.26.6** (D39.130), там же `:144` состав `npm run check` отстал (4 шага против нынешних 7); -> `FRONTEND_SESSION_PROMPT.md:62` «скелет P0 ждёт приёмки» → P5 принята. Два числа живут в двух -> версиях без арбитража: ключей i18n — 147 (запись S3.7 ниже) против 157 (Ф-35 и живой `ru.ts`; -> верно **157**) · цена бандла +7999 (запись) против +6971 (Ф-35). Долги без Ф-строки: RTL-проверка -> (триггер «появление I18nProvider» НАСТУПИЛ в S3.7) · спойлер достаётся поиском по странице/печатью -> в PDF (продуктовое слово) · канвас-замер не знает `lang` при двух CJK-лицах — завести Ф-строки -> S4-сессией. Промты S3/S3.5/S3.7 архивированы с баннерами-исходами (`docs/archive/prompts/FRONTEND_S3*`), -> зонные копии сняты. -> -> **Дописка 14.08 (вечер): S4-промт ВЫДАН** — `S4_SESSION_PROMPT.md` (эта папка), запуск одобрен -> владельцем. Его решения того же дня, внесённые в промт: (1) `BookIntake.title` — в спек-правку -> 0.2.3 (ратификация оркестратора как исполнение замечания 12 S3.5, владелец подтвердил); -> (2) продуктовые фразы причин отказа PD-173 пишет S4-сессия сама — по реальному флоу отказов -> платформы, в границах ПТ-33, на утверждение владельцем по отчёту; (3) Ф-52 — `--color-danger` -> осветлить на ступень до ≥3:1 силами S4 (закрыть замером). - -> **Запись фронт-сессии S4, 14.08.** Путь «у меня есть файл → книга переводится» построен и прожит -> в мире моков: форма загрузки отправляет книгу (XHR — `fetch` прогресса ТЕЛА запроса не даёт вовсе), -> 201 приносит `parsing`, разбор — отдельный видимый шаг БЕЗ выдуманного процента, отказы интейка -> (400/404/408/413) и отказ книги (три причины PD-173 с разными советами) — состояния формы и -> карточки, а не алерты. Форма запуска даёт потолок в ГЛАВАХ из `CeilingBounds`; потолочный стоп -> прожит по контракту (`EventCeiling` + `paused`), и оповещение — не тост, а несъезжающая фраза в -> статус-полосе и на карточке (аргумент в записи). Библиотека: дерево свыше 200 разделов -> складывается по сотням, книга в дереве стала ВЫБИРАЕМОЙ (без этого половина статусов была -> недостижима на карточке), все одиннадцать состояний прожиты. Контракт зонной копии бампнут до -> **0.2.3** (PD-172/173/174/180 + `BookIntake.title`); ⚠ по коду платформы `not_configured` на -> отклонённую книгу сегодня не попадает вовсе — названо в записи. Девять несущих находок приёмки -> S3.7 разобраны, и одна из них ОПРОВЕРГЛА клейм S3.7 замером (resync рвал живой поток, когда -> чтение медленное). -> -> ⚠ **Три ошибки в самом промте S4, найденные вторым проходом ревью и исправленные** (промт писала -> нейросеть, и это ровно тот случай): (1) «поле после файла = 400» — платформа так НЕ делает, она -> перестаёт читать на файле, и необязательное поле за ним просто теряется (спека и фикстура -> исправлены, два теста); (2) §7 велит записать «7 шагов у `npm run check`» — их шесть, и первая -> редакция моей правки повторила ошибку промта; (3) §4 сводит 409 на старте к «уехал потолок», а -> платформа отвечает им на пять разных состояний — совет на экране сделан причинно-нейтральным. -> Ждут владельца: **В-10** (спойлер — защита или вежливость) и **В-11** (фразы отказа PD-173 на -> утверждение). - -> **Запись фронт-сессии S3.7, 10.08.** Долги слоя данных закрыты тестами, падающими на до-фиксном -> коде (Ф-49…Ф-51), контракт зонной копии бампнут до **0.2.2** (`BankTerm.sense` обязателен), -> строки интерфейса уехали в КАТАЛОГ (`src/i18n/`, 147 ключей, механизм — `@internationalized/string`, -> уже лежавший в дереве под react-aria-components), и весь код зоны переведён на английский: -> комментарии, имена тестов, сообщения ошибок, логи скриптов. Держат это два гейта, и каждый -> проверен ЖИВЫМ нарушением — ESLint ловит текст в разметке МЕСТОМ, а не алфавитом (ловит и русский, -> и английский литерал), `src/i18n/catalogue.test.ts` подметает комментарии, CSS и python. -> `npm run check` зелёный: **266 тестов** (было 160), `npm run check:full` — 7 маршрутов и -> **9 сценариев** (добавлены `hidden` и `zoom`: раскладка при 125/150/200% с прогоном axe). -> ⚠ Находка приёмки 8.1 (HIGH) **не воспроизвелась** — замер в браузере в записи ниже, механизм, -> написанный было под неё, снят; сценарий на тот же путь оставлен сторожем (Ф-53). - -> **Запись оркестратора, 10.08 — ПРИЁМКА S3.7: принято и залендено (D39.129).** Гейты -> пере-прогнаны исполнением (279 тестов, 9 сценариев, EXIT=0); **арбитраж по 8.1 — права -> СЕССИЯ, HIGH приёмки S3.6 снята как моя ложноположительная** (разбор — приёмочной записью -> хроники). 12 находок ревью (1 high: ложный клейм про импорт `fill.ts` сценами) — носитель: -> блок находок следующего фронт-промта (S4). Канон контракта догнал 0.2.2 диффом при лендинге, -> зеркало байт-равно. **S4 — следующий этап; промт выдаёт оркестратор** (после приёмки -> платформы P5 — экран загрузки стоит на её `POST /books`). - -> **Четвёртый круг замечаний, 10.08 (раздел 12 хроники).** Четыре бага владельца по таблице банка -> закрыты, и один из них был МОЙ механизм: заморозка ширин (`useSettledWidth`) оставляла таблицу -> сжатой после драга, а перезамер показал, что она ничего не покупала — драг держит p90 17 мс и -> без неё. Ширины отданы алгоритму библиотеки, содержимое меряется канвасом по всему банку и идёт -> ПОТОЛКОМ столбца. Спойлер описания переделан с фильтра на боксе на размытие глифов. Сверх списка -> владельца найдено шесть дефектов того же класса — в том числе замер по запасному шрифту (−4.7% -> ширины) и живой `getComputedStyle`, обнулявшийся за время загрузки шрифтов. Приёмка: 158 тестов, -> 7 сценариев, 4 новые проверки, каждая падает на откате своего фикса. - -> **Запись оркестратора, 10.08 — ПРИЁМКА S3.6: принято и залендено (D39.127).** Гейты -> пере-прогнаны исполнением, 12 находок ревью (1 high) и поправки к записи S3.6 — первой записью -> хроники ниже. Промт **S3.7** выдан (`S37_SESSION_PROMPT.md`: Ф-49…Ф-51, Ф-47 ратифицирован, -> Ф-40, Ф-25, Ф-35 — словарь строк + гейт против хардкода русского; ⚠ Ф-35 в первой редакции -> промта отсутствовала — пропуск поймал владелец при ревью промта, D39.127 п.2г), запуск -> по слову владельца; **S4 — после приёмки S3.7.** Экспорт банка заведён строкой 169 -> единого бэклога (Ф-43). - - **Пройдено:** S0 (план), S1 (инструменты, скриншот-цикл, `tokens.css`, витрина), S2 (оболочка трёх панелей, слой `src/ui/`, масштаб списков, хвосты гейтов), S3 (слой данных: контракт 0.2.0, MSW, фикстуры всех состояний, живой поток, типы контракта), @@ -218,44 +45,10 @@ **S3.6 (второй круг: палитра по замеру референсов, модель вкладок fleet_2, банк таблицей, ручка из vojo дословно)**, **S3.7 (долги слоя данных: контракт 0.2.2, каталог i18n 157 ключей, код зоны на английском; D39.129)**, **S4 (загрузка книги и запуск прогона на контракте 0.2.3, - библиотека на масштабе, находки приёмки S3.7 — ждёт приёмки)**. -- ⚠ **АБЗАЦ НИЖЕ ИСТОРИЧЕСКИЙ (04.08), читать как хронику:** замок с тех пор открыт (контракт ратифицирован D39.99), S3 исполнен и принят D39.115. Оставлен, потому что объясняет, ПОЧЕМУ S3 однажды остановилась, а не как диспозиция. -- **S3 НЕ сделана: замок на входе закрыт, проверено исполнением 04.08.** Контракта API v0 - по-прежнему нет (`docs/architecture/14-api-contract.md` отсутствует; строка 95 единого - бэклога — статус «дизайн-промт по слову владельца», то есть не исполнена). Вместо S3 сессия - сделала то, что предписывает её же промт на этот случай: **довела Ф-14 до артефакта - `API_CONTRACT_INPUT.md`** и остановилась. Кода не написано ни строки — это диспозиция, - а не недоделка: моки, снятые не с того контракта, разойдутся с API, ровно ради чего - строка 95 и заведена. -- **Написан черновик контракта, принят в работу ревью оркестратора и ДОФИКШЕН (04.08).** - Форма сменена решением владельца: нормативная поверхность — **OpenAPI 3.1 - `docs/api-contract/openapi.yaml`**, `API_CONTRACT_DRAFT.md` стал обязательным спутником - (провенанс ✓/◆/○, обоснования, К-вопросы — то, чего YAML не выражает). Дом ратифицированной - копии — `docs/architecture/14-api-contract/`, зона оркестратора; после ратификации типы - генерятся из перенесённой копии — контракт первичен. -- **Гейт контракта живой:** `npm run check` стал шестишаговым (spectral на строгом пороге), - `openapi-typescript` генерит `src/api/schema.ts`, тест дрифта не даёт спеке и типам - разъехаться, ветка неизвестного значения стоит на шве (`src/api/contract.ts`), MSW отдаёт - тело в форме контракта. Четыре формы нарушения проверены живьём (`FRONTEND_PLAN.md` §5.4.2). -- **Ждёт оркестратора, четыре вещи:** (1) ратифицировать спеку и перенести в - `docs/architecture/14-api-contract/`, либо вернуть с правками; (2) три зависимости, которых - фронт заводить не может — **артефакт экспорта БАНКА** (строки в едином бэклоге нет вовсе: - 101 даёт таблицу подписи, не банк), **механизм поднятия потолка** (без него `resume` после - стопа по потолку прогон не двигает) и правка устаревшего комментария провенанса - `store/migrate.go:193`; (3) залендить дерево сессии; (4) вопросы владельцу — В-4, В-5 ниже - и К-1..К-12 спутника, из них продуктовых пять, остальные — автору контракта и платформе. -- **Ждёт владельца:** контраст палитры (`BACKLOG.md` Ф-11) и два вопроса ниже — В-1 диск, - В-2 копирайт. -- **Ждёт движка:** манифест глав и пофазный прогресс (единый бэклог, строки 99–100), - к S5 — машиночитаемая таблица подписи банка (строка 101). -- `npm run check` зелёный (6 шагов, 80 тестов), `npm run check:full` зелёный, гейт доступности - зелёный на обоих маршрутах. Гейтов стало больше: включён Ф-4 (отступы), закрыты Ф-9 и Ф-10, - по ревью оркестратора 04.08 добавлены динамические импорты швов и языковая ветка в CSS. + библиотека на масштабе, находки приёмки S3.7) — ПРИНЯТ 15.08, D39.135; фикс-лист ФС-1…ФС-12 + остаётся входом первого касания**. - **Карта канона для фронт-сессии — `FRONTEND_PLAN.md` §0.1, транспорт — §0.2.** Заведены после того, как две ошибки S1 пришли из непрочитанного канона; читать до кода, не после. -- **Передача следующей сессии написана — `S3_SESSION_PROMPT.md`** (сессией S2 по просьбе - владельца; ратифицирует оркестратор): вход в S3 под замком контракта API, который сессия - проверяет сама, плюс фолбэк на Ф-14, если замок закрыт, и перечень ловушек, купленных S2. - **Гейты стоят и на коммите:** pre-commit хук (02.08, вторая сессия) гоняет `npm run check` для коммитов с frontend-путями и блокирует смесь зон и файлы «никогда не коммитить»; ставится сам при `npm install`. Обход — только осознанный `git commit --no-verify`. @@ -270,12 +63,10 @@ вкладок у нас фиксированные (слева `Книги · Поиск`, справа `О книге · Замечания · Банк`) — пользователь их не расширяет, и fleet-овский смысл «+» («добавить инструмент») к ним не применим. -Владелец: **«+» слева = «Добавить книгу»**. Отсюда: - -| Было | Стало | Почему | -|---|---|---| -| «+ Добавить книгу» строкой в дереве, «+» в рядах вкладок нет | «+» в ряду вкладок ЛЕВОЙ панели, строка из дерева убрана | одна вещь — одно место; в референсе «+» стоит ровно там | -| — | в центре и справа «+» нет | добавлять в фиксированный набор нечего, кнопка вышла бы декоративной | +Владелец: **«+» слева = «Добавить книгу»**. Отсюда: «+» стоит в ряду вкладок ЛЕВОЙ панели и +запускает добавление, дублирующая строка «+ Добавить книгу» из дерева убрана (одна вещь — одно +место, и в референсе «+» стоит ровно там); в центре и справа «+» нет — наборы вкладок там +фиксированные, пользователь их не расширяет, и кнопка вышла бы декоративной. Остаток строки — Ф-7. В S2 действие открывает вкладку-документ «Добавить книгу»; содержимое экрана (§3.1) делает S4. @@ -292,14 +83,10 @@ Плюс три дефекта структуры в той же реплике: две кнопки настроек, банк продублирован (вкладка слева + отдельная карточка внизу), окно главы слишком мало. -Что из этого следует и сделано: - -| Было | Стало | Почему | -|---|---|---| -| Настройки в верхней полосе И внизу левой панели | только внизу левой панели | промт §3.12: настройки — отдельный раздел из низа левой панели, второй вход лишний | -| Банк во вкладке слева И карточкой в центре снизу | одна вкладка в правой панели | одна вещь — одно место; слева осталась только навигация | -| Центр разрезан на две карточки | одна панель во всю высоту | нижняя карточка была скопирована с терминала Fleet и не несла задачи | -| Кнопки сворачивания трёх панелей | двух | кнопка есть у той панели, которая существует | +Сделано: настройки — только внизу левой панели (второй вход из верхней полосы снят, промт §3.12), +банк — одной вкладкой в правой панели вместо «вкладка слева + карточка в центре», центр — одна +панель во всю высоту (нижняя карточка была скопирована с терминала Fleet и не несла задачи), +кнопки сворачивания — у двух панелей, а не трёх. **Вертикальный параллелизм — суть решения:** рабочий цикл читателя это «читаю перевод → спотыкаюсь о термин → проверяю его в банке». Все три вещи обязаны быть видны одновременно, поэтому колонки: @@ -312,32 +99,6 @@ экран открывается **вкладкой в центре**, как документ, а правая панель ведёт в него действием «Подписать банк». В витрине эта связка показана. -### 02.08 — данные: моки берут форму от движка - -Владелец: **«всё на диске лежит, и главы, и банки памяти»**. Разбор бэкенда подтвердил: моя -первая фикстура выдумывала поля (`kind: 'имя'`, `state: 'подписан'`), а промт прямо запрещает -изобретать API. Формы переписаны по настоящим: - -- банк — таблица `glossary` (`backend/internal/store/migrate.go:176`): `src · dst · type · sense · - status · source · since_ch/until_ch`. Статусы подписи ровно три: `auto|draft|approved`, - провенанс — `seed|ruby|mined` (⚠ **испр. 04.08 по ревью оркестратора:** здесь стояло - `seed|ruby|auto` плюс `mined` по комментарию схемы; комментарий устарел, а литералы `"auto"` - в коде пишут СТАТУС, не провенанс — `membank/memseed.go:328`). - **Типы — исправлено 04.08 по ревью оркестратора:** сама колонка `type` в схеме свободный текст, - и её комментарий перечень не закрывает («name|term|title|…»), поэтому истина — гейты в Go: - классификатор знает четыре (`terminology/classify.go:15`), банкнота принимает их же плюс - `nickname` (`pipeline/banknote.go:74`). Итого пять: `name|place|title|term|nickname`. - `org` и `fullname` движок не знает вовсе — они приехали в тип фронта из этой самой строки; -- пары — `ChunkExport` из `tmctl export --pairs` (`backend/internal/pipeline/export.go:25`): - `chapter · chunk_idx · disposition · flag_reason · detail · final_text · source`; -- причины замечаний — enum из `backend/internal/pipeline/disposition.go:56-104`. - -**Следствие для интерфейса, которое из этого выросло:** движок говорит своим языком — -`sanitizer_stripped`, «CJK leak in the ru output: 第一节». Промт §4.1 запрещает показывать это -пользователю. Значит между движком и экраном обязан стоять **слой перевода вердиктов -в продуктовые понятия**, и он заведён одной картой в данных: причина → человеческая фраза; -поле `detail` не показывается никогда. Это же ПТ-33 реестра требований. - ### 02.08 — шов данных заведён: моки выкидываются одной папкой Владелец: **«все моки рано или поздно придётся выкинуть; нужна минимальность моков, @@ -350,92 +111,30 @@ видит только `api/`. Правило теперь машинное, а не на совести ревьюера: `no-restricted-imports` на `**/mock/**` и запрет сетевых вызовов, с исключением для `src/api/**`. -**Две поправки к этой записи, обе от ревью 02.08 (№3) — исходные формулировки были сильнее -сделанного:** - -- «Доменных значений в разметке не осталось» — **было неверно**: в JSX оставались четыре - литерала языка (`lang="zh"`, `lang="ru"`, подпись пары `zh → ru`), и тот же проход, - который вынес числа в `book.*`, прошёл мимо них в той же строке. Починено: язык — код - в данных, имя считается `Intl.DisplayNames`, `lang` берётся из книги; правило закрыто - тестом (`src/generality.test.ts`), а не обещанием. -- «В день HTTP правится тело функций, экраны не трогаются» — **переобещание**. Верно только - про день замены MSW настоящим сервером. Асинхронность приезжает раньше, на S3, вместе - с MSW и TanStack Query — то есть ДО первого продуктового экрана (S4–S7), поэтому - переписывать экраны и правда не придётся. Но четыре вызова витрины на S3 изменятся. - ### 02.08 — что дал разбор канона: фронт мокает не тот контракт -`docs/README.md` прямо требует читать `research/23` перед любым кодом стыка. Не читал, -и это стоило двух вещей. +`docs/README.md` прямо требует читать `research/23` перед любым кодом стыка. Не читал, и это стоило +двух ошибок сразу — формы моков и способа связи. -1. **D39.85 (`research/23` §0): фронт читает ТОЛЬКО read-модель платформы в Postgres, - материализованную из NDJSON-потока событий; движок не опрашивается никогда, чтение живого - SQLite движка — запрещённый анти-паттерн.** Значит формы `glossary`/`ChunkExport` — словарь - предметной области, но НЕ форма API. Типы фронта переписаны так, чтобы это было видно: - `src/api/types.ts` объявлен рабочей гипотезой до появления контракта. -2. **Единый бэклог, строка 95:** «Контракт API v0 + продуктовый словарь статусов, ранняя - вставка, зафиксировать артефактом ДО большого кода обеих сторон — фронт пишется на моках, - без пришпиленного контракта моки и API разойдутся». Дом — `docs/architecture/14-api-contract.md`, - **файла нет**. То есть фронт делает ровно то, ради предотвращения чего строка заведена. - Для S2 (оболочка, `src/ui/`) это не помеха. **Для S3 (слой данных) — вход, которого нет.** +**D39.85 (`research/23` §0): фронт читает ТОЛЬКО read-модель платформы в Postgres, материализованную +из NDJSON-потока событий; движок не опрашивается никогда, чтение живого SQLite движка — запрещённый +анти-паттерн.** Значит движковые формы (таблица `glossary`, `ChunkExport`) — словарь предметной +области, но НЕ форма API: форма API живёт только в каноне `docs/architecture/14-api-contract/`. +Оттуда же берётся и запрет показывать пользователю язык конвейера (`sanitizer_stripped` и подобное) — +это ПТ-33 реестра требований: `detail` не показывается никогда, а вердикт превращается в фразу по +словарю машинных кодов (карта — приложение А компаньона контракта). -### 02.08 — инверсия смысла двух вердиктов (дефект, внесённый фронтом) - -Продуктовые фразы к замечаниям были написаны по ИМЕНИ причины, а не по её доккомменту, -и две из пяти получились наоборот: - -- `glossary_miss` — «Термин не подписан в банке» ⟶ **неверно**. `disposition.go:78-85`: - «an **approved** term's src fired in the chunk but no accepted dst form appears in the - output — the model ignored the glossary». Термин подписан, его проигнорировал перевод. - Фраза посылала человека подписывать уже подписанное — по банку, который в каноне - центральная ценность. -- `sanitizer_stripped` — «Часть блока не переведена» ⟶ **неверно**. `disposition.go:96-104`: - «the chunk is NOT lost — the cleaned text is committed as the export»; `status.go:165-170`: - «the **least alarming** flag». Самый безобидный флаг из восьми подан как потеря текста. - -Заодно вскрылось, что у движка есть **ратифицированная лестница важности вердиктов** -(`status.go:150-175`, ранги 1..8), а интерфейс красил все замечания одинаково — то есть -терял единственный сигнал приоритета. Введены две ступени: `attention` и `glance`. - -**Правило на будущее:** фраза к вердикту пишется по доккомменту `disposition.go`, а не по имени -причины, и рядом кладётся цитата. Соответствие целиком — в шапке `src/mock/book.ts`; его -настоящий дом — контракт API (строка 95), не фронт. - -### 02.08 — ревью №3 на полном каноне: что оно поменяло - -Владелец разрешил читать весь корпус доков и потребовал прогнать ревью после чтения. -Прочитано: `CLAUDE.md` · `docs/README.md` · CURRENT-STATE и единый бэклог · `research/23` · -`product-requirements.md` · карта D-лога. **Ответ на вопрос владельца о способе связи: -NDJSON — это шов ДВИЖОК↔ПЛАТФОРМА (D39.85), а фронту принадлежит JSON поверх HTTP плюс SSE -от платформы (D39.84). Разведено в `FRONTEND_PLAN.md` §0.2, потому что путал их я сам.** - -Ревью: 5 линз × находки, на каждую независимый скептик с установкой опровергать. -15 находок → 12 проверено → **5 выжило, 7 опровергнуто**. Хардблокеров нет; S2 не блокирован ничем. - -| Что | Диспозиция | -|---|---| -| Язык зашит в разметку (`lang="zh"`), а в типе — русское слово | починено: коды в данных + `Intl.DisplayNames` + тест-замок | -| Девять состояний нарисованы на ГЛАВАХ, хотя это состояния ПРОГОНА над книгой | починено: состояние → книга, у главы выполнение по юнитам; библиотека из нескольких книг | -| Прогресс — наивное «N из M глав», прямо запрещённое собственным планом | починено: две пары по юнитам, фикстура поставлена в середину черновой волны (наивный счётчик дал бы 0) | -| Сетевой гейт ловил только `fetch` — `EventSource` (ратифицированный транспорт) проходил молча | починено: восемь форм, все проверены живым нарушением | -| `npm run shot` зелен, когда экран не попал в список маршрутов скрипта | починено: тест сверяет `routes.tsx` и `shot.mjs` | - -**Опровергнуто (и это ценнее половины находок):** гипотеза «синхронный `src/api/` — хардблокер, -нужен свой хук уже сегодня». Два независимых скептика показали, что S2 данных не читает вовсе, -а асинхронность приезжает на S3 вместе с MSW и TanStack Query — то есть самодельный хук был бы -работой S3, которую S3 же и выбросит. Правку я не делал; переобещание в журнале поправил. - -**Что нашла не ревьюшница, а снимок:** после переноса состояний на книгу три пары из девяти -рисовались одинаково (готова ≡ не начата, перевод ≡ финал, ошибка ≡ не разобрана) — точка -не различает девять значений. Метка стала словом; цвет остался вторым каналом и живёт -на точке, а не на тексте (цвета замечания и отказа не проходят порог контраста для текста — -проверено axe: было 5 узлов, стало 14, вернулось к 5). +**Ответ на вопрос владельца о способе связи:** NDJSON — это шов ДВИЖОК↔ПЛАТФОРМА (D39.85), а фронту +принадлежит JSON поверх HTTP плюс SSE от платформы (D39.84). Разведено в `FRONTEND_PLAN.md` §0.2, +потому что путал их я сам. **⛔ ПИНГ ОРКЕСТРАТОРА №19 — 23.08, при разморозке зоны (сейчас действий не требует).** Аудит доков нашёл два расхождения внутри зоны; рукой не лезу, зона заморожена. -1. **Три из одиннадцати вопросов ниже уже ОТВЕЧЕНЫ каноном** и просто не погашены — при разморозке - сверить каждый против D-лога прежде, чем нести владельцу повторно. -2а. **Ключ `prepare` вашего `package.json` — единственный бутстрап РЕПО-ШИРОКОГО гейта доков (дописано 24.08, строка 220).** +1. **Три вопроса списка были ОТВЕЧЕНЫ каноном и не погашены** — В-3 (D39.176 п.4: фразы делегированы + проекту), В-6 (D39.132 п.2а, а кадр `EventCeiling` снят каноном 0.3.0) и В-10 (D39.138 п.2к — + «вежливость»); строки сняты 02.09 при чистке журнала. Остальные восемь — сверить каждый против + D-лога прежде, чем нести владельцу повторно. +2а. **Ключ `prepare` вашего `package.json` — единственный бутстрап РЕПО-ШИРОКОГО гейта доков (дописано 24.08; см. буллет «Гейты стоят и на коммите» в «Текущем состоянии»).** `frontend/scripts/githooks/install.mjs` — единственный в дереве установщик диспетчера `.git/hooks/pre-commit`, а тот судит `CLAUDE.md` и доки трёх зон, не только ваши. Следствие проверено исполнением: на свежем клоне без `npm install` гейт молча выключен. Просьба при разморозке: перевести `prepare` на зоно-нейтральный установщик, который заведут в @@ -448,13 +147,10 @@ NDJSON — это шов ДВИЖОК↔ПЛАТФОРМА (D39.85), а фрон | # | Вопрос | Почему это его вопрос | |---|---|---| -| В-1 | **Данные привязаны к одной машине.** Вся фактура — прогоны, экспорты, банки — лежит в `/home/ubuntu/books/gu-zhenren/` вне git; оба `backend/example/sample-*.db` пустые (только схема); пути стенда зашиты в тесты (`miner_parity_test.go:33-35`, `labelharness_test.go:43`) и в `book.yaml` абсолютными путями. Владелец сказал: «мне не нравится, что это живёт конкретно на моём диске». Лечение — платформа (`platform/README.md:20-29`: библиотека книг, хранение исходников и экспортов), но платформы ещё нет ни строки. Фронт от этого не зависит (работает на моках), но **вопрос на движок/платформу, а не на фронт** | скоуп-решение и порядок работ | -| В-3 | **Каким словом называть ступень замечания.** Сейчас `attention` и `glance` различаются только цветом полоски: синяя и серая. Зрячему разница читается ещё и светлотой, скринридеру — никак. Лечение то же, что уже применено к состоянию прогона книги: слово рядом с цветом. Но словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, — «важно / неважно» и «ошибка / предупреждение» не годятся. Строка Ф-21 | продуктовая формулировка, которую видит пользователь | -| В-6 | **Упёрся в свой потолок ≠ кончились деньги, а интерфейс их не различает.** Владелец 07.08 дал человеку поставить перед стартом колпак в ГЛАВАХ; платформа берёт холд на его сумму. Но машинная причина паузы в контракте ровно одна — `credit_exhausted`, и она про кредит. Значит прогон, честно доехавший до выбранной человеком границы, и прогон, у которого кончился баланс, приходят на экран одной фразой «перевод остановлен: лимиты исчерпаны». Первому она врёт: лимит не исчерпан, человек сам так решил, и следующее действие у него другое — поднять колпак, а не пополнить счёт. Нужно ли второе значение и какая у него фраза — продуктовое. Заведено К-13, строка Ф-31 | продуктовая формулировка, которую видит пользователь, и разное следующее действие | +| В-1 | **Данные привязаны к одной машине.** Вся фактура — прогоны, экспорты, банки — лежит в `/home/ubuntu/books/gu-zhenren/` вне git; оба `backend/example/sample-*.db` пустые (только схема); пути стенда зашиты в тесты (`miner_parity_test.go:33-35`, `labelharness_test.go:43`) и в `book.yaml` абсолютными путями. Владелец сказал: «мне не нравится, что это живёт конкретно на моём диске». Лечение — платформа (`platform/README.md`, раздел библиотеки книг: хранение исходников и экспортов); она с тех пор построена и принята (D39.109), поэтому половина вопроса снята фактом — пинг №20 п.3. Фронт от этого не зависит (работает на моках), но **вопрос на движок/платформу, а не на фронт** | скоуп-решение и порядок работ | | В-8 | **Какое слово о состоянии книги стоит в дереве.** Замечание 23 потребовало бейджу ФИКС-место, и оно сделано; но две метки словаря в него не помещаются и усекаются с тултипом — «остановлена: лимиты» (19 знаков) и ветка неизвестного «неизвестное состояние» (21). Укоротить их сессия НЕ вправе: фраза паузы зарезервирована за владельцем (В-6), и первый заход S3.5 её укоротил и был откачен собственным ревью. Варианты: (а) оставить усечение с тултипом, (б) владелец даёт короткое слово для дерева, а полная фраза остаётся в статус-полосе, (в) расширить колонку бейджа за счёт названия книги | продуктовая формулировка, которую видит пользователь | | В-7 | **Физический масштаб интерфейса на мониторе.** Замечание 1 говорит «масштабы великоваты, особенно на 27″ 2K, на 24″ терпимее». Замер при вьюпорте референса расхождения с Fleet не нашёл (§5.5 плана): промежутки, ширины панелей, шаг строки и высота вкладки совпадают. Значит речь про физический размер на конкретном экране, а он зависит от масштабирования ОС, и «просто уменьшить токены» означало бы уехать от референса, который владелец принял. Правильное лекарство — ручка «плотность интерфейса» в настройках (3 ступени), но она требует перевода ВСЕХ размерных токенов в rem и пере-замера, то есть отдельного прохода (Ф-36). Вопрос: нужна ли ручка, и какой масштаб ОС стоит на 27″ 2K — по нему видно, во сколько раз расходится физика | продуктовое решение и цена отдельного пакета | | В-9 | **Какие языки интерфейса продукт обещает.** Механизм переводимости построен (S3.7, Ф-35): слова интерфейса живут в каталоге `src/i18n/ru.ts`, второй язык — второй файл того же вида плюс строка в `src/i18n/language.ts`, ни один компонент при этом не трогается. Чего фронт сделать не может и не должен: решить, какие языки продукт обещает, и выдумать сами переводы. Пока список из одного значения (`ru`), и каркасный пункт настроек честно говорит «сейчас интерфейс только русский» | продуктовое решение и обещание пользователю | -| В-10 | **Спойлер банка — это защита или вежливость?** Замер S4: закрытое описание лежит в тексте страницы, `window.find()` его находит, то есть Ctrl+F подсвечивает закрытую ячейку; под `@media print` размытие сохраняется, но текстовый слой PDF строится из DOM. Если спойлер — ЗАЩИТА от спойлера сюжета, описание не должно приезжать в браузер до раскрытия, а это правка контракта (признак «показывать смыслы» на чтении банка). Если ВЕЖЛИВОСТЬ («не мозолит глаза, но достать можно») — сделанного достаточно, и цена названа. Строка Ф-59 | продуктовое решение и правка контракта | | В-11 | **Фразы отказа книги (PD-173) — на утверждение.** Контракт 0.2.3 несёт машинную причину (`source_unreadable` · `not_configured` · `parser_unavailable`), а слова пишет клиент — их сочинила эта сессия по слову владельца 14.08 и они ждут его «да»: «Файл не удалось разобрать на разделы» / «Проверьте файл — возможно, это не текст книги — и добавьте её заново»; «Сервис пока не готов принять эту книгу» / «Файл здесь ни при чём: повторная загрузка поможет не раньше, чем мы это наладим»; «Не удалось обработать файл на нашей стороне» / «Файл здесь ни при чём: попробуйте добавить книгу позже»; неизвестная причина — «Книгу не удалось принять» / «Причина не названа. Попробуйте добавить книгу заново или позже» | продуктовая формулировка, которую видит пользователь | | В-2 | **Авторское право на текст фикстур.** Витрина использует короткую выдержку из настоящего прогона (蛊真人). Правило проекта уже есть: `eval/.gitignore:4` держит копирайтные тексты вне git. Сейчас взят объём под проверку плотности и не больше; нужно ли убрать совсем и заменить синтетикой — решать владельцу | юридика | | В-4 | **Чем подписывать главу в дереве, если заголовок — ровно «Глава N».** Титул движок рендерит детерминистически из шаблона пары (`configs/langpacks/zh-ru/heading.txt`: `template Глава {n}`), а подзаголовок остаётся телом текста и названием НЕ является. На 2284 разделах это дерево из 2284 одинаковых по форме строк. Оставить так, показывать номер иначе или просить у контракта что-то ещё — продуктовое решение. Артефакт §1.4 и К-3 | продуктовая формулировка, которую видит пользователь | @@ -468,46 +164,24 @@ NDJSON — это шов ДВИЖОК↔ПЛАТФОРМА (D39.85), а фрон **Фикс-лист ФС (носитель состава — эта запись; вход промта S5):** ФС-1 (major, мир intake) прогон над загруженной книгой стартует с `begin(0)` — общий прогресс 0/0 весь прогон, ETA = NaN (лечение: begin суммой units_total глав); путь заказа §4 «книга из формы переводится» в мок-мире добить · ФС-2 showcase: принятый старт прогона не меняет мир — следующее чтение отдаёт прежнее состояние, «Перевести» выглядит как ничего · ФС-3 мир intake: повторный старт переиспользует id прогона — поток не переподписывается, translating вечно (verify_bank тем же кластером: наблюдаемого эффекта в моках нет, awaiting_bank недостижим) · ФС-4 `BookIntake.title` maxLength 200 не держит никто на клиенте (TextField без лимита — собственная спека нарушаема своим клиентом) · ФС-5 докблок `upload.ts` несёт СНЯТОЕ правило «field after file → 400» (неверно для необязательного) · ФС-6 два дока «spectral шестым шагом» (`STACK_DECISIONS.md:116`, `FRONTEND_PLAN.md:124`) — spectral четвёртый из шести · ФС-7 мок считает части формы по ВСЕЙ форме (платформа — до файла) и меряет 413 по file.size (платформа — по всему телу) — мок строже провода · ФС-8 флейк сцены refusals: closeModal не дождался закрытия за 5000 мс (1 из 3 на тихой машине) · ФС-9 `role="status"` оборачивает байтовый прогресс отправки — на живом XHR скринридер зачитывает каждое обновление (в регионе — только смены состояний) · ФС-10 группа из одного раздела подписывается «разделы 201–201» · ФС-11 интейк-мир: ПЕРВОЕ нажатие «Перевести» всегда 409 (счётчик; кандидат — маркер) · ФС-12 после-пакные числа Ф-35 слегка отстают от дерева (пересборка: 800293 / 246670 gzip против записанных 800219 / 243446). -**Поправки к записи S4 (чужой текст не редактирую):** «словарь платформы — четыре значения» → в коде ПЯТЬ (пропущен `schema_mismatch`) · «одна из трёх фраз сегодня недостижима» → недостижимы ВСЕ ТРИ: платформа `reject_reason` вообще не проецирует (`v0.go:497` «never projected») — доезжает только ветка неизвестного · §1/§2 записи несут снятое правило PD-172 «поле после файла = 400», которое сама же §9а опровергла (код и спека исправлены, текст записи — нет) · «136 проверок» = 134 (наивный греп ловил 2 строки vite) · «31 строка DOM / 59 618px» — замер S2 04.08, не этого пака · «46 находок → 6 выжило» = 5 различных (одна — дубль) · три поправки к записи S3.7 из заказа §6: разобрана только цена бандла; «дрейф ширин hidden» и «счёт 106 из 119» остались на записи оркестратора 14.08 (носитель там, дефекта сессии нет — фиксирую для полноты). +**Поправки к записи S4** (сама запись — в [archive/frontend-PROGRESS-S0-S4.md](archive/frontend-PROGRESS-S0-S4.md); чужой текст не редактирую): «словарь платформы — четыре значения» → в коде ПЯТЬ (пропущен `schema_mismatch`) · «одна из трёх фраз сегодня недостижима» → недостижимы ВСЕ ТРИ: платформа `reject_reason` вообще не проецирует (`v0.go:497` «never projected») — доезжает только ветка неизвестного · §1/§2 записи несут снятое правило PD-172 «поле после файла = 400», которое сама же §9а опровергла (код и спека исправлены, текст записи — нет) · «136 проверок» = 134 (наивный греп ловил 2 строки vite) · «31 строка DOM / 59 618px» — замер S2 04.08, не этого пака · «46 находок → 6 выжило» = 5 различных (одна — дубль) · три поправки к записи S3.7 из заказа §6: разобрана только цена бандла; «дрейф ширин hidden» и «счёт 106 из 119» остались на записи оркестратора 14.08 (носитель там, дефекта сессии нет — фиксирую для полноты). *(Записи сессий S0–S4 и их приёмок (02–14.08) — в [archive/frontend-PROGRESS-S0-S4.md](archive/frontend-PROGRESS-S0-S4.md); вынесено 16.08 по слову владельца. Запись приёмки S4 с фикс-листом ФС — ВЫШЕ, она живая.)* -## Пинг оркестратора №15 (09.08): контракт 0.2.1 — `503` на старте прогона; платформа-раннер заленден - -- **Контракт бампнут 0.2.0 → 0.2.1** (D39.123): `POST /books/{bookId}/runs` получил ответ **`503`** - (`ServiceUnavailable`, problem+json) — старт прогона на деплое, где шов движка не сконфигурирован; - временное состояние, ретрай позже, `Retry-After` не обещан. Зеркало `frontend/docs/api-contract/` - синхронизировано байт-в-байт этим же лендингом. При следующей сессии — перегенерировать типы из - спеки; других изменений форм нет, правка аддитивная. -- **Платформа-раннер заленден** (`d29e30c`): пять ручек `/v0` контракта живые — библиотека · - карточка · run-options · старт прогона · usage; прогресс прогона пофазный (draft/edit) в карточке. - Фронт может говорить с живой платформой вместо моков — стык проверен 47 wire-проверками приёмки. -- Известное ограничение до эмиттера (строка 103): стоп по потолку и штатный останов приезжают как - `failed` (PD-113/двойник) — на экране не строить различение этих состояний до строки 165 движка. - -## Пинг оркестратора №16 — 09.08.2026 (кросс-ссылка зонных половинок, строка 167) - -Движковая половина темы Ф-30/В-4 («глава без заголовка») — **строка 160 единого бэклога** (`titleRaw` в манифест · provenance у heading · тип chapter/fragment; research/27 §6 Этап 0). До её лендинга `heading` манифеста = ВРЕМЕННЫЙ рендер движка (D39.122 п.2д) — честный заголовок из данных приедет строкой 160, продуктовое слово — В-4 на владельце. На экране ничего не менять до обоих. - ## Пинг оркестратора №17 — 16.08.2026 (зона во фризе; контракт-ревью принято D39.138 — четыре ваших вопроса ОТВЕЧЕНЫ, входы разморозки) Контракт-ревью API v0 отработало отдельной сессией и ПРИНЯТО (отчёт — `docs/research/28-contract-review.md`; решения владельца — его §8, ратификация D39.138). Заказан ЛОМАЮЩИЙ батч 0.3.0 (строка 183 единого бэклога) — типы перегенерируются после его лендинга; до него нормативной остаётся 0.2.3. ⚠ Читать ОРИГИНАЛ отчёта, не пересказ. По вашим строкам: -1. **Ф-56 (конец интейка):** Б-6(а) — поток перевешивается на КНИГУ (`GET /books/{id}/events`) с кадрами конца разбора и конца прогона и `204` на завершённом; ваш опрос — легитимное лечение до батча. -2. **Ф-57 (пары):** Б-2 — `GET /capabilities` (пары со статусом · порог интейка · форматы · размеры страниц · версия контракта); `languages.ts` станет чтением. -3. **Ф-61 (английские фразы):** В-1 решён владельцем вариантом B (§8 п.4): машинный `code` + `request_id`; `title`/`detail` — developer-facing, **фразу рисует клиент по словарю кодов** (ваша таблица `AddBook.tsx:282-288` становится нормой); после батча строка `AddBook.tsx:289` (`problem?.detail ?? problem?.title`) и её сёстры `Loaded.tsx:66`, `RunStart.tsx:188` СНИМАЮТСЯ. -4. **Ф-62 (переименование):** Б-5 — `PATCH /books/{id}` ТОЛЬКО с `title` (409 при живом прогоне) + `DELETE`; правка — отображение, до движкового брифа не доезжает. Жанр из продукта УХОДИТ совсем (Б-23, слово владельца 16.08) — поле формы снимется при разморозке. -5. **Ф-43 протух текстом:** движковая половина банка ПОСТРОЕНА ещё D39.122 (сайдкар `.bank.json` + `bank-stop.json`), не хватает платформенной проекции (строка 169 единого бэклога, вход P7) — обновите текст строки. -6. **Решение владельца К-6 (§8 п.11):** замечания показывать ВСЕ; в тексте — сворачивать и раскрывать кнопкой; словарь ступеней заранее НЕ проектировать; зависимость `Note.id` закрывает батч. **Зарегистрируйте зонной строкой при разморозке.** +1–6. **Ф-56 · Ф-57 · Ф-61 · Ф-62 · Ф-43 · К-6 — ответы ревью, и все они с тех пор ВОШЛИ в канон** (батч 0.3.0 и далее до 0.9.0; 20 операций), поэтому при разморозке строки читаются по канону, а не по этому пересказу. **Ф-56** (п.1): поток перевешен на КНИГУ — `GET /books/{bookId}/events` с кадрами конца разбора и конца прогона и `204` на завершённом; ваш опрос интейка снимается. **Ф-57** (п.2): `GET /capabilities` (пары со статусом · порог интейка · форматы · размеры страниц · версия контракта) — `languages.ts` становится чтением. **Ф-61** (п.3): владелец выбрал вариант B (§8 п.4) — машинный `code` + `request_id`, `title`/`detail` developer-facing, фразу рисует КЛИЕНТ по словарю кодов (ваша таблица `AddBook.tsx:282-288` становится нормой; строка `AddBook.tsx:289` — `problem?.detail ?? problem?.title` — и её сёстры `Loaded.tsx:66`, `RunStart.tsx:188` СНИМАЮТСЯ; карта «код → фраза» — приложение А компаньона, слова делегированы проекту D39.176 п.4). **Ф-62** (п.4): `PATCH /books/{bookId}` ТОЛЬКО с `title` (409 при живом прогоне) плюс `DELETE`; жанр из продукта УХОДИТ совсем (Б-23, слово владельца 16.08) — поле формы снять. **Ф-43** (п.5): движковая половина банка построена, но названный тут сайдкар `bank-stop.json` СНЕСЁН (D39.158) — строка протухла ОБЕИМИ половинами, читать пинг №20 п.2 и пинг №22. **К-6** (п.6, §8 п.11): замечания показывать ВСЕ, в тексте сворачивать и раскрывать кнопкой, словарь ступеней заранее НЕ проектировать — зарегистрировать зонной строкой при разморозке. ⚠ Читать ОРИГИНАЛ отчёта, не пересказ. 7. **Мок-находки §9 (чинить при разморозке):** отказ интейка триггерится маркером в ИМЕНИ файла (`mock/intake.ts:238`) — переключатель без аналога на сервере; стоп игнорирует `runId` (`mock/handlers.ts:190`); мир `scale`: чтения на ревизии 4102, кадры с 1841 — расходятся по построению (`mock/worlds.ts:136`); в `loading` любой POST уходит МИМО мока в реальную сеть (`handlers.ts:59`); переполнение обхода страниц = статус 0 = неотличимо от «сервер недоступен» (`client.ts:114` → `Loaded.tsx:65`); мок интейка считает ВСЕ части формы, платформа — только до файла (`intake.ts:212` против `v0.go:337-360`). 8. **Перф-входы S5 (§5б/Б-11а):** снять `refetchOnWindowFocus` для тяжёлых коллекций (замер: самый частый трафик порождает переключение вкладок, не пайплайн — 12 вкладок = 562 КБ на фокус) и поднять `staleTime` после появления ETag/304 у платформы; ErrorBoundary в зоне нет ни одного (Б-0: TypeError рендера при смене словаря волн) — завести при разморозке; 401 сегодня тупик без ссылки на вход (§4 №27) — зонная строка. Вопросы — через владельца. -**Аддендум к пингу №17 (16.08, свип планировочных доков D39.139):** (1) Три носителя зоны учат ПОФАЗНОМУ прогрессу «draft N/M ∥ edit N/M» — `FRONTEND_SESSION_PROMPT.md` §9 п.1, `FRONTEND_PLAN.md` §6 п.1, `STACK_DECISIONS.md` §8 п.1; после батча 0.3.0 это устареет (Б-0/D39.138: на проводе ОДИН счётчик до ближайшей остановки, знаменатель — купленный объём) — при разморозке обновить все три, не только код. (2) `STACK_DECISIONS.md` §8 протух целиком: все четыре пункта «достроить в движке» ПОСТРОЕНЫ (пофазные счётчики и манифест — D39.122; JSON таблицы подписи — `bank-stop.json` D39.122; TM_TRACE_ID — P6/D39.132). (3) Диспозиции строк: **Ф-59** — В-10 закрыт владельцем 16.08 «вежливость» (D39.138 п.2к: всё правильно, цена названа — строку закрыть); **Ф-37** — К-10 отвечен «НЕ строить» (поправка приёмки ревью: фазы уходят с провода и у главы — пометка черновика остаётся книжной, хвост пере-диспозиционировать); **Ф-39** — закрыта ещё словом владельца 15.08 (D39.136 п.4б: полей BankTerm не заводить, sense хватает; его калибровка — строка 178 единого). Вопросы — через владельца. +**Аддендум к пингу №17 (16.08, свип планировочных доков D39.139):** (1) Три носителя зоны учат ПОФАЗНОМУ прогрессу «draft N/M ∥ edit N/M» — `FRONTEND_SESSION_PROMPT.md` §9 п.1, `FRONTEND_PLAN.md` §6 п.1, `STACK_DECISIONS.md` §8 п.1; батч 0.3.0 ЗАЛЕНДЕН, и это устарело (Б-0/D39.138: на проводе ОДИН счётчик до ближайшей остановки, знаменатель — купленный объём) — при разморозке обновить все три, не только код. (2) `STACK_DECISIONS.md` §8 протух целиком: все четыре пункта «достроить в движке» ПОСТРОЕНЫ (пофазные счётчики и манифест — D39.122; JSON таблицы подписи — был `bank-stop.json` (D39.122), СНЕСЁН вместе с писателем D39.158, см. пинг №22; TM_TRACE_ID — P6/D39.132). (3) Диспозиции строк: **Ф-59** — В-10 закрыт владельцем 16.08 «вежливость» (D39.138 п.2к: всё правильно, цена названа — строку закрыть); **Ф-37** — К-10 отвечен «НЕ строить» (поправка приёмки ревью: фазы уходят с провода и у главы — пометка черновика остаётся книжной, хвост пере-диспозиционировать); **Ф-39** — закрыта ещё словом владельца 15.08 (D39.136 п.4б: полей BankTerm не заводить, sense хватает; его калибровка — строка 178 единого). Вопросы — через владельца. **Дописка к аддендуму (16.08):** гейт на утечку конвейера в провод — «на проводе нет имён стадий/волн и движковых словарей», машинный тест по образцу `generality.test.ts` — назначен вашей зоне (D39.138 п.2а; спек-сессия батча запишет правило в компаньон контракта). Строить при первом касании зоны вместе с синком зеркала и перегенерацией типов. **Пинг оркестратора №17 — 16.08 (модель подписи банка, слово владельца; читать ПЕРЕД S5):** подписывается ВЕСЬ банк ОДНИМ действием юзера — черновой перевод + черновой банк появляются вместе после черновой волны, юзер смотрит банк, правит ТОЛЬКО то, что захотел, и жмёт один ОК (= resume) — перевод уходит в доработку; нерешённые термины едут как предложил сервис. Пер-термная масс-подпись сотен строк НЕ строится — это была инерция (владелец уже говорил зоне 10.08, снимая счётчики «подписано N»; теперь снята и из контракта: 409-гейт полноты убран, `pending_decisions`/`complete` информационные — D39.144). Строки Ф-44/Ф-45/Ф-46 при разморозке читать через эту модель: «действие подписи» = один ОК + опциональные точечные правки, НЕ проход по строкам. Пост-ридинговые правки банка («поправить/добавить термин → пере-генерация по вхождениям») — направление, механика не спроектирована нигде (полигон работает над точечным редактором) — экранов под неё не строить до отдельного слова. Вопросы — через владельца. -**Дописка к пингам №17 (16.08, по аудиту готовности зоны — ЧЕТЫРЕ нормы зоны сменили знак, читать ДО всего остального):** (1) **Канон контракта = 0.3.0, ЗАЛЕНДЕН (D39.142/143); ваше зеркало `docs/api-contract/` осталось на 0.2.3 РАТИФИЦИРОВАННО** (D39.142 п.5, на время фриза) — «зеркало = байт-копия» временно НЕ действует, нормативен только канон в `docs/architecture/14-api-contract/`; синк + перегенерация типов = первое касание зоны. (2) **Запрет клиентской метки «Глава {n}» СНЯТ каноном 0.3.0** — клиент теперь ОБЯЗАН сам рисовать порядковый в локали интерфейса, когда `heading`/`number` = null (Ф-30 и пинг №16 «на экране ничего не менять» — устарели; движковая строка 160 остаётся про НАСТОЯЩИЕ названия). (3) **Ф-31/В-6 закрыты ещё D39.132 п.2а** (`null` на проводе ратифицирован), а кадр `EventCeiling`, на который они опирались, снят 0.3.0 целиком — строки закрыть при разморозке. (4) **В-11 (фразы) пере-собрать на словарь 0.3.0**: карта «код → фраза» — приложение А компаньона; `parser_unavailable` переименован в `processing_failed`, добавлен `content_refused`. Плюс: **дев-стенд платформы СУЩЕСТВУЕТ с P6** (`tmplatformctl seed` + дев-вход, D39.132) — Ф-29 (снос моков) и Ф-18 (вес бандла) читать через него; статус зоны: ФРИЗ (D39.136 п.2), снятие = лендинг P7. Вопросы — через владельца. +**Дописка к пингам №17 (16.08, по аудиту готовности зоны — ЧЕТЫРЕ нормы зоны сменили знак, читать ДО всего остального):** (1) **Ваше зеркало `docs/api-contract/` остаётся на 0.2.3 РАТИФИЦИРОВАННО** (D39.142 п.5, на время фриза) — «зеркало = байт-копия» временно НЕ действует, нормативен только канон `docs/architecture/14-api-contract/` (сегодня 0.9.0 — пинг №20 п.4); синк + перегенерация типов = первое касание зоны. (2) **Запрет клиентской метки «Глава {n}» СНЯТ каноном 0.3.0** — клиент теперь ОБЯЗАН сам рисовать порядковый в локали интерфейса, когда `heading`/`number` = null (строка Ф-30 и прежний пинг №16 «на экране ничего не менять» устарели; сам пинг №16 снят при чистке журнала 02.09, движковая строка 160 остаётся про НАСТОЯЩИЕ названия). (3) **Ф-31/В-6 закрыты ещё D39.132 п.2а** (`null` на проводе ратифицирован), а кадр `EventCeiling`, на который они опирались, снят 0.3.0 целиком — ИСПОЛНЕНО 02.09: строка Ф-31 зачёркнута, вопрос В-6 снят с листа. (4) **В-11 (фразы) пере-собрать на словарь 0.3.0**: карта «код → фраза» — приложение А компаньона; `parser_unavailable` переименован в `processing_failed`, добавлен `content_refused`. Плюс: **дев-стенд платформы СУЩЕСТВУЕТ с P6** (`tmplatformctl seed` + дев-вход, D39.132) — Ф-29 (снос моков) и Ф-18 (вес бандла) читать через него; статус зоны: ФРИЗ (D39.136 п.2; ⚠ условие «снятие = лендинг P7» отменено пингом №18 от 17.08 — разморозка отдельным словом владельца). Вопросы — через владельца. **Пинг оркестратора №18 — 17.08.2026: ФРИЗ ПРОДЛЁН, разморозка отвязана от P7 (слово владельца, D39.147).** Прежнее условие «разморозка = лендинг P7 + гейт S5» БОЛЬШЕ НЕ ДЕЙСТВУЕТ: владелец 17.08 сказал морозить зону дальше — сначала движок и платформа доводятся до состояния «работают и отдают результат», и только потом фронт накручивается на готовую функциональность. Практически: **лендинг P7 зону не размораживает, промт S5 не выдаётся**, разморозка — отдельным словом владельца. Всё, что было записано как первое касание зоны (синк зеркала контракта 0.3.0 · перегенерация типов · моки · гейт утечки конвейера · фикс-лист ФС-1..12 · Ф-63/Ф-28 · пере-сборка фраз В-11 по словарю кодов), НЕ отменено — оно ждёт разморозки, а не P7. Направление, которое стоит знать заранее: моки при разморозке уступают реальному источнику — дев-стенд платформы существует с P6 (`tmplatformctl seed` + дев-вход), а форму «держать моки на уровне бэкендов или отдельно» владелец назвал направлением, но механику проектировать пока не просил. Живой smoke против дев-стенда остаётся легитимным и во фризе. Вопросы — через владельца. @@ -522,33 +196,25 @@ NDJSON — это шов ДВИЖОК↔ПЛАТФОРМА (D39.85), а фрон ### Правка оркестратора №22 — 01.09.2026 (ревизия доков, батч Б0 «канон 0.9.0») -⚠ **Я правил ДВА файла вашей зоны** — по прямой санкции владельца 01.09 на документацию всех зон. -Кода зоны не трогал; зеркало `frontend/docs/api-contract/openapi.yaml` НЕ трогал (его отставание -ратифицировано D39.142 п.5 на время фриза), `src/api/schema.ts` и `src/mock/handlers.ts` НЕ трогал. - -- `frontend/README.md` — буллет о байт-зеркале учил неверному: канон ушёл на **0.9.0**, зеркало стоит - на 0.2.3, байт-равенства сегодня НЕТ, и правило «зеркало = байт-копия» на время фриза - приостановлено ратифицированно. Дописана ⚠-оговорка; сам буллет не снесён. -- `frontend/docs/frontend-PROGRESS.md` (эта строка 9) — «канон 0.8.0 … шесть миноров» → **0.9.0, СЕМЬ**. - Цена ошибки была не косметическая: перечень первого касания зоны планировался на неверное число - перегенераций. - -**Проверено моей командой:** канон `docs/architecture/14-api-contract/openapi.yaml` = `0.9.0`, -зеркало = `0.2.3`, операций 20 против 16, миноров между ними семь (0.3.0…0.9.0). -Находки ревизии: `N013`, `N022`, `N026`, `N034`, `N053`, `N056`, `N108` — реестр -`docs/DOC_REVISION_FINDINGS.md`. +⚠ **Оркестратор правил ДВА файла вашей зоны** — по прямой санкции владельца 01.09 на документацию +всех зон: `frontend/README.md` (дописана ⚠-оговорка к буллету о байт-зеркале) и шапку этого журнала +(«канон 0.8.0 … шесть миноров» → **0.9.0, СЕМЬ**; перечень первого касания планировался на неверное +число перегенераций). Кода зоны, зеркала `api-contract/openapi.yaml`, `src/api/schema.ts` и +`src/mock/handlers.ts` не трогал. Числа проверены его исполнением: канон +`docs/architecture/14-api-contract/openapi.yaml` = `0.9.0`, зеркало = `0.2.3`, операций 20 против 16, +миноров между ними семь (0.3.0…0.9.0). Находки ревизии — реестр `docs/DOC_REVISION_FINDINGS.md`. ### Пинг оркестратора №22 — 02.09.2026 (ревизия доков, находка N018) -**`.bank-stop.json` БОЛЬШЕ НЕ СУЩЕСТВУЕТ**, а ваш живой док его описывает как существующий. +**`.bank-stop.json` БОЛЬШЕ НЕ СУЩЕСТВУЕТ**, а ваш живой док описывал его как существующий. Файл снесён вместе с писателем и четырьмя тестами при постройке входной двери шва (**D39.158**). Проверено моей командой: `grep -rc 'bank-stop.json' backend/ --include='*.go'` → **ни одного вхождения**; холодный прогон 31.08 подтвердил это на диске. Свой носитель (`docs/PROGRESS.md`, строка 157) я поправил. -**Ваш носитель:** `frontend/docs/frontend-PROGRESS.md` около строки 499 (Ф-43 и аддендум к пингу №17) -называет `.bank-stop.json` как живой сайдкар. Зона заморожена — правьте при первом касании, -но в перечень касания это внести стоит: планировать чтение несуществующего файла дорого. +**Ваш носитель ПОЧИЩЕН 02.09** чисткой этого журнала: ни пинг №17 п.5, ни аддендум к нему больше не +называют `.bank-stop.json` живым сайдкаром. Остаток первого касания — строка Ф-43 бэклога: она +протухла обеими половинами (пинг №20 п.2). ⚠ **Что править НЕ надо** (проверено, это законная история в прошедшем времени, а не дрейф): `docs/architecture/18-bank-ontology.md`, `backend/docs/SEAM_FIX3_PLAN.md`,