textmachine/frontend/docs/BACKLOG.md

36 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 04.08 — axe печатает по 4 узла и на /showcase, и на /scale, а записанные здесь «5» устарели после фикс-пака S2. Это прямое следствие копирования палитры 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 · ветка неизвестного добита до шести словарей. Открытых вопросов двенадцать спека + спутник готовы · ждут ратификации ревью S1 №3 · сборка S3 04.08 · дофикс ревью 04.08 · подрезка 04.08
Ф-15 src/api/ синхронен, состояний загрузки и ошибки в типах нет — диспозиция: НЕ чинить сейчас. Два независимых скептика показали, что S2 данных не читает вовсе, а асинхронность приезжает на S3 вместе с MSW и TanStack Query, до первого продуктового экрана; самодельный хук сегодня = работа S3, которую S3 выбросит. Строка держит обязательство S3: ветки ожидания/ошибки/пустоты появляются вместе с MSW, а не после первого экрана. 04.08 (S3): замок проверен исполнением — docs/architecture/14-api-contract.md не существует, строка 95 единого бэклога всё ещё «дизайн-промт по слову владельца». Строка НЕ закрыта и переносится в сессию, которая застанет замок открытым; чинить «пока на своих типах» запрещено промтом S3 по той же причине, по которой заведена строка 95 Дофикс 04.08 продвинул строку, но НЕ закрыл: типы контракта теперь генерятся из спеки (src/api/schema.ts + дружественные псевдонимы src/api/contract.ts), ветка неизвестного значения стоит на шве, MSW отдаёт одно тело как proof-of-form. Экраны при этом по-прежнему живут на src/api/types.ts — рабочей гипотезе до контракта: перевод экранов на генерённые типы и удаление types.ts это и есть S3, и делать его до РАТИФИКАЦИИ спеки нельзя по той же причине, по какой заведена строка 95. Ветки ожидания/ошибки/пустоты и фикстуры всех состояний тоже ждут S3 S3, вход закрыт ратификацией контракта (перепроверено 04.08) ревью 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 У закрытых юнионов нет ветки неизвестного значения, и тайпчек здесь не страхует. runStates[book.state] (showcase/Library.tsx:103) — обращение к Record по КОНЕЧНОМУ юниону, а noUncheckedIndexedAccess добавляет undefined только индексным сигнатурам: проверено пробой tsc — обращение по юниону компилируется молча, ругается лишь Record<string, …>. Значение вне юниона (новое состояние прогона из контракта API) даёт undefined.tone, то есть падение дерева библиотеки, а не пустую метку; тем же классом noteTone тихо уводит незнакомую ступень замечания в спокойную ветку. Лечение — ветка неизвестного значения НА ШВЕ src/api/, где значение входит, а не в каждом компоненте. В S2 находку отвели скептики формулировкой «это работа S3» — она и есть работа S3, но хвост записан здесь, чтобы не жить только в промте. 04.08 (S3): не сделано — вход закрыт вместе с Ф-15. Разбор при сборке Ф-14 добавил к строке то, чего в ней не было: поведение ветки (показать нейтрально ↔ отвергнуть ответ) зависит от того, минор это или мажор по version-хендшейку, а это знание КОНТРАКТА — значит ветку нельзя написать раньше него не только организационно, но и по существу. Просьба объявить правило эволюции словарей ушла в артефакт §7; сам движок такую дисциплину уже держит (status.go:180 ранг по умолчанию, banknote.go:73 фолбэк в term) S3 (вместе с приведением типов к контракту) адверсариальное ревью S2 → ревью оркестратора 04.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
Ф-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