textmachine/frontend/docs/BACKLOG.md

73 KiB
Raw Blame History

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

Ведёт зона frontend/ (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог docs/PROGRESS.md — трекер движка/полигона/доков, фронт-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99102 — пофазный прогресс · манифест глав · машиночитаемая таблица подписи · трасса; все четыре ЗАКРЫТЫ, тела в D39.122/D39.131, а сама таблица подписи с тех пор снесена D39.158). Развод «предложить/ратифицировать» (D39.99 п.4, после инцидента остановки S3): ПРЕДЛАГАТЬ артефакты для чужих решений — черновики контрактов, входы дизайна — фронт не только может, но обязан; не может он их РАТИФИЦИРОВАТЬ. «Не заводит своим решением» значит «финальное слово не его», а не «не смей писать черновик». Засеян оркестратором при лендинге D39.84 — дальше правят фронт-сессии.

ID Хвост Вес Источник
Ф-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
Ф-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
Ф-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. Граница названа честно: гейт ловит литерал и шаблон, но НЕ вычисленное значение (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 узлах на маршрутЗАКРЫТА 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 строки. Число не из вкуса: главу адресуют порядковым номером, и сотня — шаг, которым их считает человек; замер говорит то же с другой стороны — плоское дерево рисуется дёшево, а полоса прокрутки на 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_DRAFT.mdоба сегодня под ⚠-баннерами «исполнено, канон живёт не здесь»), контракт РАТИФИЦИРОВАН оркестратором (D39.99) и с тех пор ушёл на 0.9.0. Где что живёт сегодня: норматив — docs/architecture/14-api-contract/openapi.yaml; провенанс ✓/◆/○, обоснования и К-вопросы — компаньон README.md рядом с ним; ход сборки, оба пасса ревью и дофиксы — archive/frontend-PROGRESS-S0-S4.md (записи 04.08 и 08.08). Урок, который строка держит живым: класс ошибки «фикстура выдумала форму» ловился в зоне ЧЕТЫРЕЖДЫ — форму берут из канона контракта, а не из движка и не из головы закрыт (D39.99) ревью S1 №3 · сборка S3 04.08
Ф-15 src/api/ синхронен, состояний загрузки и ошибки в типах нетЗАКРЫТА 08.08 в S3. src/api/ асинхронен и разговаривает HTTP: MSW стоит СЕТЕВЫМ моком (а не подменённым модулем), поэтому приложение делает настоящий fetch и настоящий EventSource, и ветки ожидания/ошибки/пустоты — код, работающий в бою, а не второй его экземпляр. types.ts удалён, экраны живут на суженных типах api/contract.ts; фикстуры всех состояний — шесть миров, по маршруту на каждый. Ловушка библиотеки: выключенный запрос TanStack Query остаётся pending НАВСЕГДА — «ждём» от «выключено» отличает fetchStatus === 'idle' закрыт ревью S1 №3
Ф-13 eslint-plugin-jsx-a11y не встаёт на ESLint 10 (peer ^3..^9, проверено --dry-run → ERESOLVE). Статическую половину доступности держит axe в скриншот-цикле; плагин подключить, когда выйдет совместимый мажор триггер: мажор плагина ревью S1 №2
Ф-7 Хвосты сведения витрины с референсом (FRONTEND_PLAN.md §5.2). Часть S2 закрыта 04.08: + стоит в ряду вкладок ЛЕВОЙ панели и запускает «Добавить книгу», дублирующая строка из дерева убрана; в центре и справа + нет и не будет (решение владельца 04.08 — журнал, «Решения владельца по продукту»). Состояния наведения сняты прогоном браузера (data-hovered/data-selected/data-focus-visible у кнопки, вкладки, строки дерева и списка), а не статичным кадром. Осталось на S6: приглушённая колонка оригинала — это решение, а не замер, пересмотреть на настоящей читалке S2 · S6 S1, сверка с fleet.png
Ф-16 Выгрузка книги (§3.11) ушла из верхней полосы. В витрине S1 там висела иконка загрузки без действия; по правилу Ф-7 («кнопка только вместе с действием») она снята до S7, который строит экспорт. Вернуть иконку вместе с самим действием S7 S2
Ф-17 Закрытие вкладки с клавиатуры не объявлено скринридеру. Крестик у вкладки-документа сознательно НЕ кнопка: у роли tab дети презентационные (ARIA), вложенный фокусируемый элемент выпал бы из дерева доступности, оставшись в порядке табуляции (это же ловит axe правилом nested-interactive). Мышь закрывает крестиком, клавиатура — Delete на выбранной вкладке. Объявить сочетание нечем: aria-keyshortcuts не входит в типизированный API Tab/TabList в react-aria-components 1.20.0. Пересмотреть при обновлении библиотеки либо при появлении палитры команд триггер: мажор RAC или экран горячих клавиш S2
Ф-19 Переключение вкладки размонтирует её содержимое, и прокрутка длинного списка теряется.ЗАКРЫТА 09.08 в S3.5, и механизмом: проп 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
Ф-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 серая). Зрячему разница читается ещё и светлотой, но скринридеру приоритет не достаётся вовсе. Лечение — слово, как это уже сделано для состояния прогона книги. ⚠ Диспозиция сменилась 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
Ф-23 openapi-typescript держится на точечном overrides, а не на честном пире. Генератор объявляет typescript@^5.x (7.13.0 вышла 11.02.2026, до релиза TS 6), у нас намеренный TS 6 — Ф-3. --legacy-peer-deps отвергнут проверкой исполнением: после него обычный npm install падает, то есть дерево ломается у каждой следующей сессии и у prepare-инсталлера pre-commit хука. Взят overrides на ОДИН пакет ({"openapi-typescript":{"typescript":"$typescript"}}) вместо глобального ослабления проверки пиров; работоспособность доказана: npm ci с нуля зелёный (0 уязвимостей), генератор запускается на TS 6, тест дрифта проходит. Снять, когда генератор расширит пир либо когда приедет TS 7.1 и Ф-3 закроется триггер: пир генератора расширен ИЛИ TS 7.1 (Ф-3) S3, дофикс контракта 04.08
Ф-24 AsyncAPI рассмотрен и отложен. Он описал бы SSE-канал строже, чем нынешняя таблица «имя события → схема» в описании EventEnvelope. Отложен, потому что это второй нормативный артефакт и второй линтер ради ОДНОГО эндпоинта, при том что полезные нагрузки уже описаны схемами OpenAPI и попадают в генерённые типы. Пересмотреть, когда каналов станет больше одного либо когда понадобится генерировать клиент потока, а не разбирать кадры руками триггер: второй канал ИЛИ генерация клиента потока S3, дофикс контракта 04.08
Ф-25 src/ смешанный по языку комментариев.ЗАКРЫТА 10.08 в S3.7, и шире, чем строка просила. Правило владельца ужесточено до «в КОДЕ никакого русского»: комментарии, имена тестов, сообщения ошибок, логи скриптов и служебные строки сценариев — английские, по ВСЕМУ дереву зоны (54 файла, включая корневые конфиги), а не по списку тронутых сессией. Держит это не дисциплина, а гейт: src/i18n/catalogue.test.ts подметает кириллицу построчно по ts/tsx/css/mjs/py (идентификаторы вида Ф-49 исключены явно — это имена строк бэклога, а не проза). Граница машинная: русский легален ровно в двух местах, и оба — данные (каталог src/i18n/ru.ts и фикстуры src/mock/) закрыт слово владельца 04.08 → S3.7 10.08
Ф-26 Загрузка книги ПОСТРОЕНА (S4), выгрузка ждёт S7. POST /books живёт в src/api/upload.ts — XHR, а не fetch, потому что стандарт Fetch не отдаёт прогресс ТЕЛА запроса вовсе (поток в запросе — только Chromium и только HTTP/2), а книга это минуты тишины. Порядок частей формы — правило провода (PD-172, file последним) и держится тестом; отказы 400/404/408/413 — состояния формы. Экспорт (POST/GET /books/{id}/exports) не строился намеренно: цикл опроса по Retry-After — часть экрана S7, а механизм без задачи проектный норматив прямо запрещает; контракт обе поверхности объявляет, типы сгенерены, недостающего кода ровно на десяток строк S7 (экспорт) S3 08.08 → S4 14.08
Ф-27 GET /usage читается слоем данных, но ни один экран его не показывает. Так и задумано: страница статуса использования живёт в настройках (ПТ-35/П-5), а настройки строит S7. Функция и её тест существуют, чтобы поверхность контракта была покрыта целиком и чтобы S7 не пришлось трогать шов; если S7 сдвинется далеко, строку пересмотреть — непрочитанный код тоже стоит денег S7 S3 08.08
Ф-28 Три high-уязвимости в дереве, все транзитивные и все в инструментах сборки (замер 08.08): js-yaml через @redocly/openapi-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 Метка главы без заголовка — голый порядковый номер.Прежнее основание строки СНЯТО каноном 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.codeceiling_reached («прогон закончен, нужен новый») против credit_unavailable («пополнить счёт») закрыт S3 08.08 → D39.132
Ф-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
Ф-6 @tanstack/react-virtual — в резерве, подключать только по замеру (дефолт виртуализации — RAC Virtualizer). Замер сделан 04.08 (S2) и триггер НЕ наступил: RAC Virtualizer держит и дерево, и банк на настоящем масштабе (числа — Ф-12), то есть вторая библиотека виртуализации была бы платой без выигрыша; пересматривать — если появится список с переменной высотой строки (читалка S6) и estimatedRowSize не справится по замеру; замер 04.08 отрицательный STACK_DECISIONS §2
Ф-33 Поиска по ТЕКСТУ книги в контракте нет вовсе. Замечание владельца 4 («две кнопки поиска, делают одно и то же») разведено так: лупа в правом верхнем углу стала настоящей палитрой быстрого перехода к разделу или термину, а вкладка-заглушка «Поиск» из левой панели СНЯТА. Снята потому, что построить её нечем: у контракта ни одной поисковой ручки (сверено по канону 0.9.0 02.09: 20 операций, GET /books/{id}/… даёт списки, не запросы; греп operationId: по docs/architecture/14-api-contract/openapi.yaml — ни одной поисковой), то есть вкладка была обещанием без адресата. Вопрос не фронта: полнотекстовый поиск по книге — это либо ручка платформы, либо индекс на клиенте поверх выгрузки, и то и другое решает автор контракта. До ответа поиск по книге живёт как хвост читалки S6 либо ручка контракта S3.5, замечание 4
Ф-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 по живому 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
Ф-37 Пометка черновика висит на статусе КНИГИ, а не главы. Замечание 24 сделано данными контракта: пока прогон не довёл книгу до ready, текст помечен как черновой. Но у главы есть своё выполнение (units_done/units_total), и глава, уже прошедшая обе волны в незавершённой книге, получает ту же пометку, что и непереведённая. ⚠ Открытый вопрос К-10, на который строка ждала ответа, ОТВЕЧЕН «НЕ строить» (D39.138): фазы уходят с провода, и у главы тоже — пометка черновика остаётся КНИЖНОЙ. Хвост строки пере-диспозиционировать при разморозке: безопасная сторона (предупредить лишний раз) осталась взятой намеренно пере-диспозиция при разморозке S3.5, замечание 24 → D39.138
Ф-38 Вкладка «Замечания» защищена сводкой по книге — слово за владельцем. Замечание 8 («непонятно зачем, всё рисуется в тексте») по пер-ГЛАВНОМУ списку было верным: он дублировал читалку. Вкладка переделана в пер-КНИЖНУЮ сводку со ссылкой в раздел — сценарий «пройтись по всем местам, требующим внимания», который без неё не живёт и который не стоит ни одного лишнего запроса (контракт даёт замечания пер-книжно, Ф-32). Если владелец сценария не признает — вкладка снимается одной строкой в Context.tsx, вместе с ней уходит и notesQuery с экрана решение владельца S3.5, замечание 8
Ф-39 Банку не хватает полей для проверки термина, и выдумывать их фронт не стал (замечания 7/13 просили род/пол, описание-обоснование, варианты перевода; в BankTerm их нет) — ЗАКРЫТА словом владельца 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
Ф-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 (закреплённая вкладка молча теряет содержимое — новый класс сюрприза), а порог всё равно называть по замеру на НАСТОЯЩЕЙ книге, которого у фикстуры нет. Пересмотр — при первой жалобе на память или когда прогон пойдёт на живой платформе замерено, отложено с числами
Ф-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). Счётчики «подписано N / ждут решения N» сняты с экрана словом владельца 10.08 — они и были путаницей «подписываем банк или термины»; в подвале остались размер банка, «показано N из M» при сужении и состояние ВСЕГО банка из статуса книги. Экран действия — при разморозке S5 S3.6 10.08 · пере-читано по D39.144
Ф-45 Править термин в таблице нельзя — только читать. Владелец: «разумеется в банке можно подправить какой то термин». Контракт это выражает (BankDecision: promote с непустым dst либо decline, частичное накопление на сервере), то есть редактирование строки — это отправка решения, а не PATCH. Экран решений = S5; в S3.6 таблица читающая, и это названо честно, а не замаскировано неактивными полями S5 S3.6 10.08, замечание 7
Ф-46 У таблицы банка нет сортировки, и она не различает канон от предложенного. Первое: сортировки по столбцу нет — примитив ui/Table.tsx на RAC Table её допускает (sortDescriptor), но проектировать навигацию по банку на 60 фикстурных терминах нельзя. Второе, найдено при снятии столбца подписи (10.08): в банке лежат и уже канонические термины, и свежепредложенные — подписывают, по сути, вторые, а таблица их не различает. Возвращать это как «состояние подписи строки» нельзя (владелец снял его прямо), значит нужен другой различитель, и он продуктовый. Сегодня путей два: поиск по трём полям и фильтр по типу со счётчиками. Оба вопроса упираются в настоящий банк книги (Ф-43) и в слово владельца после Ф-43 + владелец S3.6 10.08, замечание 7
Ф-47 Дефект контракта: sense не обязателен, хотя объявлен частью ключа уникальности.ЗАКРЫТА 10.08 в S3.7 по ратификации оркестратора: в зонной копии openapi.yaml поле в required, правило пустой строки («различителя нет») записано в описании, версия 0.2.1 → 0.2.2, spectral зелёный, типы перегенерены (счёт прежней редакции строки был завышен: ?? '' стояло в одном месте, а не в трёх) закрыт S3.6 10.08 → S3.7 10.08
Ф-48 Перетаскивание границ столбцов снято по замеру, а не по вкусу. Отраслевая норма таблиц данных (MUI X flex+minWidth+resize, AG Grid) — колонки тянут рукой; в react-aria это ResizableTableContainer + ColumnResizer, и оно работает (проверено сценарием: столбец растянут 194→274px). Но A/B-замер драга ПАНЕЛИ с открытым банком на dev-сервере: без ресайза p90 33 мс и 7 длинных кадров, с ресайзом — p90 83 мс и 44 длинных кадра: контейнер пересчитывает ширины на каждый кадр изменения своей ширины. Владелец дважды жаловался именно на драг, поэтому выбрана плавность: остались текучие доли (fr) с минимумами на столбец. Возвращать — когда банк станет поверхностью РЕДАКТИРОВАНИЯ (S5, там ширины важнее) либо когда у библиотеки появится способ не пересчитывать ширины на ресайз контейнера. ⚠ База замера УСТАРЕЛА (10.08, четвёртый круг): «без ресайза p90 33 мс» на текущем дереве не воспроизводится — драг держит p90 17 мс, 1 длинный кадр из 84, и A/B по блюру и contain числа не двигает. Значит и цену ресайза (83 мс) надо мерить заново от новой базы, а не наследовать: возвращая ресайз, начинать с замера, а не со ссылки на эту строку S5 либо новая версия библиотеки S3.6 10.08, A/B-замер; база пере-замерена 10.08
Ф-49 Текст открытой главы не подписан ни на один сигнал изменения.ЗАКРЫТА 10.08 в S3.7. Кадр chapter инвалидирует чтение юнитов ИМЕННО названной главы (keys.units), кадр status — юниты всей книги (keys.bookUnits): границы стадий это единственный сигнал, пока units_done остаётся единым счётчиком. Тест — showcase/useRunStream.test.tsx закрыт S3.6 10.08 → S3.7
Ф-50 Кадр потока, пришедший до появления чтения в кэше, теряется молча.ЗАКРЫТА 10.08 в S3.7, обе половины. (1) Кадр, не нашедший снимка, зовёт resetQueries по своему ключу — именно reset, и это ЗАМЕР, а не вкус: invalidateQueries на запросе без данных не делает ничего (Query.fetch: state.data !== undefined && cancelRefetch) и возвращает промис того самого устаревшего чтения. (2) Ревизия многостраничного списка — МИНИМУМ из страниц (client.ts): рваный список свеж настолько, насколько стара его первая страница закрыт S3.6 10.08 → S3.7
Ф-51 resync сбрасывает один гард из двух.ЗАКРЫТА 10.08 в S3.7. На resync_required зовётся resetQueries по скоупу КНИГИ (bookScope — предикат по второму элементу ключа: id книги стоит там у каждого книжного чтения и больше нигде), то есть отметка ревизии уходит вместе со снимком, а библиотека, usage и другие книги не трогаются. Гарантии закрыты тестами: «новая эпоха с НИЗШИМ номером принимается» и «resync одной книги не трогает чужие скоупы» закрыт S3.6 10.08 → S3.7
Ф-52 --color-danger не дотягивает до порога нетекстаЗАКРЫТА 14.08 в S4 по слову владельца, и не одним числом, а ПРАВИЛОМ. Тон поднят минимальным сдвигом при сохранённых тоне и насыщенности (350°, S 0.60): #b82e45#bd2f47, на полотне панели #181818 это 3.10:1 против прежних 2.97 (порог нетекста 3:1; для сравнения --color-note стоит на 3.07). Токен переехал из «замеренных» в «подобранные по референсу» с числом референса в комментарии. Сверх задания поставлен гейт: tokens.test.ts считает контраст КАЖДОГО тона состояния (note/ok/warn/danger) о полотно и требует 3:1 — axe этого не ловит вовсе (контраст нетекста он не проверяет), и обе просадки нашлись руками, с разницей в пакет. Проверено живым нарушением. ⚠ Порог НЕтекста — потолок этих тонов: тона состояния не проходят порог для ТЕКСТА, поэтому цвет живёт на точке, а смысл несёт слово (замер 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, замер в браузере
Ф-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
Ф-56 Конца интейка никто не объявляет — клиент опрашивает. В контракте 0.2.3 живой канал принадлежал ПРОГОНУ (GET /runs/{runId}/events), а книга в uploading/parsing прогона не имеет вовсе: «разбор закончился» не приходило ничем. S4 закрыла дыру опросом (src/api/queries.ts, intakePollMs = 3000, только пока книга в интейке) плюс перечитыванием дерева на выходе (useIntakeEnd.ts). Цена опроса названа: чтение библиотеки раз в три секунды на всё время разбора — минуты на настоящей книге. ⚠ ОТВЕТ ПРИШЁЛ (Б-6а, канон 0.3.0+): поток перевешен на КНИГУ — GET /books/{bookId}/events с кадрами конца разбора и конца прогона и 204 на завершённом. Опрос снимается при разморозке разморозка: снять опрос, перейти на книжный поток S4 14.08 → канон
Ф-57 Какие пары языков продукт принимает — форма загрузки предлагает свой список. src/showcase/languages.ts держит zh·ja·en → ru как ДАННЫЕ клиента, имена считает Intl.DisplayNames. Это честная слабость интейка: что деплой умеет переводить — свойство деплоя (движок читает пару из langpack), и список на клиенте соврёт в тот день, когда деплой поедет с другим набором. ⚠ ОТВЕТ ПРИШЁЛ (Б-2, канон 0.3.0+): GET /capabilities отдаёт пары со статусом, порог интейка, форматы, размеры страниц и версию контракта — languages.ts при разморозке становится ЧТЕНИЕМ, а не константой разморозка: languages.ts на /capabilities S4 14.08 → канон
Ф-58 RTL не проверен ни разу, а триггер наступил. Условие, записанное S1 («когда появится I18nProvider»), исполнилось в S3.7: провайдер стоит, локаль берётся из стора, <html lang> следует выбору. Чего нет — ни одного прогона с языком письма справа налево: примитивы react-aria логическое направление поддерживают, а НАША раскладка (фиксированное место бейджа, обрезка вкладок, инсеты панелей, стрелка zh → ru в статус-полосе) написана в физических терминах местами. Цена вопроса неизвестна до замера; сам замер дешёвый — второй каталог-заглушка плюс dir="rtl" и прогон сцен. Делать, когда владелец назовёт второй язык (В-9): до того это проверка обещания, которого продукт не давал владелец (В-9) → отдельный проход аудит-свип 14.08 → S4
Ф-59 Спойлер банка достаётся поиском по странице — ЗАМЕРЕНО.ЗАКРЫТА решением владельца 16.08 (D39.138 п.2к): спойлер это ВЕЖЛИВОСТЬ, сделанного достаточно, цена названа. Сам замер (S4, браузер, /showcase, вкладка «Банк»): размытие глифов (filter: blur) прячет описание от глаза, но текст остаётся текстом страницы — закрытое описание лежит в document.body.innerText, window.find() его находит, то есть Ctrl+F подсвечивает закрытую ячейку; скринридер отрезан честно (aria-hidden), выделение мышью пусто, под @media print размытие СОХРАНЯЕТСЯ (blur(3px)), но текстовый слой PDF строится из DOM, и там строка остаётся закрыт замер S4 14.08 → D39.138
Ф-60 Канвас-замер ширин не знает языка строки. Ширины столбцов банка меряются канвасом одним шрифтом на весь набор (ui/Table.tsx), а при ДВУХ CJK-лицах в стеке один и тот же код-пойнт рисуется по-разному: японская и китайская гарнитуры дают разную метрику для общего иероглифа. Сегодня это спит: пара одна (zh→ru), лицо одно. Сработает в день, когда в наборе окажутся и zh, и ja — то есть ровно на второй паре, которую движок уже умеет. Лечение: мерить с тем же lang, что у ячейки (канвасу lang не передашь — придётся мерить в DOM либо держать по замерщику на язык), и это отдельный проход с пере-замером цены триггер: вторая пара в одной библиотеке аудит-свип 14.08 → S4
Ф-61 Платформа отвечает по-английски, а интерфейс русский. Контракт 0.2.3 клал фразу отказа на СЕРВЕР (Problem.title/detail — «product language», клиент показывает как пришло), и живая платформа слала "The file is larger than this service accepts" рядом с русским советом — два языка на одном экране. ⚠ РЕШЕНО владельцем, вариант B (D39.138 §8 п.4; требование интернациональности подтверждено D39.176 п.4): на проводе машинный code + request_id, title/detail developer-facing, а ФРАЗУ рисует клиент по словарю кодов и локали — таблица AddBook.tsx (греп по предмету) становится нормой, а фолбэки AddBook.tsx (греп по предмету) (problem?.detail ?? problem?.title), Loaded.tsx (греп по предмету) и RunStart.tsx (греп по предмету) при разморозке СНИМАЮТСЯ. Карта «код → фраза» — приложение А компаньона контракта разморозка: словарь кодов вместо серверной фразы S4 14.08 → D39.138
Ф-62 Переименовать книгу нечем. В 0.2.3 POST /books клал название один раз (рукой или из имени файла), дальше только чтения. Найдено адверсариальным ревью S4: подсказка формы обещала «название можно будет поправить позже» — обещание, которого контракт не давал; фраза исправлена, обещание снято. ⚠ ОТВЕТ ПРИШЁЛ (Б-5, канон 0.3.0+): PATCH /books/{bookId} ТОЛЬКО с title (409 при живом прогоне) и DELETE; правка — отображение, до движкового брифа не доезжает. Плюс слово владельца 16.08 (Б-23): жанр из продукта УХОДИТ совсем — поле формы снять при разморозке разморозка: правка названия и удаление на живых ручках S4 14.08 → канон
Ф-63 Рамка поля ввода на модальной поверхности — 1.20:1, ниже порога 1.4.11. Замер S4 (найдено адверсариальным ревью на новом чекбоксе, у которого рамка — единственный носитель границы; там починено локально на --color-text-muted, 3.29:1): --color-border #2e2e2e о --color-elevated #202020 даёт 1.20:1 при пороге 3:1 для границ элементов управления. Тот же токен несёт рамку ВСЕХ полей формы (ui/Field.module.css, модификатор .framed), рамку самого модального окна и разделители секций, поэтому одним значением это не чинится: поднять токен — перекрасить всю оболочку, принятую владельцем по референсу (S3.6). Правильный ход — отдельный токен границы для того, что является КОНТРОЛОМ (поле, чекбокс), и он у него уже есть в частном виде. Отложено сознательно: перекраска рамок — это вид, а вид принимает владелец владелец + отдельный проход S4 14.08, адверсариальное ревью