Second cut pass in the frontend zone: restore what the review found lost, and take the sediment the first pass left behind

This commit is contained in:
heaven 2026-09-02 11:49:09 +03:00
parent 21de1779ba
commit 2449a56a41
7 changed files with 179 additions and 219 deletions

View file

@ -57,13 +57,14 @@ npm run scenes # сценарии интеракций: клик → ка
ссылки. Прогресс фронта пишут сюда все, включая оркестратора (слово владельца 04.08). ссылки. Прогресс фронта пишут сюда все, включая оркестратора (слово владельца 04.08).
- [`docs/BACKLOG.md`](docs/BACKLOG.md) — зонный бэклог фронта. - [`docs/BACKLOG.md`](docs/BACKLOG.md) — зонный бэклог фронта.
- [`docs/API_CONTRACT_INPUT.md`](docs/API_CONTRACT_INPUT.md) — ⚠ **исполнено, не поддерживается:** - [`docs/API_CONTRACT_INPUT.md`](docs/API_CONTRACT_INPUT.md) — ⚠ **исполнено, не поддерживается:**
вход в контракт, написанный до него. Всё содержимое ушло в спеку и спутник ниже; остаётся как вход в контракт, написанный до него; остаётся аудиторским следом. Условия переноса в архив —
аудиторский след (как каждая просьба выводилась из кода движка), место ему в `docs/archive/`. в его собственном баннере.
- [`docs/api-contract/openapi.yaml`](docs/api-contract/openapi.yaml) — **сам контракт v0, - [`docs/api-contract/openapi.yaml`](docs/api-contract/openapi.yaml) — зонная копия контракта
OpenAPI 3.1: нормативная поверхность.** Линтуется spectral шагом `npm run check`, из неё v0 (OpenAPI 3.1), из неё генерится `src/api/schema.ts` и её линтует spectral шагом
генерится `src/api/schema.ts`. ⚠ **Не черновик** (исправлено S4 14.08): контракт ратифицирован `npm run check`. ⚠ **Не черновик и НЕ норматив:** контракт ратифицирован (D39.99), нормативен
D39.99, эта копия — байт-зеркало канона `docs/architecture/14-api-contract/`**С ФРИЗА ЗОНЫ ОТСТАЁТ РАТИФИЦИРОВАННО (D39.142 п.5): нормативен ТОЛЬКО канон (0.9.0), зеркало стоит на 0.2.3 — байт-равенства сегодня НЕТ. Синк `cmp` + перегенерация типов — первое касание зоны при разморозке.**; правит её ТОЛЬКО канон `docs/architecture/14-api-contract/` (0.9.0), а копия стоит на 0.2.3 —
фронт-сессия, переносит в канон диффом оркестратор при лендинге (прецедент Ф-47/0.2.2). байт-равенства сегодня НЕТ (D39.142 п.5). Синк `cmp` + перегенерация типов — первое касание
зоны при разморозке; правит копию фронт-сессия, переносит в канон диффом оркестратор.
- [`docs/API_CONTRACT_DRAFT.md`](docs/API_CONTRACT_DRAFT.md) — ⚠ **исполнено:** зонный указатель - [`docs/API_CONTRACT_DRAFT.md`](docs/API_CONTRACT_DRAFT.md) — ⚠ **исполнено:** зонный указатель
на канон спутника `docs/architecture/14-api-contract/README.md`. Провенанс каждого решения, на канон спутника `docs/architecture/14-api-contract/README.md`. Провенанс каждого решения,
обоснования, зависимости и К-вопросы живут ТАМ; при расхождении по форме побеждает спека. обоснования, зависимости и К-вопросы живут ТАМ; при расхождении по форме побеждает спека.
@ -74,14 +75,12 @@ npm run scenes # сценарии интеракций: клик → ка
Исходники зоны **английские**: комментарии, имена тестов, сообщения ошибок и логи скриптов. Исходники зоны **английские**: комментарии, имена тестов, сообщения ошибок и логи скриптов.
Русский в `src/` живёт ровно в двух местах, и оба — данные: каталог сообщений Русский в `src/` живёт ровно в двух местах, и оба — данные: каталог сообщений
`src/i18n/ru.ts` (слова интерфейса, переводится файлом) и фикстуры `src/mock/` (проза книги, `src/i18n/ru.ts` (слова интерфейса, переводится файлом) и фикстуры `src/mock/` (проза книги,
которая стоит вместо настоящей). Держат это два гейта с РАЗНЫМИ правилами: ESLint ловит текст в разметке по МЕСТУ (а не по алфавиту), `src/i18n/catalogue.test.ts` — то, чего AST не видит: комментарии, CSS, python-скрипты и корневые конфиги зоны. Почему именно так — `docs/STACK_DECISIONS.md` §1. которая стоит вместо настоящей). Держат это два гейта с РАЗНЫМИ правилами, и почему именно так — `docs/STACK_DECISIONS.md` §1.
## Источник внешнего вида — один ## Источник внешнего вида — один
**Замеры с `references/fleet.png`, зафиксированные в `tokens.css`.** Промежуточных дизайн-макетов **Замеры референсов, зафиксированные в `tokens.css`.** Промежуточных дизайн-макетов нет:
нет: инструменты проектирования макетов не используются (решение владельца 02.08). Палитра и инструменты проектирования макетов не используются (решение владельца 02.08). Палитра и геометрия
геометрия сняты с референса пипеткой и лежат в `STACK_DECISIONS.md` и §1.1 промта — их берут сняты замером, а не подобраны; действующие числа, способ их повторить и то, что отвергнуто, —
как данность, а не выводят заново. `docs/FRONTEND_PLAN.md` §5.1. Их берут как данность, а не выводят заново.
⚠ JetBrains Fleet закрыт (скачивание прекращено 22.12.2025) — добрать новые измерения неоткуда.
Всё, что не замерено (кегли, интерлиньяж, внутренние отступы), подбирается на глаз по скриншоту.

View file

@ -1,15 +1,13 @@
# Вход фронта в контракт API v0 — материал для строки 95 # Вход фронта в контракт API v0 — материал для строки 95
> ⚠ **ИСПОЛНЕНО И БОЛЬШЕ НЕ ПОДДЕРЖИВАЕТСЯ (04.08).** Этот файл — ВХОД, написанный до > ⚠ **ИСПОЛНЕНО И БОЛЬШЕ НЕ ПОДДЕРЖИВАЕТСЯ (04.08).** Этот файл — ВХОД, написанный до
> контракта: просьба к его автору. Контракт с тех пор написан и ратифицирован, и все 18 пунктов > контракта: просьба к его автору. Контракт написан и ратифицирован, и все 18 пунктов ниже стали
> ниже стали либо полем канонической спеки `docs/architecture/14-api-contract/openapi.yaml`, либо > либо полем канонической спеки `docs/architecture/14-api-contract/openapi.yaml`, либо разделом §2
> разделом §2 канонического спутника `docs/architecture/14-api-contract/README.md`, либо его > канонического спутника `docs/architecture/14-api-contract/README.md`, либо его К-вопросом §4.
> К-вопросом §4. **Читать для решения — канон; этот файл с ним не сверяется и уже расходится:** > **Читать для решения — канон; этот файл с ним не сверяется и уже расходится:** часть просьб
> его `file:line` в чужую зону протухли (проверено 02.09 — цитаты грепаются, номера строк нет), > канон пере-решил, а грунт в движок снят 04.08 и протух ВЫБОРОЧНО — проверено 02.09: часть
> а часть просьб канон пере-решил. Ценность, ради которой он не удалён и не сокращён: > номеров строк ещё резолвится, а часть цитат из движка не грепается вовсе. Не удалён и не
> аудиторский след — как каждая просьба выводилась из кода движка, и что ломалось без неё; > сокращён ради аудиторского следа; место такому — `docs/archive/`, перенос делает оркестратор.
> цитаты из движка в нём дословные и грепаются. Место такому — `docs/archive/`, перенос делает
> оркестратор (зона).
> >
> **Что это было.** Ф-14 бэклога зоны, доведённый до артефакта: перечень того, что интерфейсу > **Что это было.** Ф-14 бэклога зоны, доведённый до артефакта: перечень того, что интерфейсу
> нужно от read-модели платформы, с обоснованием на КАЖДЫЙ пункт. Адресат — автор контракта > нужно от read-модели платформы, с обоснованием на КАЖДЫЙ пункт. Адресат — автор контракта

View file

