# Бэклог фронта (зонный) > Ведёт зона `frontend/` (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог `docs/PROGRESS.md` — трекер движка/полигона/доков, фронт-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99–102 — пофазный прогресс · манифест глав · машиночитаемая таблица подписи · трасса). Засеян оркестратором при лендинге D39.84 — дальше правят фронт-сессии. | ID | Хвост | Вес | Источник | |---|---|---|---| | Ф-1 | **Этапы S2–S7** (`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'; `) — синтаксически это не берётся, и объявлять строку «закрытой без остатка» было бы ровно тем переобещанием, из-за которого она и заведена | закрыт для литеральных форм, граница названа | адверсариальное ревью 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 на 5 узлах** (приглушённый текст `#707479` и `#8a8e91` на `#17191a`). Это прямое следствие копирования палитры 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)** — собрать одним списком то, что фронт уже знает и обязан подать автору контракта: язык кодами (не именами) · состояние прогона отдельно от выполнения главы · прогресс пофазно по ЮНИТАМ · ревизия/`Last-Event-ID` для докачки SSE · трёхзначный статус подписи термина (`auto|draft|approved`), а не булев `signed` · поля банка, без которых подпись не работает (`type`/`sense`/`since_ch`) · **ключ строки банка** (ревью оркестратора 04.08): у движка термин уникален по (`src`, `sense`, `since_ch`, `until_ch`), то есть один и тот же `src` законно приходит несколькими строками, а список на экране требует различимого ключа — сейчас он собран из (`src`, `dst`, `type`) и это заплата, а не решение. **Своим решением фронт это не заводит** (`BACKLOG.md` шапка: запросы к движку идут строками единого бэклога через оркестратора) — строка держит список готовым к дизайн-промту 95 | к дизайн-промту 95 (до S3) | ревью S1 №3 | | Ф-15 | **`src/api/` синхронен, состояний загрузки и ошибки в типах нет** — диспозиция: НЕ чинить сейчас. Два независимых скептика показали, что S2 данных не читает вовсе, а асинхронность приезжает на S3 вместе с MSW и TanStack Query, до первого продуктового экрана; самодельный хук сегодня = работа S3, которую S3 выбросит. Строка держит обязательство S3: ветки ожидания/ошибки/пустоты появляются вместе с MSW, а не после первого экрана | S3 | ревью 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 узлов стоит 70–140 мс (два независимых замера). Ф-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 | | Ф-21 | **Ступень замечания различается только цветом полоски** (`attention` синяя, `glance` серая). Зрячему разница читается ещё и светлотой, но скринридеру приоритет не достаётся вовсе. Лечение — слово, как это уже сделано для состояния прогона книги; **какое именно слово, решает владелец**: словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, так что «важно/неважно» не годится | решение владельца | адверсариальное ревью S2 | | Ф-18 | **Бандл 545 КБ (168 КБ gzip) одним куском** — замер 04.08 после установки `react-aria-components`; Rolldown предупреждает про порог 500 КБ. Для десктопного SPA за логином это не авария (индексации нет, сеть локальная), но одним куском едут и экраны, которых пользователь ещё не открыл. Лечение — разбиение по маршрутам, и делать его осмысленно после S4–S7, когда экраны появятся: сейчас резать нечего | после S7 либо при жалобе на первый экран | S2, замер | | Ф-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 |