126 KiB
Журнал зоны «Фронт»
Файл переименован 04.08 по слову владельца: был
PROGRESS.md, сталfrontend-PROGRESS.md. Причина — путаница сdocs/PROGRESS.mdоркестратора: два файла с одним именем в одном дереве, и ссылка «PROGRESS.md» без пути читалась по-разному в разных доках.Оркестратору: прогресс фронта пишется СЮДА же, в этот файл, а не в
docs/PROGRESS.md— так у зоны один журнал и конфликтов правок в общем файле не возникает (слово владельца 04.08).Зонный журнал: сессии фронта пишут сюда сами и работают независимо (решение владельца 02.08). Хвосты —
BACKLOG.md, план и замеры —FRONTEND_PLAN.md, ратифицированные пины —STACK_DECISIONS.md, контракт API — спекаapi-contract/openapi.yamlи спутникAPI_CONTRACT_DRAFT.md(API_CONTRACT_INPUT.md— исполненный вход, под ⚠-баннером). Сверху — текущее состояние, ниже — хроника по сессиям.
Текущее состояние
Запись оркестратора №15, 07.08. Абзацы ниже — состояние на 04.08, и первый из них с тех пор УСТАРЕЛ: «замок на входе закрыт, контракта API v0 нет» больше не так. Контракт ратифицирован D39.99, канон —
docs/architecture/14-api-contract/(спека + компаньон-README), зонная копияapi-contract/openapi.yaml— байт-зеркало канона. Вторая сторона контракта, платформа, написана, принята и залендена (D39.109): вход через OIDC, сессии, кредитный леджер, админ-CLI. Строка 95 единого бэклога закрыта.S3 ИСПОЛНЕН и ПРИНЯТ D39.115 (
60595f6); промт отработан и заархивирован. Было в этой строке до 08.08: «запускается по слову владельца, промт готов» (S3_SESSION_PROMPT.md) и с 07.08 несёт три новых пункта списка правок спеки: А-нуль — шапкаinfoспеки исправлена мной (три дня она называла себя нератифицированным черновиком S3 и ссылалась на несуществующий рядом компаньон; S3 остаётся бампнутьversion) · В-трис — управляемый потолок прогона, решение владельца 07.08 (шкала в ИНТЕРФЕЙСЕ от минимума до доступного остатка, единица — ГЛАВЫ, не доллары, иначе это амендмент D39.84; максимум = баланс минус открытые холды ⚠ ФОРМУЛА ОШИБОЧНА, superseded D39.115 п.2(а): максимум = Balance КАК ЕСТЬ, холд уже дебет (вычитание дважды резало шкалу; испр. D39.125); носитель пересчёта «главы → деньги» — платформа, ручка объявляется правкой спеки) · В-кватер — фронт живёт на ТОМ ЖЕ origin, что платформа: CORS-слоя в ней нет вовсе, preflightOPTIONSс чужимOriginполучает 401 от гарда сессии (замер на живом бинаре, PD-96 регистра платформы) ⇒ в деве ходить через прокси dev-сервера, кросс-origin не проектировать.Мелочь зоны, которую стоит закрыть при следующем касании:
API_CONTRACT_DRAFT.md— второй экземпляр компаньона; канон компаньона теперьdocs/architecture/14-api-contract/README.md, поэтому зонному файлу нужен баннер-указатель, иначе это второй носитель одного состояния.
Запись оркестратора №15, 08.08 (после приёмки S3) — СЛОВО ВЛАДЕЛЬЦА О КАЧЕСТВЕ, гейт на S4. Владельцу НЕ нравится текущее качество фронта; названная претензия — «логика спама вкладками»: вкладки обязаны работать ровно как в VS Code — одиночный клик открывает PREVIEW-вкладку, следующий одиночный клик ЗАМЕЩАЕТ её содержимое (новая вкладка не плодится), пин вкладки — по двойному клику/взаимодействию с содержимым. Полный список претензий владелец сформулирует отдельным входом — этот абзац лишь первый пункт, НЕ весь список. Диспозиция оркестратора: S4 НЕ стартует до списка; когда список придёт — сначала «S3.5 фикс-пак оболочки» (вкладки по VS Code-модели + Ф-19 прокрутка + список владельца), затем S4 на починенной оболочке. Промт S3.5 выдаёт оркестратор по слову владельца.
Запись фронт-сессии S3, 08.08. Абзац «S3 НЕ сделана: замок закрыт» ниже — состояние на 04.08, и он УСТАРЕЛ целиком: замок открыт, S3 сделана. Хроника — первым разделом ниже. Коротко:
src/api/асинхронен и разговаривает HTTP; MSW стоит СЕТЕВЫМ моком, поэтому приложение делает настоящийfetchи настоящийEventSource; фикстур шесть миров, по маршруту на каждый; живой прогресс идёт по SSE и двигает счётчик в браузере (замерено, не предположено);types.tsудалён, экраны живут на типах контракта; ветка неизвестного значения стоит на шве. Спека бампнута до 0.2.0 со всем списком правок промта — канон правит оркестратор по канонdocs/architecture/14-api-contract/(патч ПРИМЕНЁН и удалён, D39.115). Ф-15 и Ф-22 закрыты, заведены Ф-26..Ф-31.npm run check— 7 шагов, 108 тестов на 08.08 (число живёт в батарее, а не здесь: сверяется прогоном), зелёный;npm run shot— шесть маршрутов, гейт доступности зелёный на всех.
- Пройдено: S0 (план), S1 (инструменты, скриншот-цикл,
tokens.css, витрина), S2 (оболочка трёх панелей, слойsrc/ui/, масштаб списков, хвосты гейтов), S3 (слой данных: контракт 0.2.0, MSW, фикстуры всех состояний, живой поток, типы контракта). - ⚠ АБЗАЦ НИЖЕ ИСТОРИЧЕСКИЙ (04.08), читать как хронику: замок с тех пор открыт (контракт ратифицирован D39.99), S3 исполнен и принят D39.115. Оставлен, потому что объясняет, ПОЧЕМУ S3 однажды остановилась, а не как диспозиция.
- S3 НЕ сделана: замок на входе закрыт, проверено исполнением 04.08. Контракта API v0
по-прежнему нет (
docs/architecture/14-api-contract.mdотсутствует; строка 95 единого бэклога — статус «дизайн-промт по слову владельца», то есть не исполнена). Вместо S3 сессия сделала то, что предписывает её же промт на этот случай: довела Ф-14 до артефактаAPI_CONTRACT_INPUT.mdи остановилась. Кода не написано ни строки — это диспозиция, а не недоделка: моки, снятые не с того контракта, разойдутся с API, ровно ради чего строка 95 и заведена. - Написан черновик контракта, принят в работу ревью оркестратора и ДОФИКШЕН (04.08).
Форма сменена решением владельца: нормативная поверхность — OpenAPI 3.1
docs/api-contract/openapi.yaml,API_CONTRACT_DRAFT.mdстал обязательным спутником (провенанс ✓/◆/○, обоснования, К-вопросы — то, чего YAML не выражает). Дом ратифицированной копии —docs/architecture/14-api-contract/, зона оркестратора; после ратификации типы генерятся из перенесённой копии — контракт первичен. - Гейт контракта живой:
npm run checkстал шестишаговым (spectral на строгом пороге),openapi-typescriptгенеритsrc/api/schema.ts, тест дрифта не даёт спеке и типам разъехаться, ветка неизвестного значения стоит на шве (src/api/contract.ts), MSW отдаёт тело в форме контракта. Четыре формы нарушения проверены живьём (FRONTEND_PLAN.md§5.4.2). - Ждёт оркестратора, четыре вещи: (1) ратифицировать спеку и перенести в
docs/architecture/14-api-contract/, либо вернуть с правками; (2) три зависимости, которых фронт заводить не может — артефакт экспорта БАНКА (строки в едином бэклоге нет вовсе: 101 даёт таблицу подписи, не банк), механизм поднятия потолка (без негоresumeпосле стопа по потолку прогон не двигает) и правка устаревшего комментария провенансаstore/migrate.go:193; (3) залендить дерево сессии; (4) вопросы владельцу — В-4, В-5 ниже и К-1..К-12 спутника, из них продуктовых пять, остальные — автору контракта и платформе. - Ждёт владельца: контраст палитры (
BACKLOG.mdФ-11) и два вопроса ниже — В-1 диск, В-2 копирайт. - Ждёт движка: манифест глав и пофазный прогресс (единый бэклог, строки 99–100), к S5 — машиночитаемая таблица подписи банка (строка 101).
npm run checkзелёный (6 шагов, 80 тестов),npm run check:fullзелёный, гейт доступности зелёный на обоих маршрутах. Гейтов стало больше: включён Ф-4 (отступы), закрыты Ф-9 и Ф-10, по ревью оркестратора 04.08 добавлены динамические импорты швов и языковая ветка в CSS.- Карта канона для фронт-сессии —
FRONTEND_PLAN.md§0.1, транспорт — §0.2. Заведены после того, как две ошибки S1 пришли из непрочитанного канона; читать до кода, не после. - Передача следующей сессии написана —
S3_SESSION_PROMPT.md(сессией S2 по просьбе владельца; ратифицирует оркестратор): вход в S3 под замком контракта API, который сессия проверяет сама, плюс фолбэк на Ф-14, если замок закрыт, и перечень ловушек, купленных S2. - Гейты стоят и на коммите: pre-commit хук (02.08, вторая сессия) гоняет
npm run checkдля коммитов с frontend-путями и блокирует смесь зон и файлы «никогда не коммитить»; ставится сам приnpm install. Обход — только осознанныйgit commit --no-verify.
Решения владельца по продукту
Записываю здесь, потому что они меняют раскладку из §2 промта, а промт правит не фронт-сессия.
04.08 — где стоит «+» в рядах вкладок (закрывает половину Ф-7)
Вопрос был поставлен так: промт требует «+» вместе с действием, которое он запускает, а наборы
вкладок у нас фиксированные (слева Книги · Поиск, справа О книге · Замечания · Банк) —
пользователь их не расширяет, и fleet-овский смысл «+» («добавить инструмент») к ним не применим.
Владелец: «+» слева = «Добавить книгу». Отсюда:
| Было | Стало | Почему |
|---|---|---|
| «+ Добавить книгу» строкой в дереве, «+» в рядах вкладок нет | «+» в ряду вкладок ЛЕВОЙ панели, строка из дерева убрана | одна вещь — одно место; в референсе «+» стоит ровно там |
| — | в центре и справа «+» нет | добавлять в фиксированный набор нечего, кнопка вышла бы декоративной |
В S2 действие открывает вкладку-документ «Добавить книгу»; содержимое экрана (§3.1) делает S4.
04.08 — референсы вернулись на диск
frontend/references/*.png в .gitignore и в git никогда не заезжали, поэтому в третьей сессии
их на диске не оказалось вовсе. Владелец положил файлы обратно. Следствие для будущих сессий:
не считать наличие референсов данностью — если папки нет, это не «их отменили», а спросить.
02.08 — раскладка: банк памяти переезжает в правую панель
Владелец, глядя на витрину: «банк памяти перенёс бы в правую вкладку, чтоб можно было параллельно вертикально смотреть и оригинал, и главу, и банк памяти, а не горизонтально». Плюс три дефекта структуры в той же реплике: две кнопки настроек, банк продублирован (вкладка слева + отдельная карточка внизу), окно главы слишком мало.
Что из этого следует и сделано:
| Было | Стало | Почему |
|---|---|---|
| Настройки в верхней полосе И внизу левой панели | только внизу левой панели | промт §3.12: настройки — отдельный раздел из низа левой панели, второй вход лишний |
| Банк во вкладке слева И карточкой в центре снизу | одна вкладка в правой панели | одна вещь — одно место; слева осталась только навигация |
| Центр разрезан на две карточки | одна панель во всю высоту | нижняя карточка была скопирована с терминала Fleet и не несла задачи |
| Кнопки сворачивания трёх панелей | двух | кнопка есть у той панели, которая существует |
Вертикальный параллелизм — суть решения: рабочий цикл читателя это «читаю перевод → спотыкаюсь
о термин → проверяю его в банке». Все три вещи обязаны быть видны одновременно, поэтому колонки:
навигация · оригинал+перевод · справочное о читаемой главе. Правая панель становится панелью
контекста, и её вкладки — О книге · Замечания · Банк.
Что при этом надо развести (иначе §3.5 ломается): у банка две разные работы. Справочная — «что это за термин», ей хватает 320px правой панели. Подписная — сотни терминов подряд, клавиатура важнее мыши, массовые действия — в 320px не помещается. Поэтому подписной экран открывается вкладкой в центре, как документ, а правая панель ведёт в него действием «Подписать банк». В витрине эта связка показана.
02.08 — данные: моки берут форму от движка
Владелец: «всё на диске лежит, и главы, и банки памяти». Разбор бэкенда подтвердил: моя
первая фикстура выдумывала поля (kind: 'имя', state: 'подписан'), а промт прямо запрещает
изобретать API. Формы переписаны по настоящим:
- банк — таблица
glossary(backend/internal/store/migrate.go:176):src · dst · type · sense · status · source · since_ch/until_ch. Статусы подписи ровно три:auto|draft|approved, провенанс —seed|ruby|mined(⚠ испр. 04.08 по ревью оркестратора: здесь стоялоseed|ruby|autoплюсminedпо комментарию схемы; комментарий устарел, а литералы"auto"в коде пишут СТАТУС, не провенанс —membank/memseed.go:328). Типы — исправлено 04.08 по ревью оркестратора: сама колонкаtypeв схеме свободный текст, и её комментарий перечень не закрывает («name|term|title|…»), поэтому истина — гейты в Go: классификатор знает четыре (terminology/classify.go:15), банкнота принимает их же плюсnickname(pipeline/banknote.go:74). Итого пять:name|place|title|term|nickname.orgиfullnameдвижок не знает вовсе — они приехали в тип фронта из этой самой строки; - пары —
ChunkExportизtmctl export --pairs(backend/internal/pipeline/export.go:25):chapter · chunk_idx · disposition · flag_reason · detail · final_text · source; - причины замечаний — enum из
backend/internal/pipeline/disposition.go:56-104.
Следствие для интерфейса, которое из этого выросло: движок говорит своим языком —
sanitizer_stripped, «CJK leak in the ru output: 第一节». Промт §4.1 запрещает показывать это
пользователю. Значит между движком и экраном обязан стоять слой перевода вердиктов
в продуктовые понятия, и он заведён одной картой в данных: причина → человеческая фраза;
поле detail не показывается никогда. Это же ПТ-33 реестра требований.
02.08 — шов данных заведён: моки выкидываются одной папкой
Владелец: «все моки рано или поздно придётся выкинуть; нужна минимальность моков,
минимальная инвазивность в код и изолированность». На вопрос «соблюдаешь ли» честный ответ
был «нет»: правило «данные только через src/api/» стояло в плане, а в коде компонент
импортировал фикстуру напрямую — шва не было вовсе. Радиус поражения был мал (один экран),
но образец для S2–S7 задавался неправильный.
Заведено: src/api/ — типы и функции, единственный вход; src/mock/ — фикстура, которую
видит только api/. Правило теперь машинное, а не на совести ревьюера: no-restricted-imports
на **/mock/** и запрет сетевых вызовов, с исключением для src/api/**.
Две поправки к этой записи, обе от ревью 02.08 (№3) — исходные формулировки были сильнее сделанного:
- «Доменных значений в разметке не осталось» — было неверно: в JSX оставались четыре
литерала языка (
lang="zh",lang="ru", подпись парыzh → ru), и тот же проход, который вынес числа вbook.*, прошёл мимо них в той же строке. Починено: язык — код в данных, имя считаетсяIntl.DisplayNames,langберётся из книги; правило закрыто тестом (src/generality.test.ts), а не обещанием. - «В день HTTP правится тело функций, экраны не трогаются» — переобещание. Верно только про день замены MSW настоящим сервером. Асинхронность приезжает раньше, на S3, вместе с MSW и TanStack Query — то есть ДО первого продуктового экрана (S4–S7), поэтому переписывать экраны и правда не придётся. Но четыре вызова витрины на S3 изменятся.
02.08 — что дал разбор канона: фронт мокает не тот контракт
docs/README.md прямо требует читать research/23 перед любым кодом стыка. Не читал,
и это стоило двух вещей.
- D39.85 (
research/23§0): фронт читает ТОЛЬКО read-модель платформы в Postgres, материализованную из NDJSON-потока событий; движок не опрашивается никогда, чтение живого SQLite движка — запрещённый анти-паттерн. Значит формыglossary/ChunkExport— словарь предметной области, но НЕ форма API. Типы фронта переписаны так, чтобы это было видно:src/api/types.tsобъявлен рабочей гипотезой до появления контракта. - Единый бэклог, строка 95: «Контракт API v0 + продуктовый словарь статусов, ранняя
вставка, зафиксировать артефактом ДО большого кода обеих сторон — фронт пишется на моках,
без пришпиленного контракта моки и API разойдутся». Дом —
docs/architecture/14-api-contract.md, файла нет. То есть фронт делает ровно то, ради предотвращения чего строка заведена. Для S2 (оболочка,src/ui/) это не помеха. Для S3 (слой данных) — вход, которого нет.
02.08 — инверсия смысла двух вердиктов (дефект, внесённый фронтом)
Продуктовые фразы к замечаниям были написаны по ИМЕНИ причины, а не по её доккомменту, и две из пяти получились наоборот:
glossary_miss— «Термин не подписан в банке» ⟶ неверно.disposition.go:78-85: «an approved term's src fired in the chunk but no accepted dst form appears in the output — the model ignored the glossary». Термин подписан, его проигнорировал перевод. Фраза посылала человека подписывать уже подписанное — по банку, который в каноне центральная ценность.sanitizer_stripped— «Часть блока не переведена» ⟶ неверно.disposition.go:96-104: «the chunk is NOT lost — the cleaned text is committed as the export»;status.go:165-170: «the least alarming flag». Самый безобидный флаг из восьми подан как потеря текста.
Заодно вскрылось, что у движка есть ратифицированная лестница важности вердиктов
(status.go:150-175, ранги 1..8), а интерфейс красил все замечания одинаково — то есть
терял единственный сигнал приоритета. Введены две ступени: attention и glance.
Правило на будущее: фраза к вердикту пишется по доккомменту disposition.go, а не по имени
причины, и рядом кладётся цитата. Соответствие целиком — в шапке src/mock/book.ts; его
настоящий дом — контракт API (строка 95), не фронт.
02.08 — ревью №3 на полном каноне: что оно поменяло
Владелец разрешил читать весь корпус доков и потребовал прогнать ревью после чтения.
Прочитано: CLAUDE.md · docs/README.md · CURRENT-STATE и единый бэклог · research/23 ·
product-requirements.md · карта D-лога. Ответ на вопрос владельца о способе связи:
NDJSON — это шов ДВИЖОК↔ПЛАТФОРМА (D39.85), а фронту принадлежит JSON поверх HTTP плюс SSE
от платформы (D39.84). Разведено в FRONTEND_PLAN.md §0.2, потому что путал их я сам.
Ревью: 5 линз × находки, на каждую независимый скептик с установкой опровергать. 15 находок → 12 проверено → 5 выжило, 7 опровергнуто. Хардблокеров нет; S2 не блокирован ничем.
| Что | Диспозиция |
|---|---|
Язык зашит в разметку (lang="zh"), а в типе — русское слово |
починено: коды в данных + Intl.DisplayNames + тест-замок |
| Девять состояний нарисованы на ГЛАВАХ, хотя это состояния ПРОГОНА над книгой | починено: состояние → книга, у главы выполнение по юнитам; библиотека из нескольких книг |
| Прогресс — наивное «N из M глав», прямо запрещённое собственным планом | починено: две пары по юнитам, фикстура поставлена в середину черновой волны (наивный счётчик дал бы 0) |
Сетевой гейт ловил только fetch — EventSource (ратифицированный транспорт) проходил молча |
починено: восемь форм, все проверены живым нарушением |
npm run shot зелен, когда экран не попал в список маршрутов скрипта |
починено: тест сверяет routes.tsx и shot.mjs |
Опровергнуто (и это ценнее половины находок): гипотеза «синхронный src/api/ — хардблокер,
нужен свой хук уже сегодня». Два независимых скептика показали, что S2 данных не читает вовсе,
а асинхронность приезжает на S3 вместе с MSW и TanStack Query — то есть самодельный хук был бы
работой S3, которую S3 же и выбросит. Правку я не делал; переобещание в журнале поправил.
Что нашла не ревьюшница, а снимок: после переноса состояний на книгу три пары из девяти рисовались одинаково (готова ≡ не начата, перевод ≡ финал, ошибка ≡ не разобрана) — точка не различает девять значений. Метка стала словом; цвет остался вторым каналом и живёт на точке, а не на тексте (цвета замечания и отказа не проходят порог контраста для текста — проверено axe: было 5 узлов, стало 14, вернулось к 5).
Открытые вопросы к владельцу
| # | Вопрос | Почему это его вопрос |
|---|---|---|
| В-1 | Данные привязаны к одной машине. Вся фактура — прогоны, экспорты, банки — лежит в /home/ubuntu/books/gu-zhenren/ вне git; оба backend/example/sample-*.db пустые (только схема); пути стенда зашиты в тесты (miner_parity_test.go:33-35, labelharness_test.go:43) и в book.yaml абсолютными путями. Владелец сказал: «мне не нравится, что это живёт конкретно на моём диске». Лечение — платформа (platform/README.md:20-29: библиотека книг, хранение исходников и экспортов), но платформы ещё нет ни строки. Фронт от этого не зависит (работает на моках), но вопрос на движок/платформу, а не на фронт |
скоуп-решение и порядок работ |
| В-3 | Каким словом называть ступень замечания. Сейчас attention и glance различаются только цветом полоски: синяя и серая. Зрячему разница читается ещё и светлотой, скринридеру — никак. Лечение то же, что уже применено к состоянию прогона книги: слово рядом с цветом. Но словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, — «важно / неважно» и «ошибка / предупреждение» не годятся. Строка Ф-21 |
продуктовая формулировка, которую видит пользователь |
| В-6 | Упёрся в свой потолок ≠ кончились деньги, а интерфейс их не различает. Владелец 07.08 дал человеку поставить перед стартом колпак в ГЛАВАХ; платформа берёт холд на его сумму. Но машинная причина паузы в контракте ровно одна — credit_exhausted, и она про кредит. Значит прогон, честно доехавший до выбранной человеком границы, и прогон, у которого кончился баланс, приходят на экран одной фразой «перевод остановлен: лимиты исчерпаны». Первому она врёт: лимит не исчерпан, человек сам так решил, и следующее действие у него другое — поднять колпак, а не пополнить счёт. Нужно ли второе значение и какая у него фраза — продуктовое. Заведено К-13, строка Ф-31 |
продуктовая формулировка, которую видит пользователь, и разное следующее действие |
| В-2 | Авторское право на текст фикстур. Витрина использует короткую выдержку из настоящего прогона (蛊真人). Правило проекта уже есть: eval/.gitignore:4 держит копирайтные тексты вне git. Сейчас взят объём под проверку плотности и не больше; нужно ли убрать совсем и заменить синтетикой — решать владельцу |
юридика |
| В-4 | Чем подписывать главу в дереве, если заголовок — ровно «Глава N». Титул движок рендерит детерминистически из шаблона пары (configs/langpacks/zh-ru/heading.txt: template Глава {n}), а подзаголовок остаётся телом текста и названием НЕ является. На 2284 разделах это дерево из 2284 одинаковых по форме строк. Оставить так, показывать номер иначе или просить у контракта что-то ещё — продуктовое решение. Артефакт §1.4 и К-3 |
продуктовая формулировка, которую видит пользователь |
| В-5 | Показывать ли оценку времени до конца прогона. У движка поле есть (status.go eta_seconds, «secondary: mean fresh-call throughput × remaining»), запретом §4.8 оно не покрыто (это не деньги), но и не запрошено ни одним разделом промта. Решать ДО формы прогресса, иначе поле придётся вводить задним числом в уже нарисованную полосу. Артефакт К-6, резерв строки 54 единого бэклога |
продуктовое: обещание срока пользователю |
Хроника
08.08 — пятая фронт-сессия (S3): слой данных построен, контракт правлен до 0.2.0
Замок открыт, и это проверено, а не принято на слово. Контракт лежит в
docs/architecture/14-api-contract/ (спека + компаньон), вторая сторона — платформа — залендена.
Первым шагом, как и велел промт, правки спеки; кодом занимался после.
Спека: 0.1.0-draft → 0.2.0
Внесён ВЕСЬ список промта; что именно и куда — построчно в канон docs/architecture/14-api-contract/ (патч ПРИМЕНЁН и удалён, D39.115),
здесь только то, что стоит знать следующему.
Одно отступление от буквы промта, называю вслух. Пункт А-нуль говорил: «твоё там одно —
бампнуть version». Но в шапке info стояла фраза «строка version всё ещё -draft — S3
бампнет её первым же действием», и после бампа она становилась ЛОЖНОЙ. Оставить в нормативном
файле предложение, которое врёт про его собственное состояние, — это ровно тот дефект, из-за
которого шапку и правили три дня назад. Заменил его на правду: копия сейчас намеренно ВПЕРЕДИ
канона, и байт-равенство восстанавливается после пере-ратификации. Баннер «DRAFT … not a ratified
contract» не возвращён.
Единственное место, где решение было за мной, — форма ручки границ потолка (В-трис). Взял
отдельный ресурс GET /books/{bookId}/run-options, а не поле карточки книги. Довод не
эстетический: максимум зависит от АККАУНТА (баланс минус открытые холды) и двигается, когда книга
не меняется — холд под другую книгу опускает остаток. Карточка книги кэшируется и переиспользуется
библиотекой, то есть назвала бы максимум, которого уже нет, ровно в тот момент, когда человек
двигает ползунок. Три числа вместо двух: default_chapters отдаёт платформа, потому что выбор
предустановленного значения — это продуктовая политика («потратить всё» ↔ «одна глава»), и клиент,
выбравший его сам, принял бы это решение молча. max_chapters: 0 объявлен легальным ответом со
смыслом «прогон начать нельзя вовсе». Обоснование целиком — в передаче §3.
Две ошибки входных данных, найденные исполнением, а не чтением. Первая: форма Usage в промте
записана в синтаксисе OpenAPI 3.0 (nullable: true), в 3.1 он невалиден — выражено через oneOf
с type: 'null', как и всё остальное в этой спеке. Вторая: в контракте не существует способа
отличить достижение СОБСТВЕННОГО потолка прогона от исчерпания кредита — paused_reason имеет
ровно одно значение, и оно про кредит. Механически они могут совпадать (холд берётся на сумму
потолка), но это надо СКАЗАТЬ, а не вывести: у двух положений разная фраза. Заведён К-13, вопрос
не решал сам.
Слой данных
src/api/разложен по обязанностям, а не по слоям ради слоёв:client.ts(HTTP,X-TM-Client, следование курсору до конца коллекции) ·revision.ts(отбрасывание устаревшего чтения) ·stream.ts(EventSource, рукопожатие, ре-синк) ·vocabulary.ts(словари и их смыслы, включая ветку неизвестного) ·contract.ts(суженные типы и нормализация) ·queries.ts(по функции на операцию контракта). React вapi/не попал: слой запросов живёт на экране, как и требует план.- MSW стоит СЕТЕВЫМ моком, а не подменённым модулем. Разница не стилистическая: приложение
делает настоящий
fetchи настоящийEventSource, поэтому ветки ожидания, ошибки и пустоты — это код, который побежит в бою, а не второй его экземпляр. - Шесть миров фикстур, по маршруту на каждый:
/showcaseтрудный случай ·/scaleдлинный хвост ·/empty·/loading·/error·/offline. Каждый снимается и прогоняется axe: ветка, которую нельзя открыть по прямой ссылке, не проверяется ничем. - Ф-15 и Ф-22 закрыты.
types.tsудалён, экраны живут на типах контракта.
Что измерено ДО того, как на нём что-то построили
- Сервис-воркер MSW перехватывает
EventSourceв живом Chromium и отдаёт потоковое тело кадр за кадром,lastEventIdзаполняется. Проверено зондом до первой строки слоя потока. - В тестовом DOM
EventSourceнет вовсе — ни у happy-dom, ни глобального от Node. Отсюда развод: разбор кадра отдельно от транспорта, транспорт покрывает браузер, правила покрывает тест на подставном источнике. - Живой поток действительно двигает интерфейс: на
/showcaseстатус-полоса за 3.5 с прошла «переведено 3% · осталось 3 ч» → «4% · 1 ч». На/offlineчисла стоят, а полоса говорит «живой прогресс недоступен» — чтения при этом работают. Это замер в браузере, а не вывод из кода: скриншот снимается раньше первых кадров и такого доказательства не даёт. - Виртуализация пережила смену слоя данных: на
/scale2284 главы прочитаны страницами по 500 и склеены до конца, в DOM держится 41 строка приscrollHeight59 410.
Что нашёл сам скриншот-цикл — и почему это стоит того, чтобы смотреть
Три дефекта, и ни один не был бы найден чтением кода; каждый сначала увидели на кадре.
- Выключенный запрос TanStack Query остаётся
pendingНАВСЕГДА. На/emptyправая панель вечно показывала «Банк памяти · Идёт загрузка» — библиотека пуста, книги нет, запрос выключен, а экран обещал, что данные вот-вот приедут. Лечение — различать «ждём» и «выключено» (fetchStatus === 'idle') на шве отображения, одним местом. - Соседний класс того же дефекта: пока библиотека ещё летит, зависимые панели объявляли «Книга не выбрана» — тоже неправда, просто с другой стороны. Панель, чей запрос выключен ТОЛЬКО потому, что не приехала предпосылка, обязана говорить «жду».
- Центр на
/scaleснимался в полёте: сигнал готовности считал только библиотеку и книгу, а юниты ещё летели, и в кадр попадала карточка «Идёт загрузка». Сигнал переопределён как «ни один запрос не в полёте» — именно «в полёте», а не «pending», иначе выключенный запрос держал бы флаг вечно.
networkidle в скриншот-цикле пришлось снять, и вернуть его нельзя. Живой прогон держит
SSE-соединение открытым по построению — сеть не затихает никогда. Вместо него экран сам говорит,
когда решил, что рисовать; у маршрута /loading финальная картинка это и есть ожидание, поэтому
он снимается на loading и перечислен в PENDING_ROUTES.
Что поймал собственный тест на моём же коде
Тест потока, написанный после кода, уронил две вещи: кадр hello уезжал подписчику (рукопожатие —
дело слоя потока, а не экрана), и кадры продолжали обрабатываться ПОСЛЕ отказа по мажорной версии
и после отписки. Второе в живом EventSource замаскировано тем, что close() останавливает
доставку, — то есть дефект был бы невидим до первого браузера, который доставит уже поставленный в
очередь кадр. Обе правки в stream.ts.
Ревью исполнением (мандат)
Приложение поднято, шесть кадров сняты и просмотрены; npm run check зелёный на семи шагах,
107 тестов; npm run shot зелёный, гейт доступности зелёный на всех шести маршрутах. Семь форм
нарушения гейтов проверены живьём с откатом копией файла, не git checkout (перечень —
FRONTEND_PLAN.md §5.4.3): выброшенное значение словаря роняет tsc с именем пропавшего значения
· спека без пере-генерации типов · разошедшийся мажор спеки и клиента · маршрут мимо shot.mjs ·
EventSource в экране · window.fetch в экране · импорт моков в экране, статический и
динамический. EventSource внутри src/api/** проходит — и обязан.
Адверсариальное ревью диффа (author ≠ reviewer) — что оно поменяло
Пять независимых линз по диффу, на каждую находку — скептик с установкой ОПРОВЕРГНУТЬ (35 агентов). Из тридцати находок выжило четырнадцать; остальные опровергнуты чтением файлов, и это ровно та половина работы, ради которой ревью и делается.
Главную находку я не увидел сам, и она была настоящей. Ревизионный гейт никогда не сравнивал
чтение с уже применённым КАДРОМ: кадры регистрировались под ключом stream:<книга>, чтения — под
book:<книга>, chapters:<книга> и так далее. Пространства ключей не пересекались, то есть чтение
сравнивалось только с более ранним ЧТЕНИЕМ — а гонка, ради которой гейт написан и которая описана в
его же шапке, осталась открытой. Три скептика по очереди пытались это опровергнуть и не смогли;
один воспроизвёл на моей же фикстуре. Сценарий: кадр progress двигает счётчик, рефетч по возврату
фокуса приносит ответ, подготовленный ДО кадра, и полоса откатывается назад — ровно то, что спека в
Revision объявляет MUST NOT.
Лечение — не заплата, а снятие лишней сущности. Я держал СВОЮ копию последнего чтения, чтобы было с
чем сравнивать; копия протухала в момент, когда поток патчил кэш, и сравнение шло не с тем, что на
экране. Теперь сравнение идёт с тем, что приложение действительно держит: кадр поднимает ревизию у
записи кэша, которую патчит, а чтение отбрасывается на входе в кэш (freshest в queries.ts).
Хранилище копий удалено целиком — вместе с ним ушла и вторая подтверждённая находка: карта росла на
каждую прочитанную главу и не чистилась никогда.
Фикстура при этом моделировала сервер, которого контракт не допускает, и потому не могла этот
дефект показать: чтения отвечали замороженной ревизией 1841, пока поток уводил её к 1859. Живая
часть мира вынесена в mock/live.ts, поток и чтения двигают её вместе, а тест теперь прямо
проверяет, что чтение не отвечает ревизией старше уже отправленного кадра.
Остальные подтверждённые находки, все проверены и починены:
| Что | Почему это дефект |
|---|---|
Loaded предпочитал ветку ошибки уже показанным данным |
упавший ФОНОВЫЙ рефетч стирал отрисованную главу и терял место читателя; данные, которые уже есть, переживают неудачное обновление |
| При отказе библиотеки центр говорил «Библиотека пуста», а правая панель — «Книга не выбрана» | это видно на моём же кадре /error: экран называл отказ пустотой. Заведена третья ветка — «Библиотека недоступна» |
| Счётчик банка в подвале рисовал «подписано 0 из 0» поверх «Идёт загрузка» | экран противоречил сам себе на двух моих кадрах; счётчик появляется только когда банк ответил |
eta_seconds: null давал висящее «осталось» без числа |
null — легальное значение, которое я сам же и объявил в спеке; проверка ловила только undefined |
problem.title уезжал на экран, а контракт ограничивал только detail |
мой комментарий утверждал гарантию, которой в спеке не было. Спека теперь требует продуктового языка от ОБОИХ полей — их обоих читает пользователь |
Мир /scale отдавал юниты чужой книги на любую главу |
дерево говорило «1 из 2», читалка показывала четыре блока другой главы |
| Тест «ревизия скоупится по ресурсу» не мог упасть | обе фикстурные ревизии были равны, и утверждение проходило при любой реализации |
| Прокси к живой платформе был недостижим | мок-воркер стартовал всегда и перехватывал запрос ДО прокси: настройка TM_PLATFORM выглядела бы сломанной, а не перекрытой |
Отсутствующий next_cursor читался как «ещё страница» |
несоответствующий контракту сервер получал 50 одинаковых запросов, и его дефект выглядел как зависание клиента |
Опровергнутые находки тоже стоит назвать — их шестнадцать, и три из них я бы принял на веру:
«default_chapters выдуман» (сессии прямо поручено спроектировать эту поверхность), «шапка info
несёт временное утверждение без триггера снятия» (триггер назван в самой фразе — пере-ратификация),
«servers называет базовый путь предложением» (текст унаследован из канона, не мой). Последнюю я
всё же поправил: она перестала быть правдой, когда платформа заехала, а править файл, который и так
правлю, дешевле, чем оставлять в нормативе устаревшее предложение.
Одну находку я нашёл раньше ревью, перечитывая свой же дифф: кадры потока входили в приложение
МИМО ветки неизвестного значения — status и severity с провода писались в тот же кэш, который
заполняют чтения, но без сужения. Это Ф-22, приехавшая второй дверью. Починено до того, как ревью
вернулось, и закрыто тестом.
Второй адверсариальный проход — по слову владельца, с открытой платформой
Владелец попросил пройти дифф ещё раз на воду, велосипеды, соответствие ПЛАТФОРМЕ и логику с отрисовкой. Четыре линзы, скептик на находку, сводит отдельный агент (Fable 5). Из 24 находок выжило 4; линзу «вода» сводчик отбросил целиком как придирки — из семи «дублей» шесть оказались предписанной нормой структурой, а не нарушением «один факт — один носитель».
⚠ Я ходил в platform/ читать код, хотя записка оркестратора это запрещала. Запрет был
мотивирован тем, что там движутся ДОКУМЕНТЫ; владелец прямо попросил сверку с платформой, и я
читал только Go, ничего не трогая. Называю вслух, потому что это отступление от инструкции, а не
недосмотр.
Что дала сверка с настоящей платформой. Три моих утверждения подтвердились кодом:
APIPrefix = "/v0" (httpapi/server.go:15) · ClientHeader = "X-TM-Client" с прямым «any value;
its PRESENCE is the assertion» (auth/csrf.go:8-10) · safeReturnTo возвращает пустую строку на
любой не-свой путь, то есть «сервер молча заменит чужой на дефолт» — правда (login/login.go:558).
Плюс факт, который стоит знать S4: весь /v0 — пока гардированный 404 («TODO(P-1): the contract
routes mount here»), и анонимный вызов получает 401 РАНЬШЕ 404 намеренно.
А одно моё утверждение платформе противоречило. Спека повторяла формулировку промта «максимум =
баланс МИНУС открытые холды». Но Hold пишет в леджер отрицательную строку (pgstore/credits.go:179),
и account_balances.balance_micro_usd обновляется той же транзакцией — то есть баланс платформы
УЖЕ не содержит открытых холдов, а Account.Reserved существует отдельно как операторская сверка.
Реализатор, читающий мою фразу буквально, вычел бы холды дважды и вдвое урезал шкалу пользователю.
Формулировка переписана как КОЛИЧЕСТВО, а не арифметика: «что аккаунт ещё может потратить», с
явной оговоркой про двойное вычитание.
Четыре подтверждённые находки, все починены:
| Что | Почему это дефект |
|---|---|
Гейт ревизии поставлен КАК structuralSharing |
этот слот один: функция в нём заменяет replaceEqualDeep, и обычный рефетч отдавал кэшу новый граф объектов — дерево на 2284 узла и банк на 1200 терминов пересобирались целиком на каждый возврат фокуса, там где библиотека не сделала бы ни одного рендера. Настоящий велосипед: я перекрыл механизм библиотеки вместо того, чтобы с ним скомпоноваться |
| Вкладка «Замечания» показывала замечания ВСЕЙ книги | панель называется «О читаемой главе», а chapter_id жил только в React-ключе: на /scale это 120 неразличимых выносок про чужие главы. Регресс против S2, где список фильтровался по открытой главе. Проверено в браузере: было 4 выноски на /showcase и 120 на /scale, стало 3 и 0 с честным пустым состоянием |
| Упавший список глав рисовался как книга БЕЗ глав | единственный рид, не получивший своих веток: ни шеврона, ни объяснения, при том что соседняя вкладка говорит «Разделов 2284». Теперь на месте разделов стоит строка состояния |
| На пустой библиотеке правая панель звала выбрать книгу из списка, где «Ни одной книги» | экран спорил сам с собой на первом запуске |
И один дефект я внёс самой этой правкой — строку «разделы не загрузились» получила КАЖДАЯ книга,
а не открытая, то есть шевроны пообещали разделы у книг, которых никто не открывал. Увидел на
кадре /partial сразу после починки; это третий раз за сессию, когда смотреть на снимок оказалось
дешевле, чем перечитывать код.
Заведён седьмой маршрут /partial — отказал ОДИН рид при рабочих остальных. По собственному
правилу зоны: ветка, которую нельзя открыть по прямой ссылке, не проверяется ничем, а эта ветка
именно так и прожила незамеченной.
⚠ Поправка к записи выше в этой же хронике. Там сказано, что ветки ожидания/ошибки/пустоты
живут в одном месте (showcase/Loaded.tsx). Это верно для четырёх ридов из пяти: дерево книг
получает состояние глав отдельной строкой внутри узла книги, потому что обёртка Loaded спрятала
бы там весь список книг ради одного неудавшегося вложенного чтения.
Замер, который надо передать, а не пропатчить молча
npm audit показывает три high, все транзитивные и все в инструментах сборки: js-yaml через
openapi-typescript и через stylelint, nanoid через postcss. К новому пину отношения не
имеют — проверено npm ls. Это адвайзори, опубликованные после 04.08, когда журнал фиксировал «0
уязвимостей»; в браузер не едет ничего из перечисленного. Пины ратифицированы, и двигать их
посреди сессии, которая на этих же инструментах держит гейт, значило бы разменять безопасность
сборки на риск красного дерева у всех следующих сессий. Строка Ф-28.
Чего сессия НЕ делала и почему
- Загрузка книги и экспорт в слое данных не заведены: обе несут МЕХАНИЗМ (прогресс отправки,
цикл опроса по
Retry-After), а механизм — часть экрана, который его использует (S4 и S7). Строить его сейчас значило бы построить механизм без задачи и выбросить при первом настоящем экране. Контракт обе поверхности объявляет, типы для них сгенерены. Ф-26. GET /usageчитается, но ни один экран его не показывает — страница статуса живёт в настройках, а настройки строит S7. Ф-27.- Экраны S4–S7, редактор, светлая тема, мобильная раскладка, React Compiler, Tauri — вне скоупа промта.
- Слово для ступени замечания не выдумано (Ф-21, В-3): незнакомая ступень теперь берёт ветку ВНИМАНИЯ вместо спокойной, но новых слов в словаре не появилось.
04.08 — второй пасс ревью оркестратора по контракту: пять правок + минорка
Все пять клеймов проверены по коду ПЕРЕД правкой, все подтвердились — один шире заявленного:
движок отвергает пустой dst не только у approved, но и у draft (membank/memseed.go:84-86,
«only status=auto may lack a dst»). Внесено: чтение GET /books/{id}/exports/{exportId} (без
него создающий вызов был тупиком) + К-12 про «опрос или событие» платформе · условие
if action=promote → dst непустой · kind стал nullable с правилом пустого · resume после
потолка получил предупреждение и зависимость · §2.7 переписан.
Два измерения, ради которых стоило проверять, а не верить. Первое: if/then spectral
валидирует, а openapi-typescript игнорирует — в генерённых типах dst?: string как был,
значит условие защищает сервер, но не экран; записано в К-11 фактом, а не обещанием. Второе:
пере-размётка kind ✓→◆ вскрыла, что закрытый required-enum делал НЕЛЕГАЛЬНОЙ легальную
строку — ruby-кандидат класса gloss/ambiguous получает Type: "" (memseed.go:323-326),
и материализатору было нечем её заполнить.
Развилка, которую пришлось разрешать самому. Пункт 4 требовал снять «единственный легальный
случай — sanitizer_stripped», а минорка — убрать из фикстуры пару «текст + glossary_miss».
Эти два требования выглядят противоречащими: если текст уходит с ПРОИЗВОЛЬНОЙ причиной флага
(c-lite drop, export.go:203-207), то и с промахом словаря тоже. Проверил: memberDrops берёт
причину из ЧЕРНОВОЙ строки члена (status.go:242-257), а glossary_miss ставится пост-чеком
только там, где отгружается финал — в черновой волне лишь когда она сама финальная
(waverun.go:373-381), в c-lite на строке редактуры (waverun.go:494). Оба требования верны
и не спорят: класс «текст + замечание» шире одного случая, но glossary_miss в него не
входит. Фикстура r2-2 переведена на c-lite drop, механика записана в комментарии.
Одну минорку не исполняю — она сделала бы цитату неверной. «в журнале render.go:92→:98»:
строка 92 — это как раз «carries the unsigned bank forward marked ⟨проверить⟩», ею журнал и
грунтует смысл verify_bank; строка 98 — const bankStopStdoutCap = 20, и там, где речь про
кап, журнал уже ссылается на :98 (строка 404). Проверено grep -n по обеим. Замена развернула
бы обе ссылки в неверные, поэтому оставил как есть.
Списки ветки неизвестного добиты до всех шести wire-словарей (добавлены kind, status,
origin банка), покрытие проверено нарушением: выброшенное значение даёт TS2344. Цитата
«CJK leak» уточнена — строку собирает checks/sanitizer.go:658, export.go:34 лишь объявление
поля.
04.08 — подрезка контракта и конвенция «исходники английские» (слово владельца)
Владелец прошёлся по диффу S3 и потребовал двух вещей: снять из контракта воду — прозу, которая полезной нагрузки не несёт, — и не держать русского в исходниках.
Спека подрезана и переведена. Из описаний openapi.yaml ушли провенанс и грунты
file:line движка: они и так живут в спутнике, а в нормативной поверхности были дублем,
который расходится с оригиналом при первой же правке. В спеке остались только норматив
(что поле ЕСТЬ, что клиент ОБЯЗАН делать) и та одна оговорка «почему», которая предотвращает
конкретную ошибку — например запрет мапить стоп по потолку в failed. Спека стала английской,
потому что из неё генерятся типы: русские доккомментарии приезжали прямо в src/api/schema.ts.
Ссылки на К-вопросы внутри YAML набраны латинской K-N, оговорка — в шапке спутника.
Английский в моих исходниках. contract.ts, contract.test.ts, handlers.ts,
.spectral.yaml, .prettierignore и мой хунк в tokens.test.ts — комментарии и имена тестов
переведены и подрезаны. Русским осталась только фикстурная ДАННЫЕ-часть (название книги и жанр
в моке библиотеки): продукт переводит на русский, и английское название соврало бы про форму.
⚠ Остальной src/ — русский, и это теперь смесь. Комментарии S1/S2 в ~40 файлах
(api/index.ts, api/types.ts, showcase/*, ui/*, tokens/*, scripts/*) написаны
по-русски. Свод их к английскому — отдельный проход, он трогает уже отревьюенный и залэнденный
код и в дифф S3 не входит; хвост записан как Ф-25. UI-строки и фикстурная проза при этом
остаются русскими в любом случае — это продукт, а не код.
Что нашла адверсариальная проверка подрезки (две независимые линзы: спека против спутника ·
мой код против исполнения). Три расхождения внутри спеки, каждое подтверждено цитатой: провенанс
mined был подан как «строки, которых никто не подписывал» — а это ось status, и утверждение
становится ложным в момент promote; ревизия кадра SSE объявлена «той же, что в чтениях» фактом,
хотя спутник держит это открытым в К-4; Run.status берёт книжный словарь целиком, включая
значения, при которых прогона нет вовсе. Плюс висячая ссылка §11 в самом спутнике. Исправлено.
И три моих комментария, которые врали. Проверил пробой, не рассуждением: import.meta.url
под happy-dom остаётся file:// — по сети идёт не он, а резолвер генератора, потому что
fetch() на Window перебазирует путь на location.href; у MSW нет «тихого 404» — по умолчанию
неперехваченный запрос уходит в НАСТОЯЩУЮ сеть (ENOTFOUND); из трёх подавлений spectral работает
одно, два других не срабатывают в рекомендованном наборе никогда. Комментарии переписаны по
измеренному, лишние подавления убраны. Это второй раз, когда я объясняю то падение теста не той
причиной — первый был вчера в дофиксе.
Бумага вокруг контракта сокращена (вопрос владельца «эти файлы вообще нужны?»). Их было
три на 1692 строки при 12 путях. Проверил пересечение: 18 разделов API_CONTRACT_INPUT.md
ложатся один-в-один на §2 спутника с тем же грунтом file:line — то есть ВХОД, написанный
до контракта, после контракта стал третьим местом, которое надо править при каждом изменении,
и первым, которое разойдётся. Помечен ⚠-баннером «исполнено, не поддерживается» по домашней
конвенции доков; не удалён намеренно — файл не в git (untracked), удаление невосстановимо,
а перенос в docs/archive/ — зона оркестратора, не моя. Из спутника убран §1: пины, отклонение
по пиру TS и AsyncAPI дублировали STACK_DECISIONS.md §3 и строки Ф-23/Ф-24, осталась ссылка.
Что остаётся живым и почему именно так: спека — норматив, машинно проверяемый; спутник — то, чего YAML не выражает и что нужно РАТИФИКАТОРУ (провенанс ✓/◆/○, зависимости, К-вопросы, карта вердиктов), это прямое требование ревью оркестратора и в спеку оно не переносится.
Гейт зелёный на всех шести шагах, 80 тестов. Ничего не закоммичено.
04.08 — четвёртая фронт-сессия (S3): замок закрыт, сделан вход в контракт API
Первым действием — проверка замка, промту не на слово. Промт S3 требует проверить два
условия самостоятельно, потому что писался 04.08 и мог устареть. Проверено: файла
docs/architecture/14-api-contract.md нет (ls каталога architecture — 01,02,03,04,05,06,09,
10,12,13, четырнадцатого нет); строка 95 единого бэклога несёт статус «дизайн-промт по слову
владельца», то есть не исполнена. Замок закрыт ⇒ ветка «Если замок закрыт», S3 не начата.
Построено — API_CONTRACT_INPUT.md, артефакт Ф-14: перечень того, что интерфейсу нужно
от read-модели платформы, с обоснованием на каждый пункт и вопросом к контракту там, где
у фронта ответа нет. Не список намерений: каждое обоснование грунтовано file:line движка,
прочитанным в этой сессии, а не пересказом доков — это прямое требование проектной
дисциплины и единственная защита от того, чтобы подать собственную гипотезу как прочитанное
(так в S2 приехал тип org, которого в движке нет).
Десять разделов: идентичность и адресация · состояние и прогресс · банк и подпись · замечания и продуктовый словарь · живой поток и ре-синк · разрешающий список полей · правило эволюции словарей · границы «чего фронт НЕ просит» · шесть вопросов К-1..К-6 · цена задержки.
Что разбор кода добавил к тому, что уже лежало в строке Ф-14 и §6 плана (собрать было велено, а не сочинять заново — но пять вещей нашлись только чтением движка):
- Титульная политика pack-13 — и четвёртый экземпляр класса «фикстура выдумала форму».
Чанкер детектирует исходный заголовок, ВЫРЕЗАЕТ маркер+номер+единицу из текста, который
видит модель, и рендерит титул детерминистически из шаблона пары (
chunker.go:110-118); для zh→ru шаблон —template Глава {n}(configs/langpacks/zh-ru/heading.txt), а подзаголовок остаётся обычным телом текста. На экспорте титул вклеивается внутрь финального текста первого юнита (export.go:209-215), причём колонка исходника под--pairsостаётся без него. Следствие для читалки: у первой пары каждой главы колонки асимметричны ПО ПОСТРОЕНИЮ. Наша фикстура рисует «Раздел N. …» в дереве и неснятый «第二节:» в оригинале — ни одной из этих форм движок для zh→ru не порождает. Не чинил: форма заголовка — вопрос контракта (К-2), правка до ответа дала бы пятую выдумку вместо четвёртой. Хвост в Ф-14, продуктовая половина — В-4. - Автоинкремент банка стабильным ключом не является — это сказано в самой схеме:
«glossary.id is a fresh autoincrement each replace and is NEVER hashed» (
migrate.go:172-174), потому что банк книги ЗАМЕНЯЕТСЯ целиком на каждом прогоне. То есть просьба «дайтеid» без этого уточнения была бы удовлетворена полем, которое ломается ровно так же, как сегодняшняя заплата из (src,dst,type). - Стоп подписи снимается ТОЛЬКО полным набором решений: «the stop clears once every
proposed term is promoted or rejected» (
mining.go:200-202). Отсюда три требования к экрану S5, которых в Ф-14 не было: счётчик «решено N из M», запрет «продолжить» при неполном наборе и обязательное частичное сохранение (сотни терминов в один присест не подписываются). - Номер главы — не идентификатор: пустая глава не потребляет номер (
chunker.go:101-118), значит правка исходника сдвигает нумерацию всех последующих глав, а персиста манифеста нет (строка 100). Дерево и открытые вкладки живут по ключу — после пере-разбора под ключом оказался бы другой текст. - Разрешающий список вместо вычитания. Инвентаризация
StatusReport(status.go:58-130) иChapterPassport(status.go:37-56) показала, сколько запрещённого лежит в операторских структурах: пять денежных полей плюсcost_usdу главы (§4.8 — денег в MVP нет вообще),routingвида «stage=model» иcontent_labels(ПТ-33 — имена моделей и стадий), снапшот, дрифт и ре-билл. Просьба сформулирована как allowlist, потому что при вычитании ответ на ревью-вопрос контракта («сменится стадия конвейера — придётся ли править фронт?») держится, пока кто-то помнит список.
Второй заход той же сессии: черновик контракта (решение владельца). Владелец спросил,
откуда контракт вообще должен взяться, и почему сессия остановилась. Разбор доков дал
механику: дом файла — зона оркестратора; колонка «Чем закрывается» строки 95 = «дизайн-промт
по слову владельца»; промты пишет оркестратор, сессии стартует владелец
(ORCHESTRATOR_SESSION_PROMPT.md, «Кто ты»); активного промта на 95 нет
(docs/README.md:18). То есть от владельца требовалось одно решение — запускать. Владелец
выбрал третий путь: черновик пишет фронт, оркестратор ревьюит и переносит.
Здесь я был слишком буквален в первом заходе и говорю это прямо: запрет шапки BACKLOG.md
— про право ЗАВОДИТЬ контракт своим решением, а не про право написать черновик, и строка 95
сама называет фронт одной из трёх своих зон. Остановка на входе была защитой процесса там,
где можно было предложить работу.
API_CONTRACT_DRAFT.md — 12 разделов плюс приложение: версия и правило эволюции · ресурсы
чтения · продуктовый словарь статусов · продуктовый словарь замечаний · пофазный прогресс ·
SSE со словарём событий · действия · подпись банка · разрешающий список полей · зависимости
от строк 99–103/П-1 · семь открытых вопросов · проверка ревью-вопросом строки 95
(«сменится стадия конвейера — придётся ли править фронт?» — прогон по шести изменениям, «да»
ровно одно и ровно то, которое должно быть «да»). Каждое место помечено: ✓ выведено из кода ·
◆ предложено фронтом · ○ открыто.
Двухфазный поток выведен, а не спроектирован, как и требует строка 95: --verify-bank —
параметр запуска (invocation.go:108), без него прогон «carries the unsigned bank forward
marked ⟨проверить⟩» (render.go:92), то есть это выбор пользователя «я подпишу» ↔ «переводи
как есть», а не техническая настройка.
Что нашла самопроверка черновика: в лестнице статусов строки 95 («загрузка → разбор →
перевод → подпись банка → финал → готово») нет состояния покоя между разбором и запуском,
а библиотека обязана показывать разобранную книгу, которую ещё не запускали. Без него пришлось
бы врать статусом parsing на давно разобранной книге. Заведено not_started, дыра названа
в самом черновике вслух, а не залечена молча. Заодно сегодняшние девять значений типов фронта
разводятся на десять: uploading · parsing · not_started — три разные вещи, которые
пользователь различает.
Третий заход: дофикс по ревью оркестратора и смена формы. Ревью приняло черновик
в работу и вернуло два блокера, паттерн из восьми ✓-инфляций, список фактических правок и
решение владельца о форме. Все проверяемые клеймы ревью я сверил по коду ДО правки — норма
«не идти по следам отчёта»; подтвердились все, включая неочевидный фантом auto.
Что сделано:
- Форма. Нормативная поверхность — OpenAPI 3.1 (
docs/api-contract/openapi.yaml), md стал спутником. Три пина живой сверкой:@stoplight/spectral-cli6.16.3 (релиз 03.08),openapi-typescript7.13.0,msw2.15.0.npm run checkстал шестишаговым. - Блокер 1 — состояние пары. Первая редакция утверждала «
withheld= текст не выдан» как факт о движке; ложно: флагнутый юнит легально приходит С ТЕКСТОМ — косметическая зачистка санитайзера (disposition.go:96-104) и c-lite member-drop (export.go:203-207). Переопределено: состояние выводится из ПАРЫ (вердикт, наличие финального текста). Пример был невоспроизводим —translated+glossary_missне порождается ничем: при включённом гейте текст удерживается (export.go:295-300), при выключенном мисс живёт в retrieval-state, закрытом собственным же разрешающим списком. Заменён наsanitizer_stripped. Дописано правило свежести: текст обновляется на границах стадий и стопах — посреди прогона канала чтения не существует. - Блокер 2 — чтения без канала. У
GET /bankсегодня нет канала вообще: строка 101 даёт таблицу ПОДПИСИ (кап 20,cmd/tmctl/render.go:98), а не экспорт банка; сам банк в приватном SQLite, платформе запрещённом. Не решал — помечено зависимостью, строку заводит оркестратор. То же у событияnote. - Восемь ✓-инфляций пере-размечены. Паттерн один: пометка ставилась на РАЗДЕЛ, хотя
у пункта половина выведена, а половина предложена. Главные: ревизия на ЧТЕНИЯХ и «один
счётчик» — это просьба фронта (◆), выведен только апсерт
(run_id, seq); полеheading— ◆, движок делает обратное; имена полей банка — ◆ при ✓-словарях;resumeперестал быть «резервом», раз сам же черновик делает его носителем снятия стопа. - Ложный друг устранён: движковое
source— это ПРОВЕНАНС, а первая редакция отдала имяsourceдругой колонке. Теперь провенансorigin, формы терминаsrc/dst. - Фантом
autoубран по путям записи: комментарий схемы (migrate.go:193) устарел, литералы"auto"в коде пишут СТАТУС (membank/memseed.go:328:Status:"auto", Source:"ruby"). Та же поправка внесена в строку этого журнала от 02.08. - Фактические правки:
upstream_not_okдобавлен пятнадцатой причиной (ранг по умолчанию) · ПТ-34 заведён отдельным пунктом контракта · стоп по потолку разведён сfailed(у движка это штатный резюмируемый стоп,stagerun.go:488-489) · правило разрываLast-Event-ID(сигналresync_required, реплей истории запрещён) · §12 про чанкер смягчён · путь цитаты уточнён доcmd/tmctl/render.go:92(в репо дваrender.go) · посылка «ETA не запрошено» снята — ПТ-19 существует (product-requirements.md:49). - Чужого не решал: русские фразы карты вердиктов помечены плейсхолдерами до слова
владельца, граница ступеней замечания оформлена вопросом (К-6), движковые работы — только
зависимостями. Новый К-9: отказ прескрина (строка 94) не выразим ни одним из десяти
статусов — это не
rejected(тот про неразобранный файл) и неfailed.
Что стоило отдельной работы — пир генератора. openapi-typescript объявляет
typescript@^5.x, у нас намеренный TS 6. --legacy-peer-deps отвергнут проверкой,
а не вкусом: после него обычный npm install падает, то есть ломается дерево у каждой
следующей сессии и у prepare-инсталлера хука. Взят точечный overrides на один пакет;
npm ci с нуля проходит за 22 с, 0 уязвимостей, генератор работает на TS 6. Хвост — Ф-23.
AsyncAPI рассмотрен и отложен с записанной причиной — Ф-24.
Самопроверка исполнением (мандат): spectral зелёный в check · типы генерятся и
собираются · MSW-хендлер компилируется от генерённых типов и отдаёт тело в тесте · четыре
формы нарушения гейта проверены живьём, откат копией файла, не git checkout.
Попутно поймано ложное срабатывание чужого гейта: контракт-тест токенов принял флаг
движка в доккомментарии генерённого файла (`--verify-bank`) за имя CSS-переменной.
Починено по классу — генерённый файл исключён из скана с названной причиной, остаточная
граница записана честно. Тестов стало 80 (было 77).
⚠ Свою же ошибку фиксирую: первое падение теста дрифта я объяснил перехватчиком MSW —
неверно. Настоящая причина: окружение тестов happy-dom, import.meta.url становится
http://localhost:3000/…, и резолвер генератора идёт за спекой по сети. Тест сделан
герметичным, комментарий исправлен, чтобы не врал о причине следующему читателю.
Ревью исполнением. Кода в первом заходе не было, поэтому ревью исполнением — это (а) проверка
замка командами, а не чтением промта; (б) чтение движка вместо цитирования доков: каждая
строка-грунт открыта в файле (migrate.go, status.go, export.go, chunker.go, ingest.go,
mining.go, disposition.go, banknote.go, classify.go, main.go, book.go, langpack.go,
heading.txt); (в) npm run check:full прогнан на переданном дереве и зелёный; (г) снимок
.shots/showcase.png открыт и просмотрен — на нём и найдено расхождение №1.
Чего сессия НЕ делала и почему: S3 (src/api/ асинхронен, MSW, фикстуры всех состояний,
EventSource, приведение типов) — замок; Ф-16, Ф-17, Ф-18, Ф-20 — у каждой свой триггер,
ни один не наступил; фикстуру заголовков — см. пункт 1.
Переименование по слову владельца: frontend/docs/PROGRESS.md → frontend-PROGRESS.md,
чтобы имя не совпадало с журналом оркестратора. Ссылки в своей зоне обновлены (README.md,
FRONTEND_SESSION_PROMPT.md, S2_SESSION_PROMPT.md, S3_SESSION_PROMPT.md,
FRONTEND_PLAN.md, src/mock/book.ts). Битых ссылок вне зоны нет — проверено grep'ом по
всему репозиторию: на frontend/docs/PROGRESS.md не ссылался ни один файл за пределами
frontend/, а упоминания docs/PROGRESS.md в CLAUDE.md и промтах других зон относятся
к журналу оркестратора и остаются верными. Оркестратору остаётся не починка ссылок,
а соглашение: прогресс фронта пишется в этот файл.
Заодно поправлено устаревшее число в Ф-11: там стояло «5 узлов» контраста, живой прогон
check:full печатает по 4 на каждом маршруте (и промт S3 говорит «на четырёх» — то есть
устарела строка бэклога, а не измерение).
⚠ Расхождение промта S3 с каноном, найденное и снятое владельцем в ходе сессии. Промт
велел отдать артефакт «пингом в docs/PROGRESS.md» — но зона записи фронта это только
frontend/, а шапка этого журнала фиксирует решение владельца 02.08 «в docs/PROGRESS.md
оркестратора не пишут». Вопрос задан владельцу, ответ: у фронта свой журнал, писать сюда;
оркестратор по прогрессу фронта пишет в этот же файл. Записано в шапке.
04.08 — ревью оркестратора по S2 (ece94ef): что дофиксили
Ревью принято целиком; каждая находка проверена по коду движка ДО правки, а не по формулировке ревью. Три главных:
- Токен
--font-cjkнарушал собственный пин «CJK — системный стек + обязательныйlang» (STACK_DECISIONS.md§2): в нём стояли именованные гарнитуры, и все три — упрощённый китайский (Noto Sans CJK SC,PingFang SC,Microsoft YaHei). Именованный фейс перекрывает языковой подбор браузера, то есть японские кандзи получили бы китайские начертания (унификация Хань) при живом и правильномlang="ja". Из двух предложенных лечений выбрано первое — снять фейсы, а не скоупить:lang(): языковая ветка в CSS — это ровно та пар-специфика в коде, которую запрещает §2 канона, и новой паре понадобилась бы правка стилей. Токен удалён, у колонки оригинала гарнитуры больше нет: подбор делает браузер поlangиз данных пары. orgвыкинут изBankTerm['kind']. Движок его не знает: закрытый словарь —terminology/classify.go:15(четыре типа) иpipeline/banknote.go:74(те же плюсnickname). Исправлена и строка журнала, из которой этот тип приехал: она приписывала переченьmigrate.go:176, где перечня нет — там свободный текст и открытый комментарий.- Ключ ряда банка был
term.source— это ломается на полисемии: у движка термин уникален по (src,sense,since_ch,until_ch), и один иероглиф законно приходит несколькими строками. До приезда полей (Ф-14) ключ составной из того, что есть.
Мелочь оттуда же: ранг content_filter в карте вердиктов = 0, не 1 (status.go:152 — он в одной
группе с отказами); в текущем состоянии висели два счёта тестов сразу (31 и 62) — снят устаревший;
у пары r2-2 замечание «подписанный термин не применён» было фантомным — в тексте промаха не было,
теперь он там настоящий («Фан Юэнь» против подписанного «Фан Юань»), иначе витрина показывала
предупреждение о том, чего на экране нет.
Хвосты в гейты, тоже из ревью (каждая форма проверена живым нарушением — 7 падений и 4 прохода
одним прогоном, FRONTEND_PLAN.md §5.4.1): no-restricted-imports видит только статический
импорт, поэтому три шва (моки · глобальный CSS · библиотека примитивов) продублированы селекторами
на ImportExpression — с теми же точечными послаблениями, что у статической половины; в гейте
CSS-переменной закрыты носители литерала — тернарник и шаблонная строка. Плюс generality.test.ts
получил вторую половину: языковая ветка :lang(…) в стилях теперь падает тестом, а не остаётся
на глазах ревьюера, и в самом тесте названа граница — значения стилей он по-прежнему не видит
(именно там и пряталась находка №1).
04.08 — третья фронт-сессия (S2): оболочка трёх панелей, слой src/ui/, масштаб и гейты
Построено.
src/shell/— верхняя полоса · три панели наreact-resizable-panels4.12.2 · статус-полоса · сворачивание левой и правой кнопками полосы · персист раскладки черезuseDefaultLayout.Panel— скруглённая карточка с рядом вкладок, телом и прибитым к низу подвалом; ориентир страницы у каждой панели свой (nav·main·aside), иначе документ остаётся без главной области и гейт axe это ловит.src/ui/— семь примитивов наreact-aria-components1.20.0, и библиотека видна ТОЛЬКО отсюда (теперь машинно):Button(два вида: строка и иконка) ·Tabs(ряд + область содержимого- «+») ·
Tree(виртуализованное дерево) ·List(виртуализованный список) ·FilterField·Callout(выноска замечания) ·Locale(русские служебные строки библиотеки). Вид задаётся атрибутомdata-*, а не набором классов: так имя класса остаётся одно и опечатка в нём ловится структурным тестом, а не уезжает вclassName="undefined".
- «+») ·
- Витрина переехала на настоящую оболочку и разложена на наполнение трёх панелей
(
Library·Reader·Context·Blank). Второй маршрут/scale— тот же экран на настоящем масштабе книги. - Состояние интерфейса —
zustand5.0.14 сpersist, ровно на свёрнутость панелей. Больше в сторе ничего нет: вкладки и открытые документы живут на уровне экрана, и заводить под них глобальное состояние сейчас было бы абстракцией «на будущее».
Что видно на снимках (.shots/showcase.png, .shots/scale.png, .shots/collapsed.png —
все три открыты и просмотрены). Оболочка сошлась с fleet.png там же, где сходилась витрина S1:
поле 8 · промежутки 8 · радиус 6 · шаг строки 26 · верхняя полоса 28 · статус-полоса 20. Добавились
вещи, которых у S1 не было: «+» в ряду вкладок слева, крестик у вкладки-документа, правый край
строки дерева (метка состояния, счётчик замечаний, полоска выполнения) прижат к краю панели.
На /scale дерево показывает 2284 раздела, банк — 1200 терминов, счётчик подписи «800 из 1200».
На collapsed.png правая панель ушла вместе со своим разделителем, поле оболочки справа осталось
замеренными 8px.
Ф-12 закрыт замером, а не обещанием (детали — BACKLOG.md): на 2293 узлах дерева в DOM
держится 31 строка, кадр при прокрутке — медиана 22 мс, p95 31 мс; банк на 1200 терминах —
30 строк, медиана 16,7 мс. Прокрутка до конца и обратно счётчик строк не растит. Отсюда же
отрицательный замер по Ф-6: @tanstack/react-virtual не нужен, RAC Virtualizer держит.
Ф-9 и Ф-10 закрыты, Ф-4 включён — все три перечнем форм, каждая проверена живым нарушением
(15 форм TSX + 14 CSS + 7 структурных, список — FRONTEND_PLAN.md §5.4.1). Из нового:
запрещён носитель style в объектных литералах (это и закрывает JSX-спред, который S1 записала
как непокрываемую дыру), закрыты системные цветовые ключевые слова во ВСЕХ свойствах, включая
сокращённые записи, и заведён замок полноты токенов — новый токен обязан попасть либо в замеры,
либо в явный список подобранных.
Где отошёл от референса и почему.
- Крестик у вкладки-документа — не кнопка. У роли
tabдети презентационные (ARIA), поэтому вложенный фокусируемый элемент выпадает из дерева доступности, оставаясь в порядке табуляции; axe роняет на этом сборку правиломnested-interactive, и это не придирка инструмента, а спека. Мышь закрывает крестиком, клавиатура —Deleteна выбранной вкладке. Объявить сочетание скринридеру нечем — строка Ф-17. - Иконка выгрузки ушла из верхней полосы до S7: в витрине S1 она висела без действия, а правило Ф-7 «кнопка только вместе с действием» относится не только к «+». Строка Ф-16.
- У книг без глав нет шеврона. В фикстуре у них честный ноль разделов, и рисовать шеврон значило бы обещать содержимое, которого нет; витрина S1 его рисовала.
- Крестик резервирует место всегда (
visibility: hidden, неdisplay: none), поэтому имя вкладки не прыгает при наведении — но ряд вкладок выходит шире, чем во Fleet. - Три вкладки-документа — заготовки (
Blank): «Добавить книгу», «Банк памяти», «Настройки». Это форма пустого состояния изantigravity_main.png, а не экраны: их строят S4, S5 и S7. Заготовки существуют ровно потому, что действия оболочки обязаны быть настоящими. - Замечания не виртуализованы — обоснование в Ф-12: 10³ замечаний в одной главе не бывает по построению.
Ревью исполнением (не заявлением): приложение поднято в браузере и прокликано скриптом —
три панели по 320px · сворачивание убирает панель из раскладки · перетаскивание разделителя
до 410px переживает перезагрузку · свёрнутость переживает перезагрузку · клик по разделу
открывает вкладку · крестик и Delete закрывают · при нуле вкладок ряд не рисуется вовсе
и видно пустое состояние · «+», «Подписать банк», «Настройки» и лупа делают ровно то, что обещают ·
фильтр банка сужает список и показывает своё пустое состояние.
⚠ Три «находки» первого прогона зонда оказались дефектами зонда, а не кода: селектор
[role="tab"][data-selected] бил по всем трём панелям сразу, поэтому «крестик не закрывает»
и «Delete не закрывает» были ложными; «в пустом фильтре остался узел» — это сам renderEmptyState
библиотеки, обёрнутый в role="option". Записано, потому что соблазн поверить первому красному
результату и «починить» рабочий код здесь был вполне реальный.
Адверсариальное агентское ревью диффа (5 линз × независимые находки, на каждую из пяти тяжелейших — по два скептика с установкой опровергать; 20 находок). Что оно поменяло в коде:
| Находка | Что сделано |
|---|---|
| Статус-полоса печатала названия фаз конвейера («черновик 214/4276 · редактура 0/4276») — это раскрытие устройства бэкенда, запрещённое §4.1, и прямое нарушение собственного правила плана (§6: «одна полоса без названий фаз») | считается одна доля по обеим волнам сразу: «переведено 3%». Пофазность осталась там, где ей место — в данных и в счёте, иначе доля стояла бы нулём всю черновую волну |
У терминов банка потерян lang — регресс против S1: японские кандзи отрисовались бы китайскими начертаниями |
lang вернулся из данных книги; заодно фильтр перестал быть регистрозависимым с одной стороны и регистронезависимым с другой |
| Перевод «важность вердикта → вид выноски» продублирован дословно в двух экранах: новый вид замечания правил бы пять файлов вместо одного — прямой промах по контрольному вопросу владельца | одна функция noteTone в showcase/format.ts, оба экрана зовут её |
| Лупа в верхней полосе при свёрнутой левой панели — мёртвое нажатие: вкладка переключалась, но её не было видно (проверено побайтовым сравнением кадров) | лупа разворачивает панель тем же нажатием; в сторе появилось expand |
| Кнопки сворачивания не несут состояния: имя всегда «Свернуть …», иконка одна | имя меняется: «Свернуть» ↔ «Развернуть» |
| Комментарий конфига утверждал симметрию TSX- и CSS-половин гейта, которой нет: именованное отключение в TSX проходит, в CSS падает | комментарий говорит правду, асимметрия названа |
Panel переписывал пропс-поверхность Tabs слово в слово |
наследует её Omit-ом |
| Гейт цветных атрибутов ловит только голый литерал | добавлена шаблонная строка; вычисленное значение синтаксически не берётся — граница названа в §5.4.1 и в самой строке Ф-9, вместо «закрыто без остатка» |
Что ревью нашло, а S2 сознательно НЕ чинила — с диспозицией, а не молчанием: переключение вкладки размонтирует её содержимое и теряет прокрутку длинного списка (Ф-19, замерено дважды независимо: 70–140 мс на пересборку 2284 узлов) · мёртвый моноширинный шрифт в бандле (Ф-20) · ступень замечания различается только цветом полоски (Ф-21 — нужно слово, а слово продуктовое, его выбирает владелец) · два хвоста Ф-12 (группировка дерева — S4, разделение банка — S5).
Опровергнуто скептиками (и это ценно ровно так же): «закрыть вкладку можно только мышью» —
неверно, Delete работает, а отсутствие aria-keyshortcuts уже лежало строкой Ф-17 с причиной;
«потеря текста в полях фильтра при сворачивании панели» — поле слева заглушка до S6, поле справа
фильтр, а не введённые данные.
Замеры, которые стоит знать дальше: бандл 545 КБ / 168 КБ gzip одним куском после установки RAC — строка Ф-18; резать его осмысленно после S4–S7.
02.08 — вторая фронт-сессия: ревью скелета свежим взглядом + защита коммитов
Ревью S0/S1 подтверждает состояние: check (5 гейтов, 31 тест), build (377 мс, Rolldown)
и npm audit (0 уязвимостей) зелёные; конфиги перечитаны построчно — расхождений с доками
не найдено; известные хвосты гейтов уже честно лежат в Ф-9/Ф-10, не дублировал.
Главная дыра скелета была не в коде, а вокруг него: CI нет (проверено: ни .github/,
ни других CI-конфигов), git-хуков нет — то есть вся построенная S1 система гейтов работала,
только если сессия сама вспомнит про npm run check. При этом START_PROMT.MD трекается
и почти всегда модифицирован — голый git commit -a унёс бы его молча.
Закрыто pre-commit хуком (запрос владельца; отменяет «Git-хуков в MVP нет» из
STACK_DECISIONS.md §3 — та строка писалась в паре с CI, которого нет):
- зонный фрагмент
scripts/githooks/pre-commit(трекается): для коммитов с frontend-путями — тот жеnpm run check(~10 сек), не дубль списка инструментов; для ЛЮБОГО коммита — блок файлов «никогда не коммитить» (START_PROMT.MD,.claude/settings.local.json) и блок смеси frontend/ с чужой зоной — машинное принуждение D39.88 (легитимной смеси не существует: фронт коммитит только свою зону, чужие зоны frontend/ не коммитят); - локальный диспетчер
.git/hooks/pre-commit(не в git) зонно-нейтрален: подхватывает<зона>/scripts/githooks/pre-commitлюбой зоны без правки себя; ставится инсталлеромinstall.mjsиз npmprepare— каждыйnpm installсам обновляет защиту; - проверено девятью сценариями в изолированном клоне: чужая зона проходит мгновенно · запрещённый файл блок · смесь зон блок · литеральный цвет в TSX валит check и блок · чистый коммит проходит · pathspec-коммит при чужом застейдженном файле не уносит чужое (временный индекс git виден хуку корректно) · повторная установка идемпотентна · чужой pre-commit не перетирается · вне git-репозитория тихий пропуск;
- догфудинг по мандату самопроверки: хук поймал ошибку в собственном инсталлере
(TS7006 в
install.mjs— strict-тайпчекcheckJsдотягивается и доscripts/).
Мелочи той же сессии: два каретных пина (^4.12.1 axe, ^4.7.2 eslint-comments)
приведены к точным — единственное расхождение с политикой пинов §1; заведён .npmrc
(engine-strict — несовпадение Node падает на установке, а не непонятно дальше;
save-exact — карет не появится при доустановке).
Заленжено: e9a6bb2 план S0 · b98afb5 весь код S1 · d8437d6 закрытие дыр после ревью.
Построено: каркас Vite 8.2.0 + React 19.2.8 + TS 6.0.3 точными пинами (22 из 23 совпали
с latest на 02.08; TS 6 при latest 7.0.2 — намеренно, у TS 7 нет программного API);
npm run check из пяти шагов; скриншот-цикл на Playwright без sudo, с локальными системными
библиотеками и шрифтом CJK в .tooling/; tokens.css по замерам референса; витрина.
Поправка к замерам промта §1.1: роль #353739 там смешана. В кадре это разные вещи —
вкладка открытого документа залита #142f4c, вкладка панели #27292b, а #353739 лежит
только под строкой дерева и клавишами-чипами. В токенах роли разведены.
Сведение с референсом: промежутки, верхний край панелей, радиус, шаг строки 26px, подложка
выделения 24px и нижняя полоса сошлись с fleet.png до пикселя (перемерено тем же кодом).
Расхождения названы в FRONTEND_PLAN.md §5.2, вид принят владельцем.
Адверсариальное агентское ревью диффа (6 ревьюеров по дименсиям + по 2 опровергателя
на находку; 30 находок, 18 выживших). Пять дефектов были в самих гейтах — то есть в главной
поставке S1 — и соло-самопроверка их не видела: голое /* stylelint-disable */ снимало гейт
цвета молча; опечатка в имени токена не ловилась ничем; shot.mjs проглатывал первый маршрут;
запрет инлайнового стиля ловил 3 формы из 11; check был зелёным при warning. Всё починено
и перепроверено живыми нарушениями, перечень форм — FRONTEND_PLAN.md §5.4.
Урок, вписанный в §5.4: формулировка «проверено живым нарушением» без перечня форм — ловушка. Она звучит как машинная гарантия, а покрывает ровно те входы, которые придумал автор.
Ф-8 закрыт технически, решение владельца не потребовалось. Вопрос был «запрещать ли
/* eslint-disable */ в TSX». Отраслевая практика — не бинарный noInlineConfig, а
@eslint-community/eslint-plugin-eslint-comments 4.7.2: no-unlimited-disable требует называть
правило, require-description — писать причину, disable-enable-pair — закрывать область.
Легитимное точечное подавление react-hooks/exhaustive-deps с причиной проходит, голое
отключение падает тремя ошибками. Симметрично reportUnscopedDisables на стороне CSS.
Git-инцидент: два первых коммита (e9a6bb2, b98afb5) сделаны голым git commit и унесли
застейдженные файлы оркестратора. Содержимое цело, потеряна атрибуция; историю не переписывали.
Норма ратифицирована владельцем как D39.88 — коммит только pathspec-формой с явным списком путей.
Второй git-урок, 02.08: откатывая пробное нарушение гейта, я сделал git checkout -- по
файлу, в котором лежали НЕЗАКОММИЧЕННЫЕ правки, — и стёр их все. Восстановил из контекста,
чужого не задело. Запрет «никакого checkout поверх грязного дерева» (CLAUDE.md) существует
ровно для этого случая и относится к своим файлам тоже. Пробу отката делать копией
(cp до, cp после), а не через git.
Пинг оркестратора №15 (09.08): контракт 0.2.1 — 503 на старте прогона; платформа-раннер заленден
- Контракт бампнут 0.2.0 → 0.2.1 (D39.123):
POST /books/{bookId}/runsполучил ответ503(ServiceUnavailable, problem+json) — старт прогона на деплое, где шов движка не сконфигурирован; временное состояние, ретрай позже,Retry-Afterне обещан. Зеркалоfrontend/docs/api-contract/синхронизировано байт-в-байт этим же лендингом. При следующей сессии — перегенерировать типы из спеки; других изменений форм нет, правка аддитивная. - Платформа-раннер заленден (
d29e30c): пять ручек/v0контракта живые — библиотека · карточка · run-options · старт прогона · usage; прогресс прогона пофазный (draft/edit) в карточке. Фронт может говорить с живой платформой вместо моков — стык проверен 47 wire-проверками приёмки. - Известное ограничение до эмиттера (строка 103): стоп по потолку и штатный останов приезжают как
failed(PD-113/двойник) — на экране не строить различение этих состояний до строки 165 движка.