@ -7,57 +7,57 @@
| Ф-1 | **Этапы S2S7** (`FRONTEND_SESSION_PROMPT.md`): S2 оболочка + слой `src/ui/` · S3 слой данных/MSW/фикстуры всех состояний · S4 библиотека/загрузка/разбор/прогресс · S5 банк памяти и подпись (самый тяжёлый) · S6 читалка двух колонок и замечания · S7 метаданные/экспорт/настройки/сквозной прогон | S0S4 пройдены и приняты (D39.135); S5 — после разморозки зоны (D39.147) | FRONTEND_SESSION_PROMPT | | Ф-1 | **Этапы S2S7** (`FRONTEND_SESSION_PROMPT.md`): S2 оболочка + слой `src/ui/` · S3 слой данных/MSW/фикстуры всех состояний · S4 библиотека/загрузка/разбор/прогресс · S5 банк памяти и подпись (самый тяжёлый) · S6 читалка двух колонок и замечания · S7 метаданные/экспорт/настройки/сквозной прогон | S0S4 пройдены и приняты (D39.135); S5 — после разморозки зоны (D39.147) | FRONTEND_SESSION_PROMPT |
| Ф-2 | **React Compiler** — включить ОТДЕЛЬНЫМ шагом после заморозки интерфейса, с CI-проверкой, что вставки реально попали в бандл (в MVP выключен; совместимость держит линт eslint-plugin-react-hooks) | после заморозки UI | STACK_DECISIONS §1 | | Ф-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 | | Ф-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`) закрыты тоже. Механизм — `stylelint.config.js`; перечень проверенных нарушений и легальных форм — `FRONTEND_PLAN.md` §5.4 | закрыт | STACK_DECISIONS §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 | | ~~Ф-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'; <svg fill={c}/>`) — синтаксически это не берётся, и объявлять строку «закрытой без остатка» было бы тем самым переобещанием, из-за которого она заведена | закрыт для литеральных форм, граница названа | адверсариальное ревью 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. **Граница названа честно:** гейт ловит литерал и шаблон, но НЕ вычисленное значение (`const c = 'red'; <svg fill={c}/>`) — синтаксически это не берётся, и объявлять строку «закрытой без остатка» было бы тем самым переобещанием, из-за которого она заведена | закрыт для литеральных форм, граница названа | адверсариальное ревью 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 | | ~~Ф-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 | | ~~Ф-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 | | Ф-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 строки. Число не из вкуса: главу адресуют порядковым номером, и сотня — шаг, которым их считает человек; замер говорит то же с другой стороны — плоское дерево рисуется дёшево, а полоса прокрутки на 2284 строках даёт треть пикселя на строку. Ниже порога дерево остаётся ПЛОСКИМ; группа с выбранным разделом раскрывается сама и остаётся закрываемой рукой (`ui/Tree.tsx`). ⚠ Хвост «разделение банка подписано/ждёт решения» СНЯТ: счётчиков подписи нет с 10.08 (Ф-44 · D39.144), а различитель канона и предложенного — Ф-46 | ревью 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 | | Ф-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 | | ~~Ф-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 | | Ф-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 | | Ф-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 | | Ф-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, и механизмом:** проп `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 | | ~~Ф-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`. **Хвост, живой и сегодня:** скрытые панели остаются в DOM, поэтому глобальный локатор по роли находит строку соседней вкладки — сценарии обязаны искать внутри своей области | закрыт | адверсариальное ревью S2 → S3.5 |
| Ф-20 | **Мёртвый шрифт и мёртвые токены в поставке.** `@fontsource-variable/jetbrains-mono` грузится в бандл, но `--font-mono` не выбран ни одним правилом: художественную прозу моноширинным не набирают, а во Fleet на этом месте был код. Туда же несколько токенов, которые ни разу не используются (`--color-current-line`, `--color-hint`). Замок полноты токенов (S2) требует классифицировать токен, но не требует его использовать. Решать на S6, когда читалка станет настоящей: либо моноширинный находит работу, либо импорт и токены уходят | S6 | адверсариальное ревью S2 | | Ф-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). Тем же ходом закрыт второй дефект того же класса: незнакомая ступень замечания берёт ветку ВНИМАНИЯ, а не спокойную — единственное, чего ось важности делать не вправе, это прятать важное | закрыт | адверсариальное ревью S2 → S3 08.08 | | ~~Ф-22~~ | ~~**У закрытых юнионов нет ветки неизвестного значения**~~**ЗАКРЫТА 08.08 в S3 механизмом, а не дисциплиной.** Ветка стоит НА ШВЕ (`src/api/vocabulary.ts` + нормализация в `contract.ts`): значение с провода сужается один раз, там, где входит, и незнакомое становится `null`, а не роняет дерево на `undefined.tone`. Словарь объявлен как `Record<Значение, Смысл>`, поэтому список значений ВЫВОДИТСЯ из записей, а новое значение контракта роняет `tsc` с именем пропавшего (проверено живым нарушением, `FRONTEND_PLAN.md` §5.4). Тем же ходом: незнакомая ступень замечания берёт ветку ВНИМАНИЯ — прятать важное ось важности не вправе | закрыт | адверсариальное ревью S2 → S3 08.08 |
| Ф-21 | **Ступень замечания различается только цветом полоски** (`attention` синяя, `glance` серая). Зрячему разница читается ещё и светлотой, но скринридеру приоритет не достаётся вовсе. Лечение — слово, как это уже сделано для состояния прогона книги; **какое именно слово, решает владелец**: словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, так что «важно/неважно» не годится | решение владельца | адверсариальное ревью S2 | | Ф-21 | **Ступень замечания различается только цветом полоски** (`attention` синяя, `glance` серая). Зрячему разница читается ещё и светлотой, но скринридеру приоритет не достаётся вовсе. Лечение — слово, как это уже сделано для состояния прогона книги. ⚠ **Диспозиция сменилась 30.08: подписи владельца НЕ ждём** — фразы делегированы проекту (D39.176 п.4), и требование к ним инженерное: слово не литерал в TSX, а ДАННЫЕ по машинному коду причины и локали, словарь один на продукт. Границы выбора: словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, так что «важно/неважно» не годится | зона, по словарю фраз (D39.176 п.4) | адверсариальное ревью S2 → пинг №20 п.1 |
| Ф-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 | | Ф-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 | | Ф-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 | | Ф-24 | **AsyncAPI рассмотрен и отложен.** Он описал бы SSE-канал строже, чем нынешняя таблица «имя события → схема» в описании `EventEnvelope`. Отложен, потому что это второй нормативный артефакт и второй линтер ради ОДНОГО эндпоинта, при том что полезные нагрузки уже описаны схемами OpenAPI и попадают в генерённые типы. Пересмотреть, когда каналов станет больше одного либо когда понадобится генерировать клиент потока, а не разбирать кадры руками | триггер: второй канал ИЛИ генерация клиента потока | S3, дофикс контракта 04.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 | | ~~Ф-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 | | Ф-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 | | Ф-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` | | Ф-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 | | Ф-29 | **Мок-сеть едет в продакшн-бандл.** `startMocking()` подтягивает `src/mock/` динамическим импортом, но скриншот-цикл гоняет `vite build` + `preview`, то есть продакшн-режим, и без мока там нечего показывать. Пока платформы на том же origin нет, это цена работоспособности; в день, когда фронт заговорит с настоящей платформой, папка `mock/` удаляется целиком и импорт уходит вместе с ней — ровно так, как обещал шов. Строка нужна, чтобы это удаление кто-то сделал ОСОЗНАННО, а не обнаружил мок в бою | триггер: живая платформа на том же origin | S3 08.08 |
| Ф-30 | **Метка главы без заголовка — голый порядковый номер.****Прежнее основание строки СНЯТО каноном 0.3.0:** запрет синтезировать «Глава {n}» на клиенте отменён — когда у главы нет ни `heading`, ни `number`, клиент ОБЯЗАН сам нарисовать порядковый в локали интерфейса (строка учила обратному по К-3). Сегодня в дереве на таком месте стоит голая позиция в порядке чтения — строка «12» среди названий; правка экрана — при разморозке. НАСТОЯЩИЕ названия глав — движковая половина (строка 160 единого бэклога), а продуктовое слово о том, чем подписывать 2284 одинаковых «Глава N», — по-прежнему В-4 на владельце | разморозка + владелец (В-4) | S3 08.08, кадр `/scale` → канон 0.3.0 | | Ф-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 | | ~~Ф-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 | | Ф-32 | **Замечания читаются пер-КНИЖНО, а показываются пер-главно.** Контракт даёт `GET /books/{id}/notes` и не даёт пер-главной ручки; панель контекста фильтровала по открытой главе на клиенте — **с S3.5 фильтр снят, сводка пер-книжная (замечание владельца 8, строка Ф-38), то есть пер-книжное чтение теперь и показывается пер-книжно**. Пока это дёшево — дефолт страницы 500, а на книге с сотнями замечаний клиент тянет весь список ради трёх строк, и с курсором дотянет ВЕСЬ. Ф-12 обосновывал отказ от виртуализации замечаний тем, что список пер-главный и мал; после этой правки он пер-главный на ЭКРАНЕ, но пер-книжный в ПАМЯТИ. Решать не фронту: либо пер-главная ручка (вопрос автору контракта), либо явный потолок с честным «показаны не все». | S6 (читалка) либо раньше, если появится книга с тысячами замечаний | второй проход ревью S3 |
| Ф-5 | **Tauri 2.x вторым шагом** — по явным триггерам (трей · глобальные горячие клавиши · распространяемый .exe · офлайн · хранилище учёток ОС); до того — установимое PWA, только браузерные API | триггеры названы | STACK_DECISIONS §6, D39.84 | | Ф-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 | | Ф-6 | **`@tanstack/react-virtual`** — в резерве, подключать только по замеру (дефолт виртуализации — RAC Virtualizer). **Замер сделан 04.08 (S2) и триггер НЕ наступил:** RAC `Virtualizer` держит и дерево, и банк на настоящем масштабе (числа — Ф-12), то есть вторая библиотека виртуализации была бы платой без выигрыша; пересматривать — если появится список с переменной высотой строки (читалка S6) и `estimatedRowSize` не справится | по замеру; замер 04.08 отрицательный | STACK_DECISIONS §2 |
| Ф-33 | **Поиска по ТЕКСТУ книги в контракте нет вовсе.** Замечание владельца 4 («две кнопки поиска, делают одно и то же») разведено так: лупа в правом верхнем углу стала настоящей палитрой быстрого перехода к разделу или термину, а вкладка-заглушка «Поиск» из левой панели СНЯТА. Снята потому, что построить её нечем: у контракта пятнадцать ручек и ни одной поисковой (`GET /books/{id}/…` даёт списки, не запросы), то есть вкладка была обещанием без адресата. Вопрос не фронта: полнотекстовый поиск по книге — это либо ручка платформы, либо индекс на клиенте поверх выгрузки, и то и другое решает автор контракта. До ответа поиск по книге живёт как хвост читалки | S6 либо ручка контракта | S3.5, замечание 4 | | Ф-33 | **Поиска по ТЕКСТУ книги в контракте нет вовсе.** Замечание владельца 4 («две кнопки поиска, делают одно и то же») разведено так: лупа в правом верхнем углу стала настоящей палитрой быстрого перехода к разделу или термину, а вкладка-заглушка «Поиск» из левой панели СНЯТА. Снята потому, что построить её нечем: у контракта пятнадцать ручек и ни одной поисковой (`GET /books/{id}/…` даёт списки, не запросы), то есть вкладка была обещанием без адресата. Вопрос не фронта: полнотекстовый поиск по книге — это либо ручка платформы, либо индекс на клиенте поверх выгрузки, и то и другое решает автор контракта. До ответа поиск по книге живёт как хвост читалки | S6 либо ручка контракта | S3.5, замечание 4 |
| Ф-34 | **Светлая тема: цена честно замерена, дёшево не выходит.** Проверено по коду, а не на глаз. Дёшево — только половина: значения цвета лежат в одном месте (`tokens.css`), второй набор под `:root[data-theme='light']` механичен. Дорого — вторая половина: подпись оболочки, снятая с Fleet, это «карточки на почти чёрном фоне», и она не инвертируется значениями — в светлой теме отношения поверхностей приходится ПЕРЕПРИДУМАТЬ (что светлее, панель или геп; чем отделять карточку без теней; что делать с вырезом активной вкладки, который держится на паре «хром ↔ полотно»). Плюс переключатель и его персист, плюс пере-прогон axe по семи маршрутам на второй теме, плюс замок токенов на второй набор. Оценка: отдельный проход масштаба S7, а не хвост фикс-пака. Пункт назван в каркасе настроек честно: «сейчас доступна только тёмная» | S7 либо отдельный пакет | S3.5, замечание 15 | | Ф-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 | | Ф-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` по живому `ru.ts`), главный чанк **724130 байт** (`npx vite build`); ходившие рядом «147» и «723102» ошибочны. После пака 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 | | Ф-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`), и глава, уже прошедшая обе волны в незавершённой книге, получает ту же пометку, что и непереведённая. ⚠ **Открытый вопрос К-10, на который строка ждала ответа, ОТВЕЧЕН «НЕ строить» (D39.138):** фазы уходят с провода, и у главы тоже — пометка черновика остаётся КНИЖНОЙ. Хвост строки пере-диспозиционировать при разморозке: безопасная сторона (предупредить лишний раз) осталась взятой намеренно | пере-диспозиция при разморозке | S3.5, замечание 24 → D39.138 | | Ф-37 | **Пометка черновика висит на статусе КНИГИ, а не главы.** Замечание 24 сделано данными контракта: пока прогон не довёл книгу до `ready`, текст помечен как черновой. Но у главы есть своё выполнение (`units_done/units_total`), и глава, уже прошедшая обе волны в незавершённой книге, получает ту же пометку, что и непереведённая. ⚠ **Открытый вопрос К-10, на который строка ждала ответа, ОТВЕЧЕН «НЕ строить» (D39.138):** фазы уходят с провода, и у главы тоже — пометка черновика остаётся КНИЖНОЙ. Хвост строки пере-диспозиционировать при разморозке: безопасная сторона (предупредить лишний раз) осталась взятой намеренно | пере-диспозиция при разморозке | S3.5, замечание 24 → D39.138 |
| Ф-38 | **Вкладка «Замечания» защищена сводкой по книге — слово за владельцем.** Замечание 8 («непонятно зачем, всё рисуется в тексте») по пер-ГЛАВНОМУ списку было верным: он дублировал читалку. Вкладка переделана в пер-КНИЖНУЮ сводку со ссылкой в раздел — сценарий «пройтись по всем местам, требующим внимания», который без неё не живёт и который не стоит ни одного лишнего запроса (контракт даёт замечания пер-книжно, Ф-32). Если владелец сценария не признает — вкладка снимается одной строкой в `Context.tsx`, вместе с ней уходит и `notesQuery` с экрана | решение владельца | S3.5, замечание 8 | | Ф-38 | **Вкладка «Замечания» защищена сводкой по книге — слово за владельцем.** Замечание 8 («непонятно зачем, всё рисуется в тексте») по пер-ГЛАВНОМУ списку было верным: он дублировал читалку. Вкладка переделана в пер-КНИЖНУЮ сводку со ссылкой в раздел — сценарий «пройтись по всем местам, требующим внимания», который без неё не живёт и который не стоит ни одного лишнего запроса (контракт даёт замечания пер-книжно, Ф-32). Если владелец сценария не признает — вкладка снимается одной строкой в `Context.tsx`, вместе с ней уходит и `notesQuery` с экрана | решение владельца | S3.5, замечание 8 |
| ~~Ф-39~~ | ~~**Банку не хватает полей для проверки термина, и выдумывать их фронт не стал**~~ (замечания 7/13 просили род/пол, описание-обоснование, варианты перевода; в `BankTerm` их нет) — **ЗАКРЫТА словом владельца 15.08 (D39.136 п.4б): полей `BankTerm` не заводить, `sense` хватает**; его калибровка — строка 178 единого бэклога. Показано было всё, что даёт контракт, а недостающее оформлено черновиком правки спеки и НЕ заведено ни в фикстуру, ни в типы — класс ошибки «фикстура выдумала форму» (Ф-14) | закрыт | S3.5, замечания 7 и 13 → D39.136 | | ~~Ф-39~~ | ~~**Банку не хватает полей для проверки термина, и выдумывать их фронт не стал**~~ (замечания 7/13 просили род/пол, описание-обоснование, варианты перевода; в `BankTerm` их нет) — **ЗАКРЫТА словом владельца 15.08 (D39.136 п.4б): полей `BankTerm` не заводить, `sense` хватает**; его калибровка — строка 178 единого бэклога. Показано было всё, что даёт контракт, а недостающее оформлено черновиком правки спеки (`archive/frontend-PROGRESS-S0-S4.md`, запись S3.5 09.08, §5) и НЕ заведено ни в фикстуру, ни в типы — класс ошибки «фикстура выдумала форму» (Ф-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 | | ~~Ф-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, ревью точности | | Ф-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 | | Ф-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 | ⚠ **Строка ПРОТУХЛА ОБЕИМИ половинами (сверено деревом 30.08, пинг №20 п.2).** Было: «у чтения банка нет канала, витрина стоит на моке, подписной экран S5 строить не на чем». Стало: движковый сайдкар банка ПОСТРОЕН (`backend/internal/pipeline/bankexport.go`), платформа его декодирует и отдаёт проекцией, у контракта есть `GET /books/{bookId}/bank`. Значит основание «S5 непостроим» и зависимости Ф-44/Ф-46 «после Ф-43» больше не держатся. Осталось: перевести витрину банка с фикстуры на живое чтение при разморозке | разморозка: снос мока банка | S3.6 10.08 → пинг №20 п.2 | | Ф-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 | | Ф-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). Счётчики «подписано 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 | | Ф-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 | | Ф-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`, правило пустой строки («различителя нет») записано в описании, версия 0.2.1 → 0.2.2, spectral зелёный, типы перегенерены | закрыт | 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 | | Ф-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` остаётся единым счётчиком. Тест — `showcase/useRunStream.test.tsx`, два случая, оба падают на до-фиксном коде | закрыт | S3.6 10.08 → S3.7 | | ~~Ф-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 | | ~~Ф-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 | | ~~Ф-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 | | ~~Ф-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 этого не ловит вовсе (контраст нетекста он не проверяет), и обе просадки нашлись руками, с разницей в пакет. Проверено живым нарушением. ⚠ **Порог НЕтекста — потолок этих тонов:** тона состояния не проходят порог для ТЕКСТА, поэтому цвет живёт на точке, а смысл несёт слово (замер 02.08: попытка красить сам текст дала axe 5 → 14 узлов, откат вернул 5) | закрыт | 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, замер в браузере | | Ф-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` | | Ф-54 | **При зуме от 125% центральная панель обрезает СВОЁ содержимое, а не сжимается.** Замер сцены `zoom` (вьюпорт в CSS-пикселях = зум браузера): при 1152×720 центр режет 55px своего содержимого, при 960×600 — 98px, при 720×450 — 170px; окно при этом не переливается, три панели стоят в ряд, боковые честно сжимаются (левая доходит до 141px при своём минимуме 220). То есть карточка центра держит ширину, которой у слота уже нет, и первой уходит вторая колонка читалки — перевод. Кадр `--size 720x450 --dpr 1` это показывает глазом. Не чинил и не считаю это своим решением: ширина центра — часть вопроса «плотность интерфейса» (Ф-36, В-7 на владельце), а менять раскладку сверх задания пака — самодеятельность. Сцена ЧИСЛО печатает, но не утверждает: гейт, красный по построению, учит пропускать команду | владелец (В-7) + Ф-36 | S3.7 10.08, замер сцены `zoom` |
| Ф-55 | **Стор языка есть, переключателя нет — и это не забывчивость.** `src/i18n/language.ts` держит выбор с персистом и отдаёт локаль каталогу, `I18nProvider` и `Intl`; `choose()` при этом не зовёт ни один экран, потому что содержимое настроек строит S7, а промт пака прямо запрещает его трогать. Пункт «Язык интерфейса» в каркасе настроек честно говорит «сейчас интерфейс только русский». Строка держит хвост, чтобы S7 не завёл ВТОРОЕ хранилище выбора рядом с этим | S7 | S3.7 10.08 | | Ф-55 | **Стор языка есть, переключателя нет — и это не забывчивость.** `src/i18n/language.ts` держит выбор с персистом и отдаёт локаль каталогу, `I18nProvider` и `Intl`; `choose()` при этом не зовёт ни один экран, потому что содержимое настроек строит S7, а промт пака прямо запрещает его трогать. Пункт «Язык интерфейса» в каркасе настроек честно говорит «сейчас интерфейс только русский». Строка держит хвост, чтобы S7 не завёл ВТОРОЕ хранилище выбора рядом с этим | S7 | S3.7 10.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 → канон | | Ф-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 → канон | | Ф-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 → канон |

