textmachine/frontend/docs/BACKLOG.md

38 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Бэклог фронта (зонный)
> Ведёт зона `frontend/` (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог `docs/PROGRESS.md` — трекер движка/полигона/доков, фронт-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99102 — пофазный прогресс · манифест глав · машиночитаемая таблица подписи · трасса). **Развод «предложить/ратифицировать» (D39.99 п.4, после инцидента остановки S3): ПРЕДЛАГАТЬ артефакты для чужих решений — черновики контрактов, входы дизайна — фронт не только может, но обязан; не может он их РАТИФИЦИРОВАТЬ. «Не заводит своим решением» значит «финальное слово не его», а не «не смей писать черновик».** Засеян оркестратором при лендинге D39.84 — дальше правят фронт-сессии.
| ID | Хвост | Вес | Источник |
|---|---|---|---|
| Ф-1 | **Этапы S2S7** (`FRONTEND_SESSION_PROMPT.md`): S2 оболочка + слой `src/ui/` · S3 слой данных/MSW/фикстуры всех состояний · S4 библиотека/загрузка/разбор/прогресс · S5 банк памяти и подпись (самый тяжёлый) · S6 читалка двух колонок и замечания · S7 метаданные/экспорт/настройки/сквозной прогон | по очереди сессий (S0/S1 — текущая) | FRONTEND_SESSION_PROMPT |
| Ф-2 | **React Compiler** — включить ОТДЕЛЬНЫМ шагом после заморозки интерфейса, с CI-проверкой, что вставки реально попали в бандл (в MVP выключен; совместимость держит линт eslint-plugin-react-hooks) | после заморозки UI | STACK_DECISIONS §1 |
| Ф-3 | **Пересмотр TS 6 → TS 7** после выхода TS 7.1 с программным API (~октябрь 2026); решение обратимо — тайпчек не участвует в сборке | триггер: релиз TS 7.1 | STACK_DECISIONS §1 |
| ~~Ф-4~~ | ~~**Токен-гейт на отступы**~~**включён 04.08 в S2.** Значение теперь обязано быть последовательностью `var(--…)`; `0`, `auto` и `inherit` легальны (это не размеры), `calc()` намеренно запрещён: составное выражение из литералов проверить нечем, а если размер понадобился новый — его место в `tokens.css`. Свойства взяты регулярками, поэтому длинные формы (`border-top-left-radius`, `padding-inline`) закрыты тоже. Обкатка потребовала трёх токенов, которых не хватало: `--radius-round`, `--radius-bar`, `--row-inset`. Проверено шестью живыми нарушениями и тремя легальными формами (перечень — `FRONTEND_PLAN.md` §5.4) | закрыт | STACK_DECISIONS §3 |
| ~~Ф-8~~ | ~~Вопрос владельцу: закрывать ли `/* eslint-disable */` в TSX~~**закрыт 02.08 без решения владельца.** Вопрос был поставлен как выбор из двух плохих вариантов, а у него есть отраслевой третий: `@eslint-community/eslint-plugin-eslint-comments` 4.7.2 (`no-unlimited-disable` + `require-description` + `disable-enable-pair`). Голое отключение падает тремя ошибками, точечное подавление с причиной проходит. Урок: не выносить владельцу технический вопрос, не поискав отраслевую практику | закрыт | адверсариальное ревью S1 |
| ~~Ф-9~~ | ~~**Хвосты гейтов после адверсариального ревью S1**~~**все четыре закрыты 04.08 в S2**, каждая живым нарушением: именованные и системные цвета в TSX ловятся разрешающим списком на цветных атрибутах (`fill`/`stroke`/`color`/`stopColor`/`floodColor`/`lightingColor` — только `none`, `currentColor`, `inherit`, `transparent`, `var(--…)`) · значение CSS-переменной в `style={{…}}` обязано быть числом или токеном · носитель `style` внутри объектного литерала запрещён, и тем самым закрыт JSX-спред · системные цветовые ключевые слова закрыты `declaration-property-value-disallowed-list` по ВСЕМ свойствам, то есть и в сокращённых записях `outline`/`box-shadow`/`border`. Перечень проверенных форм (15 TSX + 14 CSS) — `FRONTEND_PLAN.md` §5.4.1. **Граница названа честно и там же:** гейт цветных атрибутов ловит литерал и шаблон, но не вычисленное значение (`const c = 'red'; <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 |
| Ф-11 | **Контраст ниже порога WCAG на 4 узлах на маршрут** (приглушённый текст `#707479` и `#8a8e91` на `#17191a`); число пере-сверено живым прогоном `npm run check:full` 08.08 — axe печатает **6** узлов на маршрутах с данными (`/showcase`, `/scale`, `/offline`) и **3** на пустых (`/empty`, `/loading`, `/error`); было по 4 на двух маршрутах 04.08. Рост не регресс палитры, а рост числа приглушённых надписей: статус-полоса получила ETA, причину паузы и состояние живого канала, и все они на `--color-text-muted`. Маршрутов стало шесть, поэтому и сумма по прогону выросла. Это прямое следствие копирования палитры Fleet, которую владелец принял; axe отчитывается о них отдельно и команду не валит. Решение владельца: оставить как есть ради верности референсу или поднять приглушённые тона | решение владельца | ревью S1 №2 |
| Ф-12 | **Масштаб списков****главное закрыто 04.08 в S2 замером, а не обещанием.** Заведён маршрут `/scale`: та же оболочка на настоящем масштабе — 2284 раздела и банк на 1200 терминов (`src/mock/scale.ts` через `getScaleFixture()`). Дерево и банк виртуализованы по умолчанию (RAC `Virtualizer` + `ListLayout`), и это измерено в живом браузере: на 2293 узлах дерева в DOM держится **31 строка**, `scrollHeight` 59 618px, кадр при прокрутке — медиана 22 мс, p95 31 мс; банк на 1200 терминах — 30 строк в DOM, медиана 16,7 мс. Прокрутка до конца и обратно счётчик строк не растит. **Замечания намеренно НЕ виртуализованы:** их список пер-главный, а юнитов на главу ≈1,9 (ПТ-21) и флаг у юнита один — 10³ там не бывает по построению; сводный вид по книге, если он появится, придёт с S6 и получит тот же примитив `ui/List`. **Два хвоста строки НЕ сделаны и получают диспозицию, а не зачёркивание** (нашло адверсариальное ревью S2): дерево осталось плоским без группировки по сотням — это работа экрана глав (S4), у оболочки для неё нет ни данных, ни требования; банк без разделения «подписано / ждёт решения» — это работа подписного экрана (S5), справочному виду в 320px разделение только мешает | масштаб закрыт; группировка — S4, разделение банка — S5 | ревью S1 №2 |
| Ф-14 | **Вход фронта в контракт API (единый бэклог, строка 95)****доведён до артефакта 04.08 в S3: [`API_CONTRACT_INPUT.md`](API_CONTRACT_INPUT.md)**, перечень с обоснованием на каждый пункт, каждое обоснование грунтовано `file:line` движка (прочитано, не пересказано). Внутри: язык кодами · идентификатор главы отдельно от номера (номер производный от чанкера, `chunker.go:101-118` — пустая глава не потребляет номер, правка исходника сдвигает нумерацию) · единица пары = ЮНИТ с ключом лидера `(chapter, firstChunkIdx)`, `export.go:88-93` · состояние у прогона, у главы выполнение · прогресс пофазно по юнитам · `run_id` · трёхзначный `auto\|draft\|approved` вместо булева `signed` · непрозрачный ключ строки банка (автоинкремент движка стабильным НЕ является — `migrate.go:172-174`, банк заменяется целиком каждый прогон) · поля `type`/`sense`/`since_ch`/`source` с точным словарём · подпись = набор решений promote/decline, стоп снимается ТОЛЬКО полным набором (`mining.go:200-202`) · продуктовый словарь вердиктов и запрет `detail` на стороне контракта · монотонный `id` + `Last-Event-ID` + ревизия на чтениях из того же счётчика · **разрешающий список полей вместо вычитания** (инвентарь протечек `StatusReport`/`ChapterPassport`: деньги · `routing`/модели · снапшот/дрифт/ре-билл) · правило эволюции словарей под Ф-22. Плюс 6 вопросов (К-1..К-6), на которые фронт ответа не имеет. **Найдено при сборке — четвёртый экземпляр класса «фикстура выдумала форму»:** титульная политика pack-13 рендерит заголовок ДЕТЕРМИНИСТИЧЕСКИ («Глава {n}» из `configs/langpacks/zh-ru/heading.txt`), вырезает исходный маркер из текста для модели (`chunker.go:110-118`) и ВКЛЕИВАЕТ титул внутрь финального текста первого юнита (`export.go:209-215`), оставляя колонку исходника без него — а фикстура рисует «Раздел N. …» в дереве и неснятый «第二节:» в оригинале. Не чинится до ответа на К-2: правка формы заголовка без контракта даст пятую выдумку. **По слову владельца тем же днём написан и ЧЕРНОВИК самого контракта — [`API_CONTRACT_DRAFT.md`](API_CONTRACT_DRAFT.md)** (12 разделов + приложение-карта вердиктов; каждое место помечено «выведено из кода» / «предложено фронтом» / «открыто»; двухфазный поток ВЫВЕДЕН из `--verify-bank`, а не спроектирован; в конце — прогон ревью-вопроса строки 95 по шести изменениям движка). Самопроверка черновика нашла дыру в самой лестнице статусов строки 95: между «разбор» и «перевод» нет состояния покоя, а библиотека обязана показывать разобранную книгу, которую не запускали ⇒ заведено `not_started`, и сегодняшние девять значений типов разводятся на десять. **Дофикс по ревью оркестратора (04.08) исполнен, форма сменена по решению владельца:** нормативная поверхность контракта — теперь **OpenAPI 3.1 `docs/api-contract/openapi.yaml`** (spectral шестым шагом `check`, openapi-typescript генерит `src/api/schema.ts`, тест дрифта + ветка неизвестного значения на шве `src/api/contract.ts`, MSW-хендлер как proof-of-form); md стал спутником — провенанс, обоснования, К-вопросы. Закрыты оба блокера ревью (состояние пары выводится из ПАРЫ «вердикт + наличие текста», а не из вердикта — флагнутый юнит легально приходит С текстом; чтение банка помечено зависимостью «канала нет»), восемь ✓-инфляций пере-размечены на ◆, внесены все фактические правки (фантом `auto` в провенансе убран по путям записи · `upstream_not_ok` добавлен · ПТ-34 заведён пунктом · стоп по потолку разведён с `failed` · правило разрыва `Last-Event-ID` · смягчён §12 про чанкер). Открытых вопросов стало девять: добавлен К-9 — **отказ прескрина (строка 94) не выразим ни одним из десяти статусов**. **Ратифицировать и перенести в `docs/architecture/14-api-contract/` — оркестратор; фронт этого не может** (шапка этого файла). **Подрезана 04.08 по слову владельца:** из описаний спеки убран провенанс и грунты `file:line` — они дублировали спутник и разошлись бы с ним при первой правке; осталось норматив плюс та оговорка «почему», что предотвращает конкретную ошибку. Спека переведена на английский, потому что её описания приезжают доккомментариями в генерённый `src/api/schema.ts` (Ф-25). Адверсариальная проверка подрезки нашла в спеке три расхождения с самим спутником (провенанс `mined` подан как «никем не подписано» — это ось `status`; ревизия кадра объявлена фактом при открытом К-4; `Run.status` берёт книжный словарь целиком) и два вопроса, которых не было: **К-10** пофазность выполнения главы и **К-11** условная обязательность полей ⇒ открытых стало одиннадцать. **Бумага сокращена тем же днём:** `API_CONTRACT_INPUT.md` под ⚠-баннером «исполнено, не поддерживается» — его 18 разделов лежат один-в-один в §2 спутника, и как живой документ он был третьим местом правки; не удалён (untracked, удаление невосстановимо), перенос в `docs/archive/` — оркестратору. Из спутника убран §1 (пины и отклонение по пиру дублировали `STACK_DECISIONS.md` §3 и Ф-23/Ф-24). **Второй пасс ревью (round-2) исполнен:** чтение выгрузки + К-12 · условие `if action=promote → dst` непустой (spectral валидирует, генератор ИГНОРИРУЕТ — записано в К-11 измерением) · `kind` nullable с правилом пустого, пометка ✓→◆ · `resume` после потолка помечен зависимостью на платформенный механизм поднятия · §2.7 переписан: «текст + замечание» это класс, но `glossary_miss` в него не входит (проверено по `memberDrops`/`waverun`), фикстура `r2-2` переведена на c-lite drop · ветка неизвестного добита до шести словарей. Открытых вопросов двенадцать. **08.08 (S3): РАТИФИЦИРОВАН и ПРАВЛЕН.** Контракт ратифицирован оркестратором как D39.99; S3 внесла в зонную копию весь список правок своего промта и бампнула версию до **0.2.0**`paused`, `eta_seconds`, метка главы из данных, пер-книжная ревизия с правилом `>= R` и обязательным `resync_required` после полной замены, keyset-курсор на ВСЕХ списках, `X-TM-Client`, `Run.paused_reason`, `GET /usage`, `GET /books/{id}/run-options` с потолком прогона в ГЛАВАХ, same-origin. Байт-сверка с каноном намеренно даёт расхождение: канон правит оркестратор, диффом. Открытых вопросов стало **семь** (закрыты К-1/К-3/К-4/К-5/К-7/К-8/К-12, добавлен К-13 — Ф-31). Что переносить в канон — [`S3_CONTRACT_HANDOFF.md`](S3_CONTRACT_HANDOFF.md) | правки внесены · ждут пере-ратификации диффом | ревью S1 №3 · сборка S3 04.08 · дофикс ревью 04.08 · подрезка 04.08 |
| ~~Ф-15~~ | ~~**`src/api/` синхронен, состояний загрузки и ошибки в типах нет**~~**ЗАКРЫТА 08.08 в S3.** `src/api/` асинхронен и разговаривает HTTP: MSW как СЕТЕВОЙ мок (а не подменённый модуль), поэтому приложение делает настоящий `fetch` и настоящий `EventSource`, и ветки ожидания/ошибки/пустоты — это код, который работает в бою, а не второй его экземпляр. Ветки живут в одном месте (`showcase/Loaded.tsx`) и приезжают ВМЕСТЕ со слоем данных, до первого продуктового экрана, как строка и требовала. Типы сведены с контрактом: `types.ts` удалён, экраны живут на суженных типах `api/contract.ts`. **Что нашёл сам скриншот-цикл, а не рассуждение:** выключенный запрос TanStack Query остаётся `pending` НАВСЕГДА, и на пустой библиотеке правая панель вечно показывала «Идёт загрузка» — различение «ждём» и «выключено» (`fetchStatus === 'idle'`) добавлено после того, как это увидели на кадре. Фикстуры всех состояний — шесть миров, по маршруту на каждый | закрыт | ревью S1 №3 |
| Ф-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 |
| Ф-16 | **Выгрузка книги (§3.11) ушла из верхней полосы.** В витрине S1 там висела иконка загрузки без действия; по правилу Ф-7 («кнопка только вместе с действием») она снята до S7, который строит экспорт. Вернуть иконку вместе с самим действием | S7 | S2 |
| Ф-17 | **Закрытие вкладки с клавиатуры не объявлено скринридеру.** Крестик у вкладки-документа сознательно НЕ кнопка: у роли `tab` дети презентационные (ARIA), вложенный фокусируемый элемент выпал бы из дерева доступности, оставшись в порядке табуляции (это же ловит axe правилом `nested-interactive`). Мышь закрывает крестиком, клавиатура — `Delete` на выбранной вкладке. Объявить сочетание нечем: `aria-keyshortcuts` не входит в типизированный API `Tab`/`TabList` в react-aria-components 1.20.0. Пересмотреть при обновлении библиотеки либо при появлении палитры команд | триггер: мажор RAC или экран горячих клавиш | S2 |
| Ф-19 | **Переключение вкладки размонтирует её содержимое, и прокрутка длинного списка теряется.** Нашло адверсариальное ревью S2 и подтвердило независимым прогоном: `react-aria-components` не рендерит невыбранную `TabPanel` (`dist/private/Tabs.mjs:276``if (!isSelected && !shouldForceMount) return null`), поэтому на `/scale` прокрутка дерева `scrollTop=9000` → вкладка «Поиск» → обратно даёт `scrollTop=0`, а пересборка коллекции из 2284 узлов стоит 70140 мс (два независимых замера). Ф-12 это не переоткрывает: там речь про поведение списка при 10³, и оно держится. **Чинить сейчас не стали:** `shouldForceMount` не лечит (панель остаётся видимым `div` и легла бы поверх соседней в той же ячейке грида), а настоящее лекарство — keep-alive либо запоминание офсета на вкладку — это решение об устройстве оболочки, и принимать его вслепую, до настоящих экранов, значит строить механизм не под задачу | S4 (экран глав) либо раньше, если владелец увидит потерю места | адверсариальное ревью 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). Тем же ходом закрыт названный в строке второй дефект того же класса: незнакомая ступень замечания больше НЕ уходит в спокойную ветку — неизвестная ступень берёт ветку внимания, потому что единственное, чего ось важности делать не вправе, это прятать важное. Продуктовое слово ступени по-прежнему за владельцем (Ф-21), и оно тут не выдумано | закрыт | адверсариальное ревью S2 → ревью оркестратора 04.08 → S3 08.08 |
| Ф-21 | **Ступень замечания различается только цветом полоски** (`attention` синяя, `glance` серая). Зрячему разница читается ещё и светлотой, но скринридеру приоритет не достаётся вовсе. Лечение — слово, как это уже сделано для состояния прогона книги; **какое именно слово, решает владелец**: словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, так что «важно/неважно» не годится | решение владельца | адверсариальное ревью S2 |
| Ф-18 | **Бандл 545 КБ (168 КБ gzip) одним куском** — замер 04.08 после установки `react-aria-components`; Rolldown предупреждает про порог 500 КБ. Для десктопного SPA за логином это не авария (индексации нет, сеть локальная), но одним куском едут и экраны, которых пользователь ещё не открыл. Лечение — разбиение по маршрутам, и делать его осмысленно после S4S7, когда экраны появятся: сейчас резать нечего | после S7 либо при жалобе на первый экран | S2, замер |
| Ф-23 | **`openapi-typescript` держится на точечном `overrides`, а не на честном пире.** Генератор объявляет `typescript@^5.x` (7.13.0 вышла 11.02.2026, до релиза TS 6), у нас намеренный TS 6 — Ф-3. `--legacy-peer-deps` **отвергнут проверкой исполнением:** после него обычный `npm install` падает, то есть дерево ломается у каждой следующей сессии и у `prepare`-инсталлера pre-commit хука. Взят `overrides` на ОДИН пакет (`{"openapi-typescript":{"typescript":"$typescript"}}`) вместо глобального ослабления проверки пиров; работоспособность доказана: `npm ci` с нуля зелёный (0 уязвимостей), генератор запускается на TS 6, тест дрифта проходит. Снять, когда генератор расширит пир либо когда приедет TS 7.1 и Ф-3 закроется | триггер: пир генератора расширен ИЛИ TS 7.1 (Ф-3) | S3, дофикс контракта 04.08 |
| Ф-24 | **AsyncAPI рассмотрен и отложен.** Он описал бы SSE-канал строже, чем нынешняя таблица «имя события → схема» в описании `EventEnvelope`. Отложен, потому что это второй нормативный артефакт и второй линтер ради ОДНОГО эндпоинта, при том что полезные нагрузки уже описаны схемами OpenAPI и попадают в генерённые типы. Пересмотреть, когда каналов станет больше одного либо когда понадобится генерировать клиент потока, а не разбирать кадры руками | триггер: второй канал ИЛИ генерация клиента потока | S3, дофикс контракта 04.08 |
| Ф-25 | **`src/` смешанный по языку комментариев.** Конвенция владельца от 04.08 — исходники английские; мой дифф S3 ей приведён (`api/contract.ts`, `api/contract.test.ts`, `mock/handlers.ts`, `.spectral.yaml`, `.prettierignore`, хунк в `tokens/tokens.test.ts`), а комментарии S1/S2 в ~40 файлах остались русскими (`api/index.ts`, `api/types.ts`, `showcase/*`, `ui/*`, `tokens/*`, `scripts/*`). Свод — отдельный проход: он трогает уже отревьюенный и залэнденный код, то есть даёт дифф чистой косметики поверх чужой работы, и делать его надо одним движением, а не по файлу за сессию. **Границу держать явно:** переводятся комментарии, имена тестов и служебные строки; UI-текст и фикстурная проза остаются русскими — это продукт, а не код | отдельный проход, по слову владельца/оркестратора | слово владельца 04.08 |
| Ф-26 | **Выгрузка и загрузка книги в слое данных не заведены — намеренно.** `POST /books` (multipart с прогрессом отправки) и пара `POST/GET /books/{id}/exports` (опрос по `Retry-After`) несут МЕХАНИЗМ, а не поверхность: прогресс загрузки — часть экрана S4, цикл опроса — часть экрана S7. Строить их сейчас значило бы построить механизм без задачи, что проектный норматив прямо запрещает, и выбросить его при первом же настоящем экране. Контракт обе поверхности объявляет, генерённые типы для них есть — недостающего кода ровно на десяток строк, когда появится, кому их звать | S4 (загрузка) · 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` |
| Ф-29 | **Мок-сеть едет в продакшн-бандл.** `startMocking()` подтягивает `src/mock/` динамическим импортом, но скриншот-цикл гоняет `vite build` + `preview`, то есть продакшн-режим, и без мока там нечего показывать. Пока платформы на том же origin нет, это цена работоспособности; в день, когда фронт заговорит с настоящей платформой, папка `mock/` удаляется целиком и импорт уходит вместе с ней — ровно так, как обещал шов. Строка нужна, чтобы это удаление кто-то сделал ОСОЗНАННО, а не обнаружил мок в бою | триггер: живая платформа на том же origin | S3 08.08 |
| Ф-30 | **Метка главы без заголовка — голый порядковый номер.** Контракт запретил синтез «Глава {n}» (К-3: метка из ДАННЫХ книги, легальна книга без номеров и глав вовсе), и фикстуры содержат такие главы намеренно. Когда у главы нет ни `heading`, ни `number`, в дереве остаётся позиция в порядке чтения — на кадре это строка «12» среди названий. Врать словом фронт не стал: словарь продуктовый, и это ровно вопрос В-4, который уже стоит владельцу. Строка держит хвост до его ответа | решение владельца (В-4) | S3 08.08, кадр `/scale` |
| Ф-31 | **К-13 — новый вопрос контракта, заведённый этой сессией.** Достижение СОБСТВЕННОГО потолка прогона (`ceiling_chapters`) и исчерпание кредита аккаунта сегодня неразличимы: `paused_reason` имеет ровно одно значение, `credit_exhausted`, и оно про кредит. Механически они могут совпасть — платформа берёт холд на сумму потолка до спавна, — но человек, упершийся в свой колпак, и человек, у которого кончились деньги, находятся в разных положениях, и фраза у них разная. Передано оркестратору в `S3_CONTRACT_HANDOFF.md` §4; в спеке помечено в `EventCeiling` | владелец + платформа | S3 08.08 |
| Ф-32 | **Замечания читаются пер-КНИЖНО, а показываются пер-главно.** Контракт даёт `GET /books/{id}/notes` и не даёт пер-главной ручки; панель контекста фильтрует по открытой главе на клиенте. Пока это дёшево — дефолт страницы 500, а на книге с сотнями замечаний клиент тянет весь список ради трёх строк, и с курсором дотянет ВЕСЬ. Ф-12 обосновывал отказ от виртуализации замечаний тем, что список пер-главный и мал; после этой правки он пер-главный на ЭКРАНЕ, но пер-книжный в ПАМЯТИ. Решать не фронту: либо пер-главная ручка (вопрос автору контракта), либо явный потолок с честным «показаны не все». Обнаружено вторым адверсариальным проходом 08.08 при починке того, что панель «О читаемой главе» показывала замечания всей книги | S6 (читалка) либо раньше, если появится книга с тысячами замечаний | второй проход ревью S3 |
| Ф-5 | **Tauri 2.x вторым шагом** — по явным триггерам (трей · глобальные горячие клавиши · распространяемый .exe · офлайн · хранилище учёток ОС); до того — установимое PWA, только браузерные API | триггеры названы | STACK_DECISIONS §6, D39.84 |
| Ф-6 | **`@tanstack/react-virtual`** — в резерве, подключать только по замеру (дефолт виртуализации — RAC Virtualizer). **Замер сделан 04.08 (S2) и триггер НЕ наступил:** RAC `Virtualizer` держит 2284 раздела на 31 строке в DOM при медиане кадра 22 мс и 1200 терминов на 30 строках при 16,7 мс. Вторая библиотека виртуализации сейчас была бы платой без выигрыша; пересматривать — если появится список с переменной высотой строки (читалка S6) и `estimatedRowSize` не справится | по замеру; замер 04.08 отрицательный | STACK_DECISIONS §2 |