textmachine/frontend/docs/BACKLOG.md

43 KiB
Raw Blame History

Бэклог фронта (зонный)

Ведёт зона 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, перечень с обоснованием на каждый пункт, каждое обоснование грунтовано 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 (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.0paused, eta_seconds, метка главы из данных, пер-книжная ревизия с правилом >= R и обязательным resync_required после полной замены, keyset-курсор на ВСЕХ списках, X-TM-Client, Run.paused_reason, GET /usage, GET /books/{id}/run-options с потолком прогона в ГЛАВАХ, same-origin. Байт-сверка с каноном намеренно даёт расхождение: канон правит оркестратор, диффом. Открытых вопросов стало семь (закрыты К-1/К-3/К-4/К-5/К-7/К-8/К-12, добавлен К-13 — Ф-31). Что переносить в канон — канон docs/architecture/14-api-contract/ (патч ПРИМЕНЁН и удалён, D39.115) правки внесены · ждут пере-ратификации диффом ревью S1 №3 · сборка S3 04.08 · дофикс ревью 04.08 · подрезка 04.08
Ф-15 src/api/ синхронен, состояний загрузки и ошибки в типах нетЗАКРЫТА 08.08 в S3. src/api/ асинхронен и разговаривает HTTP: MSW как СЕТЕВОЙ мок (а не подменённый модуль), поэтому приложение делает настоящий fetch и настоящий EventSource, и ветки ожидания/ошибки/пустоты — это код, который работает в бою, а не второй его экземпляр. Ветки живут в одном месте (showcase/Loaded.tsx) и приезжают ВМЕСТЕ со слоем данных, до первого продуктового экрана, как строка и требовала. Типы сведены с контрактом: types.ts удалён, экраны живут на суженных типах api/contract.ts. Что нашёл сам скриншот-цикл, а не рассуждение: выключенный запрос TanStack Query остаётся pending НАВСЕГДА, и на пустой библиотеке правая панель вечно показывала «Идёт загрузка» — различение «ждём» и «выключено» (fetchStatus === 'idle') добавлено после того, как это увидели на кадре. Фикстуры всех состояний — шесть миров, по маршруту на каждый закрыт ревью S1 №3
Ф-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:276if (!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-coreopenapi-typescript, js-yaml через cosmiconfigstylelint, nanoid через postcssstylelint. К новому пину S3 отношения не имеют — проверено npm ls, @tanstack/react-query ни одну из них не тянет; это адвайзори, опубликованные после 04.08, когда журнал фиксировал «0 уязвимостей». В браузер не едет ничего из перечисленного. Пины ратифицированы (STACK_DECISIONS §3), и двигать их посреди сессии, которая на этих же инструментах держит гейт, было бы разменом безопасности сборки на риск красного дерева у всех следующих сессий — поэтому измерено и передано, а не пропатчено молча решение оркестратора/владельца: бампнуть пины отдельным проходом S3 08.08, npm audit
Ф-29 Мок-сеть едет в продакшн-бандл. startMocking() подтягивает src/mock/ динамическим импортом, но скриншот-цикл гоняет vite build + preview, то есть продакшн-режим, и без мока там нечего показывать. Пока платформы на том же origin нет, это цена работоспособности; в день, когда фронт заговорит с настоящей платформой, папка mock/ удаляется целиком и импорт уходит вместе с ней — ровно так, как обещал шов. Строка нужна, чтобы это удаление кто-то сделал ОСОЗНАННО, а не обнаружил мок в бою триггер: живая платформа на том же origin S3 08.08
Ф-30 Метка главы без заголовка — голый порядковый номер. Контракт запретил синтез «Глава {n}» (К-3: метка из ДАННЫХ книги, легальна книга без номеров и глав вовсе), и фикстуры содержат такие главы намеренно. Когда у главы нет ни heading, ни number, в дереве остаётся позиция в порядке чтения — на кадре это строка «12» среди названий. Врать словом фронт не стал: словарь продуктовый, и это ровно вопрос В-4, который уже стоит владельцу. Строка держит хвост до его ответа решение владельца (В-4) S3 08.08, кадр /scale
Ф-31 К-13 — новый вопрос контракта, заведённый этой сессией. Достижение СОБСТВЕННОГО потолка прогона (ceiling_chapters) и исчерпание кредита аккаунта сегодня неразличимы: paused_reason имеет ровно одно значение, credit_exhausted, и оно про кредит. Механически они могут совпасть — платформа берёт холд на сумму потолка до спавна, — но человек, упершийся в свой колпак, и человек, у которого кончились деньги, находятся в разных положениях, и фраза у них разная. Передано оркестратору в канон docs/architecture/14-api-contract/ (патч применён и удалён, D39.115) §4; в спеке помечено в EventCeiling владелец + платформа S3 08.08
Ф-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