View file

@ -48,9 +48,8 @@
| платформа → фронт | **JSON поверх HTTP** для чтений из Postgres read-модели + **SSE** для живого прогресса; события пушит воркер, фронт read-модель не опрашивает; WebSocket отвергнут | D39.84, `STACK_DECISIONS.md` §5 | | платформа → фронт | **JSON поверх HTTP** для чтений из Postgres read-модели + **SSE** для живого прогресса; события пушит воркер, фронт read-модель не опрашивает; WebSocket отвергнут | D39.84, `STACK_DECISIONS.md` §5 |
Фронту принадлежит только вторая строка. NDJSON в коде фронта появиться не может; если появился — Фронту принадлежит только вторая строка. NDJSON в коде фронта появиться не может; если появился —
кто-то полез не на тот уровень. Дисциплина стрима, которую обязан выдержать слой данных: кто-то полез не на тот уровень. Дисциплину стрима, которую обязан выдержать слой данных, задаёт
монотонный `id` + `Last-Event-ID` (докачка после обрыва), heartbeat ~20 с, `EventSource` в браузере `STACK_DECISIONS.md` §5 (строкой выше).
и `Authorization: Bearer` с построчным разбором для десктопа.
--- ---
@ -193,6 +192,8 @@ CJK-шрифт не ставим: иероглифы отдаём системн
| 13 | Инлайновый стиль — только литерал объекта прямо в атрибуте, и только с ключами-CSS-переменными | **машинно**: `no-restricted-syntax` запрещает сам атрибут `style` и разрешает единственную форму. Перечислять формы записи оказалось бесполезно: из одиннадцати способов записать то же самое ловилось три. JSX-спред закрыт с другой стороны — запрещено само свойство `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), а один вход делает замену библиотеки правкой одной папки | | 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()` намеренно нет | | 19 | Отступы, промежутки и радиусы — только из токенов | **машинно** (Ф-4, включён в S2): stylelint `declaration-property-value-allowed-list` на `/^(padding\|margin)(-\|$)/`, `/^(gap\|row-gap\|column-gap)$/`, `/^border-.*radius$/`. Значение — последовательность `var(--…)`; `0`, `auto`, `inherit` легальны, `calc()` намеренно нет |
| 21 | Плоско и скучно: никаких абстракций «на будущее», фабрик и слоёв ради слоёв | ревью-вопрос: что этот слой даёт СЕГОДНЯ? Дублирование двух строк лучше преждевременного обобщения — проектная норма «механизм там, где несёт ценность». Машинного гейта нет, и это честно |
| 22 | Имена человеческие, без аббревиатур | ревью глазами; проектная норма. Машинного гейта нет |
| 20 | Числа, которые нужны коду, а не стилям, не заводят вторую истину | **машинно**: `src/tokens/measures.ts` держит их одним объектом (высота строки для виртуализатора, ширины панелей для `react-resizable-panels`), а `tokens.test.ts` сверяет каждое значение с одноимённым токеном. Без этого высота строки существовала бы в двух местах и разъезжалась молча | | 20 | Числа, которые нужны коду, а не стилям, не заводят вторую истину | **машинно**: `src/tokens/measures.ts` держит их одним объектом (высота строки для виртуализатора, ширины панелей для `react-resizable-panels`), а `tokens.test.ts` сверяет каждое значение с одноимённым токеном. Без этого высота строки существовала бы в двух местах и разъезжалась молча |
**Контрольный вопрос владельца** (`§5.1`, применять к каждому пакету работ): **Контрольный вопрос владельца** (`§5.1`, применять к каждому пакету работ):
@ -269,6 +270,8 @@ SSE-соединение открытым ПО ПОСТРОЕНИЮ — сеть
видит `.tooling/root` — думать об этом больше не нужно. видит `.tooling/root` — думать об этом больше не нужно.
```bash ```bash
npm i -D playwright
npx playwright install chromium # БЕЗ --with-deps: он требует sudo и ставит лишнее
mkdir -p .tooling && cd .tooling mkdir -p .tooling && cd .tooling
apt-get download libnss3 libnspr4 libasound2t64 fonts-noto-cjk apt-get download libnss3 libnspr4 libasound2t64 fonts-noto-cjk
for d in *.deb; do dpkg -x "$d" root; done for d in *.deb; do dpkg -x "$d" root; done
@ -290,35 +293,54 @@ rm -f *.deb && cd ..
**Сходиться обязаны:** цвета · промежутки 8px · радиусы 6px · плотность строк · общее впечатление. **Сходиться обязаны:** цвета · промежутки 8px · радиусы 6px · плотность строк · общее впечатление.
**Подбираются на глаз:** кегли, интерлиньяж, внутренние отступы — померить их больше негде. **Подбираются на глаз:** кегли, интерлиньяж, внутренние отступы — померить их больше негде.
### 5.1. Замеры, перепроверенные в этой сессии ### 5.1. Замеры референса: действующие числа, способ повторить, что отвергнуто
Значения §1.1 промта перепроверены заново по `references/fleet.png` (PIL: гистограмма всего > Прежние **§5.55.7** (пере-замеры по сессиям) слиты СЮДА: ссылки на них из кода
изображения, срезы строк и столбцов, детект края скругления по порогу яркости). > (`tokens.css`, `reset.css`, `tokens.test.ts`, `Panel.tsx`, `Shell.module.css`) и из журналов
**Все числа совпали, одна роль разошлась:** §1.1 отдаёт `#353739` и «выбранной строке», и > зоны резолвятся в этот раздел.
«активной вкладке», а в кадре это разные вещи — вкладка открытого документа залита `#142f4c`,
вкладка панели `#27292b`, и `#353739` под вкладками не встречается вовсе. В токенах роли
разведены. Ниже — сводка с тем, что удалось доснять; это и есть входные числа `tokens.css`.
Масштаб снимка 2x подтверждён независимо: все четыре промежутка между панелями равны ровно **Способ, обязательный к повторению.** Поверхности, промежутки и полосы — `scripts/measure.py`
16 физическим пикселям, что даёт целые 8 CSS-пикселей. (команда и запинованное окружение — §4). Чего он не печатает: общая гистограмма кадра —
`PIL.Image` + `collections.Counter` по `im.getdata()`, именованная область — `im.crop(box)`,
насыщенные тона — фильтр по `colorsys.rgb_to_hls` (s > 0.25, 0.12 < l < 0.75). Масштаб снимков
2x подтверждён независимо: все четыре промежутка между панелями равны ровно 16 физическим
пикселям, что даёт целые 8 CSS-пикселей.
**Поверхности** (доля площади — по гистограмме всего кадра 2560×1528): **Поверхности — тон снят с `fleet_2.png` и `antigravity_*.png`**, двух редакторов, которых
владелец назвал эталоном (замечания второго круга 2 и 9):
| Что | fleet_2.png | antigravity_*.png | Наш токен |
|---|---|---|---|
| полотно панели | `#181818` (63.97% кадра) | `#161616` (18%) | `--color-panel: #181818` |
| хром (полоса вкладок, верх окна) | `#292929` (7.17%) | `#1c1c1c`/`#252525` | `--color-chrome: #242424` |
| фон окна | — | `#101010` (72%) | `--color-shell: #090909` (не трогали) |
| выбранная строка | `#184176` (срез строки CardPicker.tsx) | — | `--color-selected: #1c4478` |
| синий/зелёный акценты | — | `#2964ad` / `#3c7d4f` (NOTE/TIP) | `--color-note` / `--color-ok` |
Главное, что дал замер: **оба референса строго нейтральны, R=G=B** — тёплый подтон
«по рекомендациям про тёмные фоны» противоречил тем самым кадрам, на которые ссылались. Числа
воспроизведены вторым исполнением по `references/fleet_2.png` и сошлись, то есть `--color-panel`
стоит на замере, а `--color-chrome` (`#242424`) остаётся осознанно на ступень спокойнее хрома
референса.
**Остальные роли — с `fleet.png`** (доля площади по гистограмме кадра 2560×1528):
| Роль | Значение | Подтверждение | | Роль | Значение | Подтверждение |
|---|---|---| |---|---|---|
| фон оболочки | `#090909` | 10.45% площади | | фон оболочки | `#090909` | 10.45% площади |
| заливка панели | `#17191a` | 80.48% площади | | **активная вкладка открытого документа** | `#142f4c` | заливка вкладки `rpc-node.ts` — 7 183 px синеватого, а не серого |
| приподнятая поверхность, наведение, **активная вкладка панели** | `#27292b` | 18 196 px; вкладки `Files`, `Terminal`, `AI Assistant` |
| выбранная строка дерева, клавиша-чип | `#353739` | 37 374 px; ровно три пятна в кадре, и все три — строка дерева и чипы клавиш |
| **активная вкладка открытого документа** | `#142f4c` | **поправка**: заливка вкладки `rpc-node.ts` — 7 183 px синеватого, а не серого |
| подсветка текущей строки | `#152945` | 36 800 px | | подсветка текущей строки | `#152945` | 36 800 px |
| выделение | `#164e8d` | 476 px | | выделение | `#164e8d` | 476 px |
| текст основной | `#dfe1e3` | 0.40% площади | | текст основной | `#dfe1e3` | 0.40% площади |
| текст вторичный | `#8a8e91` | **доснято**: статус-полоса | | текст вторичный | `#8a8e91` | статус-полоса |
| текст приглушённый | `#707479` | **доснято**: колонка номеров строк | | текст приглушённый | `#707479` | колонка номеров строк |
| акцент | `#746deb` | 1 258 px, рамка поля ввода | | акцент | `#746deb` | 1 258 px, рамка поля ввода |
| красный индикатора | `#b82e45` | 829 px | | красный индикатора | `#b82e45` | 829 px |
⚠ Текстовый ряд и два акцента в токенах ПОДНЯТЫ против замера ради контраста
(`#dedede`/`#9b9b9b`/`#707070`, `--color-note`, `--color-danger` — нетекстовый порог 3:1, Ф-52):
замер здесь провенанс происхождения, а не действующее значение. Действующие числа — `tokens.css`.
**Геометрия** (физические пиксели / 2): **Геометрия** (физические пиксели / 2):
| Величина | CSS | Как замерено | | Величина | CSS | Как замерено |
@ -326,8 +348,6 @@ rm -f *.deb && cd ..
| промежуток между панелями | **8** | четыре среза по 16 физ. без исключений | | промежуток между панелями | **8** | четыре среза по 16 физ. без исключений |
| поле оболочки по краям | **8** | тот же срез: слева и справа те же 16 физ. | | поле оболочки по краям | **8** | тот же срез: слева и справа те же 16 физ. |
| радиус панели | **6** | угол выходит на прямую за 12 физ. | | радиус панели | **6** | угол выходит на прямую за 12 физ. |
| верхняя полоса | **28** | верхняя полоса фона 72 физ. = 8 поля + 28 полосы |
| статус-полоса | **20** | нижняя полоса фона 56 физ. = 20 полосы + 8 поля |
| шапка панели (вкладки) | **26** | заливка вкладки `Files` 86..137 физ. | | шапка панели (вкладки) | **26** | заливка вкладки `Files` 86..137 физ. |
| шаг строки дерева | **26** | шаг текстовых полос: 52 физ., 15 строк подряд | | шаг строки дерева | **26** | шаг текстовых полос: 52 физ., 15 строк подряд |
| подложка выбранной строки | **24** | заливка `#353739` — 48 физ. при шаге 52 | | подложка выбранной строки | **24** | заливка `#353739` — 48 физ. при шаге 52 |
@ -335,8 +355,16 @@ rm -f *.deb && cd ..
| ширина боковой панели при 1280 | **320** (25%) | границы панелей: 8‥328 · 336‥944 · 952‥1272 | | ширина боковой панели при 1280 | **320** (25%) | границы панелей: 8‥328 · 336‥944 · 952‥1272 |
| внутренний отступ панели | **6** | подложка строки 14‥321.5 при панели 8‥328 | | внутренний отступ панели | **6** | подложка строки 14‥321.5 при панели 8‥328 |
Модель раскладки, вытекающая из замеров и сходящаяся точно (764 = 8+28 + 700 + 20+8): **Полосы окна — по пере-замеру, а не по первой модели.** Первая модель считала полосы частью
поле оболочки 8 · промежуток 8 · верхняя полоса 28 · статус-полоса 20. области панелей (8 поля + 28 полосы) и держала текст на 4px выше референса. Замер:
центр иконок верхней полосы y=17.75 при полосе 0‥36; центр текста статус-полосы y=749.75 при
полосе 735.5‥764; поле текста полос 12.5 слева и 13.5 справа **от края окна**; пилюля «Files»
15‥59 при тексте 25‥52, до следующей ~5px. Отсюда `--topbar-height: 36`, `--statusbar-height: 28`,
`--bar-inset: 12`, `--tab-inset: 10`, а поле оболочки принадлежит области ПАНЕЛЕЙ, а не окну
целиком — иначе поле полосы складывается с полем оболочки и текст уезжает вдвое дальше от края.
Полная геометрия окна при этом не изменилась: 36 + 836 + 28 = 900, промежутки панелей остались
на 0 · 328 · 944 · 1272; после правки текст статус-полосы `x 12.5…1267.0`, `y 744.5…756.0` против
`12.5…1266.5`, `744.5…756.0` у Fleet.
**Типографика** (оценка по высоте глифов, точнее не измеряется): **Типографика** (оценка по высоте глифов, точнее не измеряется):
интерфейс ~13px (высота выносных элементов 10px), статус-полоса ~12px, интерфейс ~13px (высота выносных элементов 10px), статус-полоса ~12px,
@ -351,17 +379,52 @@ rm -f *.deb && cd ..
⚠ Две палитры в одном приложении не смешиваются. Базовая — Fleet; из Antigravity берётся ⚠ Две палитры в одном приложении не смешиваются. Базовая — Fleet; из Antigravity берётся
**форма** пустого состояния и выноски, не цвета. Токенов Antigravity в `tokens.css` не заводим. **форма** пустого состояния и выноски, не цвета. Токенов Antigravity в `tokens.css` не заводим.
### 5.2. Витрина сведена с референсом — что сошлось и что нет **Что видно только при `--dpr 1`.** Замечание владельца 17 («иконки шакальные») в кадрах при 2x
не видно ВООБЩЕ: полупиксель CSS там ложится в целый пиксель устройства. Поэтому у `npm run shot`
есть ключ `--dpr`. Причина мыла — сетка, а не толщина: набор нарисован на сетке 24, рисуется
размером 16, координата c уезжает в 2c/3, и у геометрических форм (c кратно трём) центр штриха
шириной 1px попадает ровно на целый пиксель, размазываясь по двум половинкам. Проверены четыре
варианта кадрами 1x: «штрих 2 при 16» и «размер 18, штрих 2» дают жирнее, но по-прежнему мыльно;
полупиксельный сдвиг SVG даёт целые линии. Взят он (`reset.css`).
Витрина снята тем же способом, что и референс (`--size 1280x764`, 2x), и промерена тем же кодом. **Сведение нашего кадра с референсом** (`node scripts/shot.mjs /showcase --size 1280x764`,
тот же инструмент):
**Сошлось до пикселя:** промежутки — четыре по 16 физ.; верхний край панелей — 72 физ.; | Что | fleet.png (референс) | Наш `/showcase` | Вердикт |
радиус — угол выходит на прямую за 12 физ.; шаг строки дерева — 26 CSS; нижняя полоса — 56 физ.; |---|---|---|---|
доли площади поверхностей (панель 83.5% против 80.5%, фон 9.4% против 10.5% — разница от того, | фон оболочки | `#090909`, 10.45% кадра | `#090909`, 10.43% | сошлось |
что у нас другое наполнение). Высота глифов статус-полосы совпала точно (23 физ.), | полотно панелей | `#17191a`, 80.48% | `#181818`, 75.36% | тон по замеру `fleet_2`; доля ниже, потому что банк в правой панели держит свои цвета |
вкладки — 19 против 20 физ. | промежутки по горизонтали | 0 · 328 · 944 · 1272, ширина 8 | 0 · 316 · 644 · 1272, ширина 8 | **ширина та же, границы сдвинуты осознанно:** правая панель шире (`--panel-context-width: 620`), в ней таблица, а не список |
| линия в 1px на x=515 | — | есть | не дефект: жёлоб читалки покрашен в цвет оболочки (замечание второго круга 5) |
| полосы по вертикали | верхняя 0‥36, статус 735.5‥764 | 0‥37 и 735‥764 | сошлось (полпикселя — округление среза) |
**Расхождения, названные вслух:** По критерию §5 витрина СТОИТ на референсе: ширины панелей 320 · 608 · 320, шаг строки дерева 26,
высота пилюли вкладки 26 совпадают, и «масштабы великоваты» при вьюпорте референса замером
не подтверждается — речь про физический размер на конкретном экране (В-7, Ф-36).
**ОТВЕРГНУТО — не переоткрывать:**
- **тёплый ряд поверхностей `#17191a` полотном, `#27292b` приподнятой, `#353739` выбранной
строкой.** Он снят с `fleet.png` и держится в §1.1 промта как отменённый; тон отверг владелец,
и токены стоят на нейтральном ряде `fleet_2`/antigravity выше. Заодно развелись роли: §1.1
отдавал `#353739` разом «выбранной строке» и «активной вкладке», а в кадре это разные вещи —
вкладка документа `#142f4c`, вкладка панели серая, и `#353739` под вкладками не встречается вовсе;
- **модель «вырез в хроме» под активную вкладку.** Срез по вертикали через активную вкладку
`fleet_2` (x=790, y=36‥68) даёт `#181818` без единой разделительной линии до содержимого, тогда
как соседняя неактивная лежит на `#292929`. Модель по этому замеру была построена и ОТКАЧЕНА
по слову владельца: в `Tabs.module.css` пилюля по `fleet.png` — полоса вкладок это полотно
панели, активная вкладка лежит НА нём скруглённой подложкой (`--color-raised`, у документа
`--color-tab-active`). Замер верен про сам референс, не про наш код;
- **предел меры набора читалки.** ⚠ **Ограничивать ширину набора нельзя — решение владельца
10.08:** попытку центрировать колонку фиксированным `--reader-width` он назвал «жёстко приклеен
к центру», токен удалён. Набор идёт во всю ширину панели; на 2560×1440 это ~120 знаков в строке.
Функциональную ширину боковых панелей намеренно НЕ переводили в долю кадра: 25% у Fleet это
следствие окна 1280, а не инвариант — 640px под дерево разделов были бы пустой ширины.
### 5.2. Расхождения с референсом, названные вслух
Числа сведения — в §5.1; здесь то, что расходится осознанно, и почему. Из типографики сошлось
и там не повторено: высота глифов статус-полосы точно (23 физ.), вкладки — 19 против 20 физ.
1. **Субпиксельное сглаживание.** У Fleet текст сглажен в серую шкалу (macOS): в статус-полосе 1. **Субпиксельное сглаживание.** У Fleet текст сглажен в серую шкалу (macOS): в статус-полосе
ровно 0% цветных пикселей. У нас в том же месте 1.29%, в дереве 1.35% против 0.37% — ровно 0% цветных пикселей. У нас в том же месте 1.29%, в дереве 1.35% против 0.37% —
@ -429,8 +492,13 @@ rm -f *.deb && cd ..
файла, а НЕ `git checkout` (в дереве лежат незакоммиченные правки чужих зон); после отката файла, а НЕ `git checkout` (в дереве лежат незакоммиченные правки чужих зон); после отката
дерево зелёное. дерево зелёное.
**Что ловится — по швам** (полные перечни форм каждого захода — в истории git этого файла; **Что ловится — по швам** (здесь классы, потому что список форм растёт, а классы держат).
здесь классы, потому что список форм растёт, а классы держат): ⚠ Зеркало этого списка — **что гейт обязан ПРОПУСКАТЬ**: легальные формы перечислены не прозой,
а исполняемым разрешающим списком в самих конфигах — `eslint.config.js` (`allowedColorValue`:
`none`/`currentColor`/`inherit`/`transparent`/`var(--`; `dynamicSeam`, где `*.module.css` выведен
из-под запрета глобального CSS; исключения по пути для `src/ui/**` и `src/api/**`, где легальны
примитивы и все восемь транспортов) и `stylelint.config.js` (`colorValues`; `0`, `auto`,
`inherit` — не размеры, а их отсутствие). Проверять надо ИХ, а не пересказ:
- **цвет и размер в CSS:** hex во всех записях, включая сокращённую `border`; `rgb()`/`color()`; - **цвет и размер в CSS:** hex во всех записях, включая сокращённую `border`; `rgb()`/`color()`;
системный и именованный цвет в `outline`/`caret-color`/`box-shadow`; `font-size` числом; системный и именованный цвет в `outline`/`caret-color`/`box-shadow`; `font-size` числом;
@ -505,101 +573,6 @@ rm -f *.deb && cd ..
переименованием и стоят дешевле, чем пропущенный литерал. переименованием и стоят дешевле, чем пропущенный литерал.
### 5.5. Пере-замер S3.5 (09.08) — что нашёл замер, а не глаз
Замечания владельца 1 и 5 требовали работать замером. Инструмент замера теперь живёт в репозитории
и воспроизводим: `python3 scripts/measure.py references/fleet.png .shots/showcase.png` печатает
доли поверхностей, промежутки по горизонтали и полосы по вертикали в CSS-пикселях.
**Что сошлось при вьюпорте референса 1280×764** (прямое наложение, оба кадра при 2x):
промежутки — четыре по 8 CSS ровно на тех же координатах (0, 328, 944, 1272); ширины панелей
320 · 608 · 320; шаг строки дерева 26; высота пилюли вкладки 26. То есть по критерию §5 витрина
СТОИТ на референсе, и «масштабы великоваты» при вьюпорте референса замером не подтверждается.
**Что разошлось и было исправлено:**
| Что | Замер Fleet | Было у нас | Стало |
|---|---|---|---|
| верхняя полоса | центр иконок y=17.75 при полосе 0‥36 — полоса прижата к краю окна | центр 22: 8 поля + 28 полосы | `--topbar-height: 36`, поле оболочки только по бокам |
| статус-полоса | центр текста y=749.75 при полосе 735.5‥764 | центр 745.5, под текстом пустая полоса 8px | `--statusbar-height: 28`, текст по центру полосы |
| поле текста полос | 12.5 слева, 13.5 справа ОТ КРАЯ ОКНА | 14 (8 поля оболочки + 6) | поле оболочки перенесено на область панелей, полосы полнокровные, `--bar-inset: 12` |
| поля и промежутки вкладок | пилюля «Files» 15‥59 при тексте 25‥52, до следующей ~5px | поле 8, промежуток 2 | `--tab-inset: 10`, промежуток `--space-3` |
Полная геометрия окна при этом не изменилась: 36 + 836 + 28 = 900, промежутки панелей остались
на 0 · 328 · 944 · 1272. Это и есть ответ на «особенно заметно по тексту снизу»: полосы у Fleet
прижаты к краям окна, а не отступают от них на поле оболочки, и текст в них центрируется по всей
полосе. Поле оболочки поэтому принадлежит области ПАНЕЛЕЙ, а не окну целиком — иначе поле полосы
складывается с полем оболочки, и текст уезжает вдвое дальше от края, чем в референсе.
После правки замер сходится с референсом по обеим осям: текст статус-полосы `x 12.5…1267.0`,
`y 744.5…756.0` против `12.5…1266.5`, `744.5…756.0` у Fleet.
**Что видно только при `--dpr 1`.** Замечание 17 («иконки шакальные») в кадрах при 2x не видно
ВООБЩЕ: полупиксель CSS там ложится в целый пиксель устройства. Поэтому у `npm run shot` появился
ключ `--dpr`. Причина мыла — сетка, а не толщина: набор нарисован на сетке 24, рисуется размером 16,
координата c уезжает в 2c/3, и у геометрических форм (c кратно трём) центр штриха шириной 1px
попадает ровно на целый пиксель, размазываясь по двум половинкам. Проверены четыре варианта кадрами
1x: «штрих 2 при 16» и «размер 18, штрих 2» дают жирнее, но по-прежнему мыльно; полупиксельный
сдвиг SVG даёт целые линии. Взят он (`reset.css`).
**Мера набора читалки.** ⚠ **Ограничивать ширину набора нельзя — решение владельца 10.08:**
попытку центрировать колонку фиксированным `--reader-width` он назвал «жёстко приклеен к центру»,
токен удалён. Набор идёт во всю ширину панели; на 2560×1440 это ~120 знаков в строке (боковые
панели держат функциональную ширину в пикселях, и вся лишняя ширина достаётся центру).
Функциональную ширину боковых панелей намеренно НЕ переводили в долю кадра: 25% у Fleet это следствие окна 1280, а
не инвариант — 640px под дерево разделов были бы пустой ширины.
---
### 5.6. Замер палитры S3.6 (10.08) — тон снят с референсов, а не подобран
Владелец отверг тёплый подтон S3.5 и назвал эталоном два редактора разом: Fleet (`fleet_2.png`,
докинут 10.08) и Antigravity. Тон снят с их кадров гистограммой (`PIL`, счёт точек по всему
кадру плюс срезы именованных областей), а не выбран глазом:
| Что | fleet_2.png | antigravity_*.png | Наш токен |
|---|---|---|---|
| полотно панели | `#181818` (64% кадра) | `#161616` (18%) | `--color-panel: #181818` |
| хром (полоса вкладок, верх окна) | `#292929` (7%) | `#1c1c1c`/`#252525` | `--color-chrome: #242424` |
| фон окна | — | `#101010` (72%) | `--color-shell: #090909` (не трогали) |
| выбранная строка | `#184176` (срез строки CardPicker.tsx) | — | `--color-selected: #1c4478` |
| синий/зелёный акценты | — | `#2964ad` / `#3c7d4f` (NOTE/TIP) | `--color-note` / `--color-ok` |
Главное, что дал замер: **оба референса строго нейтральны, R=G=B** — тёплый подтон «по
рекомендациям про тёмные фоны» противоречил тем самым кадрам, на которые ссылались. Второй
вывод — про вкладки: срез по вертикали через активную вкладку fleet_2 (x=790, y=36‥68) даёт
`#181818` без единой разделительной линии до содержимого, тогда как соседняя неактивная лежит
на `#292929`.
**Этот вывод про вкладки в коде НЕ воплощён, и воплощать его не надо:** модель «вырез в хроме»
была построена по нему и ОТКАЧЕНА по слову владельца. В `Tabs.module.css` пилюля по `fleet.png`
полоса вкладок это полотно панели, активная вкладка лежит НА нём скруглённой подложкой
(`--color-raised`, у документа `--color-tab-active`). Замер верен про сам референс, не про наш код.
Способ повторить (проверено исполнением 10.08 в S3.7, числа сошлись): `scripts/measure.py` для
поверхностей и промежутков, `PIL.Image` + `collections.Counter` по `im.getdata()` для общей
гистограммы и `im.crop(box)` для именованной области; насыщенные тона отбираются фильтром
по `colorsys.rgb_to_hls` (s > 0.25, 0.12 < l < 0.75). Окружение python запинено
`scripts/requirements.txt` (в системном python на стенде Pillow нет вовсе).
### 5.7. Пере-замер S3.7 (10.08) — палитра воспроизведена, кадры сведены
Референс `fleet_2.png` докинут владельцем при приёмке S3.6, и палитра S3.6 впервые проверена
не «со слов»: `.tooling/py/bin/python scripts/measure.py references/fleet_2.png` даёт **`#181818`
63.97% кадра** и **`#292929` 7.17%** — ровно те числа, на которые ссылается §5.6, то есть
`--color-panel` стоит на замере, а `--color-chrome` (`#242424`) остаётся осознанно на ступень
спокойнее хрома референса.
Наши кадры при вьюпорте референса — `node scripts/shot.mjs /showcase --size 1280x764` — и тот же
инструмент:
| Что | fleet.png (референс) | Наш `/showcase` | Вердикт |
|---|---|---|---|
| фон оболочки | `#090909`, 10.45% кадра | `#090909`, 10.43% | сошлось |
| полотно панелей | `#17191a`, 80.48% | `#181818`, 75.36% | тон по замеру S3.6; доля ниже, потому что банк в правой панели держит свои цвета |
| промежутки по горизонтали | 0 · 328 · 944 · 1272, ширина 8 | 0 · 316 · 644 · 1272, ширина 8 | **ширина та же, границы сдвинуты осознанно:** правая панель шире (`--panel-context-width: 620`), в ней таблица, а не список |
| линия в 1px на x=515 | — | есть | не дефект: жёлоб читалки покрашен в цвет оболочки (замечание второго круга 5) |
| полосы по вертикали | верхняя 0‥36, статус 735.5‥764 | 0‥37 и 735‥764 | сошлось (полпикселя — округление среза) |
## 6. Форма данных — из контракта, не из пересказа ## 6. Форма данных — из контракта, не из пересказа
**Здесь стоял пересказ того, что движок отдаёт фронту, и он отстал от канона.** Пересказ снят: **Здесь стоял пересказ того, что движок отдаёт фронту, и он отстал от канона.** Пересказ снят:
@ -619,9 +592,12 @@ rm -f *.deb && cd ..
2. **Одна вертикальная прокрутка на читалку.** Список строк-пар, внутри строки 2. **Одна вертикальная прокрутка на читалку.** Список строк-пар, внутри строки
`grid-template-columns: 1fr 1fr`. Синхронизации двух прокруток нет и не будет — колонки `grid-template-columns: 1fr 1fr`. Синхронизации двух прокруток нет и не будет — колонки
физически не могут разъехаться (`STACK_DECISIONS.md` §2). физически не могут разъехаться (`STACK_DECISIONS.md` §2).
3. **Денежных полей в интерфейсе нет нигде** (§4.8 промта): ни сумм, ни потолков, ни оценки, 3. **Состояние — меткой СЛОВОМ, цвет только дублирует** и только для внимания и отказа:
девять состояний точкой не различаются — проверено снимком, три пары совпадали. Правило
живёт в коде доккомментарием `Tone` (`src/api/vocabulary.ts`), замер — только здесь.
4. **Денежных полей в интерфейсе нет нигде** (§4.8 промта): ни сумм, ни потолков, ни оценки,
ни остатка. ни остатка.
4. **Фикстура ставится в ТРУДНЫЙ случай, а не в удобный.** Правило «прогресс не строить на 5. **Фикстура ставится в ТРУДНЫЙ случай, а не в удобный.** Правило «прогресс не строить на
„готово N из M“» было записано здесь ещё на S0 — и витрина всё равно вышла с наивным „готово N из M“» было записано здесь ещё на S0 — и витрина всё равно вышла с наивным
счётчиком глав, потому что фикстура выдумала поле, которого read-модель не отдаёт. Лечение счётчиком глав, потому что фикстура выдумала поле, которого read-модель не отдаёт. Лечение
сильнее правила: мир фикстур поставлен в состояние, где наивный счётчик дал бы ноль, сильнее правила: мир фикстур поставлен в состояние, где наивный счётчик дал бы ноль,

View file

@ -62,25 +62,16 @@ OpenAPI 3.1, типы генерятся из спеки), и слой данн
Ниже — не оценка на глаз, а **замер пикселей** `fleet.png` (Python/PIL: гистограмма поверхностей, поиск насыщенных пикселей, срез по горизонтали, детект края скругления). Скриншот снят при масштабе **2x** — установлено по кнопкам окна macOS: шаг 40 физических пикселей против штатных 20 CSS. Все размеры ниже уже переведены в CSS-пиксели. Ниже — не оценка на глаз, а **замер пикселей** `fleet.png` (Python/PIL: гистограмма поверхностей, поиск насыщенных пикселей, срез по горизонтали, детект края скругления). Скриншот снят при масштабе **2x** — установлено по кнопкам окна macOS: шаг 40 физических пикселей против штатных 20 CSS. Все размеры ниже уже переведены в CSS-пиксели.
**Поверхности Fleet:** **Таблицы поверхностей здесь БОЛЬШЕ НЕТ, и брать тон отсюда нельзя.** Она давала `#17191a`
полотном, `#27292b` приподнятой поверхностью и `#353739` выбранной строкой — все три отменены
| Роль | Значение | Где замерено | пере-замером палитры по слову владельца и в токенах не стоят (`FRONTEND_PLAN.md` §5.1, блок
|---|---|---| «что отвергнуто»). Действующие числа — только `src/tokens/tokens.css` и §5.1 плана.
| Фон оболочки: промежутки, верхняя полоса, статус-полоса | `#090909` | 10.5% площади |
| Заливка панели (все три панели одинаковы) | `#17191a` | 80.5% площади |
| Приподнятая поверхность / наведение | `#27292b` | |
| Выбранная строка дерева, клавиша-чип | `#353739` | ⚠ **не активная вкладка:** пере-замер развёл роли, вкладка документа — `#142f4c` (`FRONTEND_PLAN.md` §5.1) |
| Подсветка текущей строки (синеватая) | `#152945` | |
| Выделение | `#164e8d` | |
| Основной текст | `#dfe1e3` | |
| **Акцент (фокус ввода)** | **`#746deb`** | тон 243°, насыщенность 0.76 |
| Красный индикатора | `#b82e45` | тон 350° |
**Геометрия Fleet:** **Геометрия Fleet:**
- **Промежуток между панелями — ровно 8px**, одинаковый везде: слева, между всеми панелями, справа (замер среза: четыре промежутка по 16 физических пикселей, без единого исключения). - **Промежуток между панелями — ровно 8px**, одинаковый везде: слева, между всеми панелями, справа (замер среза: четыре промежутка по 16 физических пикселей, без единого исключения).
- **Радиус скругления панели — 6px** (край выходит на прямую за ~11 физических пикселей). - **Радиус скругления панели — 6px** (край выходит на прямую за ~11 физических пикселей).
- Панели не имеют ни рамок, ни теней: отделение только промежутком и разницей заливки `#090909``#17191a`. - Панели не имеют ни рамок, ни теней: отделение только промежутком и разницей заливки оболочки и полотна (`--color-shell``--color-panel`).
**Antigravity — для пустого состояния и настроек** (чистый нейтральный серый, без холодного оттенка, в отличие от Fleet): **Antigravity — для пустого состояния и настроек** (чистый нейтральный серый, без холодного оттенка, в отличие от Fleet):
@ -105,8 +96,6 @@ OpenAPI 3.1, типы генерятся из спеки), и слой данн
**Центр.** Полоса вкладок: иконка + имя + крестик; активная вкладка светлее и скруглена. Ниже содержимое с колонкой номеров строк приглушённого цвета, у текущей строки — едва заметная подсветка всей полосы. Мелкие подписи-подсказки над блоками очень тусклым серым. В правом верхнем углу — компактный индикатор проблем. **Центр.** Полоса вкладок: иконка + имя + крестик; активная вкладка светлее и скруглена. Ниже содержимое с колонкой номеров строк приглушённого цвета, у текущей строки — едва заметная подсветка всей полосы. Мелкие подписи-подсказки над блоками очень тусклым серым. В правом верхнем углу — компактный индикатор проблем.
**Нижняя панель центра** — отдельная скруглённая карточка со своей вкладкой.
**Правая панель.** Вкладка с заголовком и `+`. В пустом состоянии — центрированный блок: иконка, заголовок, описание, ряды «клавиша + подпись». Внизу поле ввода с **акцентной рамкой** — единственное цветное пятно в панели. **Правая панель.** Вкладка с заголовком и `+`. В пустом состоянии — центрированный блок: иконка, заголовок, описание, ряды «клавиша + подпись». Внизу поле ввода с **акцентной рамкой** — единственное цветное пятно в панели.
**Нижняя статус-полоса** лежит на фоне оболочки, вне карточек: слева хлебные крошки, справа компактные метаданные состояния. **Нижняя статус-полоса** лежит на фоне оболочки, вне карточек: слева хлебные крошки, справа компактные метаданные состояния.
@ -276,7 +265,7 @@ OpenAPI 3.1, типы генерятся из спеки), и слой данн
**Стенд ставится без sudo** (WSL2/Ubuntu, пароль root недоступен): Chromium Playwright плюс четыре системные библиотеки локально в `.tooling/`. Процедура — `FRONTEND_PLAN.md` §4, второго её экземпляра здесь нет намеренно: команды уже разошлись бы (там же CJK-шрифт стенда, без которого весь китайский в кадре — квадраты). **Стенд ставится без sudo** (WSL2/Ubuntu, пароль root недоступен): Chromium Playwright плюс четыре системные библиотеки локально в `.tooling/`. Процедура — `FRONTEND_PLAN.md` §4, второго её экземпляра здесь нет намеренно: команды уже разошлись бы (там же CJK-шрифт стенда, без которого весь китайский в кадре — квадраты).
Проверка, что цикл живой, если ставишь его заново: отрисуй страницу с панелью `#17191a` на фоне `#090909`, радиус 6, отступ 8 — сними, открой, убедись, что видишь именно это. Браузер headless, окна нет, графический сервер не нужен. Проверка, что цикл живой, если ставишь его заново: отрисуй страницу с полотном панели на фоне оболочки, радиус 6, отступ 8 — сними, открой, убедись, что видишь именно это. Браузер headless, окна нет, графический сервер не нужен.
### 7.2. Ревью исполнением — обязательно ### 7.2. Ревью исполнением — обязательно
@ -301,11 +290,9 @@ OpenAPI 3.1, типы генерятся из спеки), и слой данн
## 8. Критерий сравнения с референсом ## 8. Критерий сравнения с референсом
**Не растровое совпадение.** JetBrains Fleet закрыт (скачивание прекращено 22.12.2025), референс **Не растровое совпадение**, и почему — `STACK_DECISIONS.md` §7 п.5. Сходиться обязаны:
снят на macOS при 2x, наша цель — Windows-Chromium с другим хинтингом шрифтов. Пиксель-в-пиксель **замеренные цвета, промежутки 8px, радиусы 6px, плотность строк и общее впечатление**.
недостижим и целью не является. Сходиться обязаны: **замеренные цвета, промежутки 8px, радиусы Кегли и интерлиньяж — подбором на глаз до похожести, померить их больше негде.
6px, плотность строк и общее впечатление**. Кегли и интерлиньяж — подбором на глаз до похожести,
потому что померить их больше негде.
Гейт против хардкода цвета и размера обязан быть **проверен живым нарушением** (вставить `#fff` Гейт против хардкода цвета и размера обязан быть **проверен живым нарушением** (вставить `#fff`
в компонент, убедиться, что проверка падает, убрать) — а не объявлен зелёным. Что именно уже в компонент, убедиться, что проверка падает, убрать) — а не объявлен зелёным. Что именно уже

View file

@ -127,17 +127,13 @@
### Машинный гейт «одно место для цвета и размера» ### Машинный гейт «одно место для цвета и размера»
Прямой ответ на требование владельца о поддерживаемости. Двухсторонний, в одной команде: Прямой ответ на требование владельца о поддерживаемости: двухсторонний (stylelint в CSS,
`no-restricted-syntax` в TSX), в одной команде, и отключить его комментарием нельзя.
- **в CSS** — stylelint `declaration-property-value-allowed-list` со значением `/^var\(--/` на **ПОСТРОЕН и с тех пор ушёл дальше этого абзаца** — действующий состав правил, каждое с тем,
`color`, `background-color`, `border-color`, `fill`, `stroke`, `z-index`, `font-size`; чем оно проверяется, живёт таблицей в `FRONTEND_PLAN.md` §3, а границы («что гейт НЕ видит»)
исключение по пути только для `tokens.css` и сброса; названы там же в §5.4. Здесь их копии намеренно нет: она отставала — этот абзац ещё описывал
- **в TSX** — ESLint `no-restricted-syntax` на литералы `#hex` / `rgb(` / `hsl(` / `oklch(` запрет атрибута `style` уровнем warn и отступы «вторым шагом, когда шкала зафиксируется»,
плюс запрет атрибута `style` (уровень warn); тогда как оба закрыты (Ф-4, S2).
- **в CI** — stylelint с `reportDisables: true`, чтобы отключение правила комментарием тоже падало.
Отступы (`padding`/`margin`/`gap`/`border-radius`) подключить **вторым шагом**, когда шкала
токенов зафиксирована — иначе гейт будет мешать на этапе подбора.
### Одна команда проверки ### Одна команда проверки
@ -184,6 +180,11 @@ CI вызывает **именно их**, а не дублирует списо
против ратифицированной D39.130. Ниже остаются два шва, на которых стоит КОД ФРОНТА, — стрим против ратифицированной D39.130. Ниже остаются два шва, на которых стоит КОД ФРОНТА, — стрим
и предъявление сессии. и предъявление сессии.
**Одна оговорка о границе, которой по тому адресу НЕТ:** нижняя допустимая версия pgx —
**5.9.2, не 5.9.0**. Сегодняшний пин (`v5.10.0`) выше её, но при откате знание уже не
восстановить, а `platform/docs/STACK_DECISIONS.md` несёт только сам пин. Дом строки — там;
перенос — пингом оркестратору (чужая зона).
**Прогресс — SSE, и события ПУШИТ воркер**, а не фронт опрашивает read-model (см. §7, риск про **Прогресс — SSE, и события ПУШИТ воркер**, а не фронт опрашивает read-model (см. §7, риск про
ре-ингест). Браузер — нативный `EventSource` на cookie; десктоп и CLI — обычный GET с ре-ингест). Браузер — нативный `EventSource` на cookie; десктоп и CLI — обычный GET с
`Authorization: Bearer` и построчным парсером. WebSocket не нужен. Обязательно: HTTP/2 на границе, `Authorization: Bearer` и построчным парсером. WebSocket не нужен. Обязательно: HTTP/2 на границе,

View file

@ -65,8 +65,8 @@
Владелец: **«+» слева = «Добавить книгу»**. Отсюда: «+» стоит в ряду вкладок ЛЕВОЙ панели и Владелец: **«+» слева = «Добавить книгу»**. Отсюда: «+» стоит в ряду вкладок ЛЕВОЙ панели и
запускает добавление, дублирующая строка «+ Добавить книгу» из дерева убрана (одна вещь — одно запускает добавление, дублирующая строка «+ Добавить книгу» из дерева убрана (одна вещь — одно
место, и в референсе «+» стоит ровно там); в центре и справа «+» нет — наборы вкладок там место, и в референсе «+» стоит ровно там); в центре и справа «+» нет — там кнопка вышла бы
фиксированные, пользователь их не расширяет, и кнопка вышла бы декоративной. Остаток строки — Ф-7. декоративной. Остаток строки — Ф-7.
В S2 действие открывает вкладку-документ «Добавить книгу»; содержимое экрана (§3.1) делает S4. В S2 действие открывает вкладку-документ «Добавить книгу»; содержимое экрана (§3.1) делает S4.
@ -130,10 +130,9 @@
**⛔ ПИНГ ОРКЕСТРАТОРА №19 — 23.08, при разморозке зоны (сейчас действий не требует).** **⛔ ПИНГ ОРКЕСТРАТОРА №19 — 23.08, при разморозке зоны (сейчас действий не требует).**
Аудит доков нашёл два расхождения внутри зоны; рукой не лезу, зона заморожена. Аудит доков нашёл два расхождения внутри зоны; рукой не лезу, зона заморожена.
1. **Три вопроса списка были ОТВЕЧЕНЫ каноном и не погашены**В-3 (D39.176 п.4: фразы делегированы 1. **Восемь оставшихся вопросов листа — сверить каждый против D-лога прежде, чем нести владельцу
проекту), В-6 (D39.132 п.2а, а кадр `EventCeiling` снят каноном 0.3.0) и В-10 (D39.138 п.2к — повторно.** Три из одиннадцати каноном уже отвечены и сняты 02.09: В-3 (D39.176 п.4), В-6
«вежливость»); строки сняты 02.09 при чистке журнала. Остальные восемь — сверить каждый против (D39.132 п.2а), В-10 (D39.138 п.2к).
D-лога прежде, чем нести владельцу повторно.
2а. **Ключ `prepare` вашего `package.json` — единственный бутстрап РЕПО-ШИРОКОГО гейта доков (дописано 24.08; см. буллет «Гейты стоят и на коммите» в «Текущем состоянии»).** 2а. **Ключ `prepare` вашего `package.json` — единственный бутстрап РЕПО-ШИРОКОГО гейта доков (дописано 24.08; см. буллет «Гейты стоят и на коммите» в «Текущем состоянии»).**
`frontend/scripts/githooks/install.mjs` — единственный в дереве установщик диспетчера `.git/hooks/pre-commit`, а тот `frontend/scripts/githooks/install.mjs` — единственный в дереве установщик диспетчера `.git/hooks/pre-commit`, а тот
судит `CLAUDE.md` и доки трёх зон, не только ваши. Следствие проверено исполнением: на свежем клоне без `npm install` судит `CLAUDE.md` и доки трёх зон, не только ваши. Следствие проверено исполнением: на свежем клоне без `npm install`
@ -166,12 +165,12 @@
**Поправки к записи 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 (носитель там, дефекта сессии нет — фиксирую для полноты). **Поправки к записи 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 (носитель там, дефекта сессии нет — фиксирую для полноты).
*(Записи сессий S0S4 и их приёмок (0214.08) — в [archive/frontend-PROGRESS-S0-S4.md](archive/frontend-PROGRESS-S0-S4.md); вынесено 16.08 по слову владельца. Запись приёмки S4 с фикс-листом ФС — ВЫШЕ, она живая.)* *(Запись приёмки S4 с фикс-листом ФС — ВЫШЕ, она живая; всё остальное по S0S4 вынесено в архив, см. шапку.)*
## Пинг оркестратора №17 — 16.08.2026 (зона во фризе; контракт-ревью принято D39.138 — четыре ваших вопроса ОТВЕЧЕНЫ, входы разморозки) ## Пинг оркестратора №17 — 16.08.2026 (зона во фризе; контракт-ревью принято D39.138 — четыре ваших вопроса ОТВЕЧЕНЫ, входы разморозки)
Контракт-ревью API v0 отработало отдельной сессией и ПРИНЯТО (отчёт — `docs/research/28-contract-review.md`; решения владельца — его §8, ратификация D39.138). Заказан ЛОМАЮЩИЙ батч 0.3.0 (строка 183 единого бэклога) — типы перегенерируются после его лендинга; до него нормативной остаётся 0.2.3. ⚠ Читать ОРИГИНАЛ отчёта, не пересказ. По вашим строкам: Контракт-ревью API v0 отработало отдельной сессией и ПРИНЯТО (отчёт — `docs/research/28-contract-review.md`; решения владельца — его §8, ратификация D39.138). Заказан ЛОМАЮЩИЙ батч 0.3.0 (строка 183 единого бэклога) — типы перегенерируются после его лендинга; до него нормативной остаётся 0.2.3. ⚠ Читать ОРИГИНАЛ отчёта, не пересказ. По вашим строкам:
16. **Ф-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): замечания показывать ВСЕ, в тексте сворачивать и раскрывать кнопкой, словарь ступеней заранее НЕ проектировать — зарегистрировать зонной строкой при разморозке. ⚠ Читать ОРИГИНАЛ отчёта, не пересказ. 16. **Ф-56 · Ф-57 · Ф-61 · Ф-62 · Ф-43 · К-6 — ответы ревью, и все они с тех пор ВОШЛИ в канон** (батч 0.3.0 и далее до 0.9.0; 20 операций). Пересказ здесь не держу: пять строк несут свой ответ сами — ячейки **Ф-56 · Ф-57 · Ф-61 · Ф-62 · Ф-43** бэклога переписаны по канону, и при разморозке читаются они, а не этот пинг. Своей строки нет только у **К-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`). 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) — зонная строка. Вопросы — через владельца. 8. **Перф-входы S5 (§5б/Б-11а):** снять `refetchOnWindowFocus` для тяжёлых коллекций (замер: самый частый трафик порождает переключение вкладок, не пайплайн — 12 вкладок = 562 КБ на фокус) и поднять `staleTime` после появления ETag/304 у платформы; ErrorBoundary в зоне нет ни одного (Б-0: TypeError рендера при смене словаря волн) — завести при разморозке; 401 сегодня тупик без ссылки на вход (§4 №27) — зонная строка. Вопросы — через владельца.
@ -179,9 +178,9 @@
**Дописка к аддендуму (16.08):** гейт на утечку конвейера в провод — «на проводе нет имён стадий/волн и движковых словарей», машинный тест по образцу `generality.test.ts` — назначен вашей зоне (D39.138 п.2а; спек-сессия батча запишет правило в компаньон контракта). Строить при первом касании зоны вместе с синком зеркала и перегенерацией типов. **Дописка к аддендуму (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 (модель подписи банка, слово владельца; читать ПЕРЕД S5):** модель целиком — **D39.144** (подписывается ВЕСЬ банк одним ОК; пер-термная масс-подпись сотен строк НЕ строится; 409-гейт полноты убран из канона, `pending_decisions`/`complete` информационные). Зоне отсюда два следствия. Строки Ф-44/Ф-45/Ф-46 при разморозке читать через эту модель: «действие подписи» = один ОК + опциональные точечные правки, НЕ проход по строкам. ⚠ Экранов под пост-ридинговые правки банка («поправить/добавить термин → пере-генерация по вхождениям») НЕ строить до отдельного слова: механика не спроектирована нигде, полигон работает над точечным редактором (D39.144 п.1). Вопросы — через владельца.
**Дописка к пингам №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 — разморозка отдельным словом владельца). Вопросы — через владельца. **Дописка к пингам №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 «на экране ничего не менять» устарели; движковая строка 160 остаётся про НАСТОЯЩИЕ названия). (3) **Ф-31/В-6 закрыты D39.132 п.2а** (`null` на проводе ратифицирован; кадр `EventCeiling`, на который они опирались, снят каноном 0.3.0 целиком) — исполнено 02.09. (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 против дев-стенда остаётся легитимным и во фризе. Вопросы — через владельца. **Пинг оркестратора №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 против дев-стенда остаётся легитимным и во фризе. Вопросы — через владельца.
@ -212,7 +211,7 @@
Проверено моей командой: `grep -rc 'bank-stop.json' backend/ --include='*.go'`**ни одного вхождения**; Проверено моей командой: `grep -rc 'bank-stop.json' backend/ --include='*.go'`**ни одного вхождения**;
холодный прогон 31.08 подтвердил это на диске. Свой носитель (`docs/PROGRESS.md`, строка 157) я поправил. холодный прогон 31.08 подтвердил это на диске. Свой носитель (`docs/PROGRESS.md`, строка 157) я поправил.
**Ваш носитель ПОЧИЩЕН 02.09** чисткой этого журнала: ни пинг №17 п.5, ни аддендум к нему больше не **Ваш носитель ПОЧИЩЕН 02.09** чисткой этого журнала: ни пинг №17, ни аддендум к нему больше не
называют `.bank-stop.json` живым сайдкаром. Остаток первого касания — строка Ф-43 бэклога: она называют `.bank-stop.json` живым сайдкаром. Остаток первого касания — строка Ф-43 бэклога: она
протухла обеими половинами (пинг №20 п.2). протухла обеими половинами (пинг №20 п.2).