1037 lines
128 KiB
Markdown
1037 lines
128 KiB
Markdown
# Журнал зоны «Фронт»
|
||
|
||
> **Файл переименован 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-слоя в ней нет вовсе, preflight `OPTIONS` с чужим `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 выдаёт оркестратор по слову владельца.
|
||
|
||
> **Запись оркестратора №16, 09.08 — список владельца ПРИШЁЛ, промт S3.5 ВЫДАН.** Абзац выше
|
||
> исполнен: полный список претензий (24 замечания, снятые владельцем с живого прогона витрины)
|
||
> получен 09.08 и завёрнут в промт **`S35_SESSION_PROMPT.md`** (эта папка): VS Code-модель
|
||
> вкладок + Ф-19 + все 24 номера блоками, приёмка кадрами ДО/ПОСЛЕ и playwright-сценариями,
|
||
> референс драг-ручки — vojo (`src/app/components/page/Page.tsx:191-300`, `style.css.ts:11-70`).
|
||
> Запуск сессии — по слову владельца; S4 остаётся гейчен до приёмки S3.5. Попутное: замечание
|
||
> (10) владельца независимо подтвердило Ф-21 — полоска замечания нечитаема без слова; само
|
||
> слово ступени — по-прежнему В-3 на владельце. Референс `antigravity_add_folder.png` докинут
|
||
> владельцем 09.08 (модал добавления книги, замечание (12)).
|
||
|
||
> **Запись фронт-сессии 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` перед любым кодом стыка. Не читал,
|
||
и это стоило двух вещей.
|
||
|
||
1. **D39.85 (`research/23` §0): фронт читает ТОЛЬКО read-модель платформы в Postgres,
|
||
материализованную из NDJSON-потока событий; движок не опрашивается никогда, чтение живого
|
||
SQLite движка — запрещённый анти-паттерн.** Значит формы `glossary`/`ChunkExport` — словарь
|
||
предметной области, но НЕ форма API. Типы фронта переписаны так, чтобы это было видно:
|
||
`src/api/types.ts` объявлен рабочей гипотезой до появления контракта.
|
||
2. **Единый бэклог, строка 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` числа стоят, а полоса говорит «живой
|
||
прогресс недоступен» — чтения при этом работают. Это замер в браузере, а не вывод из кода:
|
||
скриншот снимается раньше первых кадров и такого доказательства не даёт.
|
||
- **Виртуализация пережила смену слоя данных:** на `/scale` 2284 главы прочитаны страницами по 500
|
||
и склеены до конца, в DOM держится 41 строка при `scrollHeight` 59 410.
|
||
|
||
#### Что нашёл сам скриншот-цикл — и почему это стоит того, чтобы смотреть
|
||
|
||
Три дефекта, и **ни один не был бы найден чтением кода**; каждый сначала увидели на кадре.
|
||
|
||
1. **Выключенный запрос TanStack Query остаётся `pending` НАВСЕГДА.** На `/empty` правая панель
|
||
вечно показывала «Банк памяти · Идёт загрузка» — библиотека пуста, книги нет, запрос выключен,
|
||
а экран обещал, что данные вот-вот приедут. Лечение — различать «ждём» и «выключено»
|
||
(`fetchStatus === 'idle'`) на шве отображения, одним местом.
|
||
2. **Соседний класс того же дефекта:** пока библиотека ещё летит, зависимые панели объявляли
|
||
«Книга не выбрана» — тоже неправда, просто с другой стороны. Панель, чей запрос выключен ТОЛЬКО
|
||
потому, что не приехала предпосылка, обязана говорить «жду».
|
||
3. **Центр на `/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 плана** (собрать было
|
||
велено, а не сочинять заново — но пять вещей нашлись только чтением движка):
|
||
|
||
1. **Титульная политика 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.
|
||
2. **Автоинкремент банка стабильным ключом не является** — это сказано в самой схеме:
|
||
«glossary.id is a fresh autoincrement each replace and is NEVER hashed» (`migrate.go:172-174`),
|
||
потому что банк книги ЗАМЕНЯЕТСЯ целиком на каждом прогоне. То есть просьба «дайте `id`»
|
||
без этого уточнения была бы удовлетворена полем, которое ломается ровно так же, как
|
||
сегодняшняя заплата из (`src`,`dst`,`type`).
|
||
3. **Стоп подписи снимается ТОЛЬКО полным набором решений:** «the stop clears once every
|
||
proposed term is promoted or rejected» (`mining.go:200-202`). Отсюда три требования
|
||
к экрану S5, которых в Ф-14 не было: счётчик «решено N из M», запрет «продолжить» при
|
||
неполном наборе и обязательное частичное сохранение (сотни терминов в один присест
|
||
не подписываются).
|
||
4. **Номер главы — не идентификатор:** пустая глава не потребляет номер (`chunker.go:101-118`),
|
||
значит правка исходника сдвигает нумерацию всех последующих глав, а персиста манифеста нет
|
||
(строка 100). Дерево и открытые вкладки живут по ключу — после пере-разбора под ключом
|
||
оказался бы другой текст.
|
||
5. **Разрешающий список вместо вычитания.** Инвентаризация `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-cli` 6.16.3 (релиз 03.08),
|
||
`openapi-typescript` 7.13.0, `msw` 2.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`): что дофиксили
|
||
|
||
Ревью принято целиком; каждая находка проверена по коду движка ДО правки, а не по формулировке
|
||
ревью. Три главных:
|
||
|
||
1. **Токен `--font-cjk` нарушал собственный пин** «CJK — системный стек + обязательный `lang`»
|
||
(`STACK_DECISIONS.md` §2): в нём стояли именованные гарнитуры, и все три — упрощённый
|
||
китайский (`Noto Sans CJK SC`, `PingFang SC`, `Microsoft YaHei`). Именованный фейс перекрывает
|
||
языковой подбор браузера, то есть японские кандзи получили бы китайские начертания
|
||
(унификация Хань) при живом и правильном `lang="ja"`. **Из двух предложенных лечений выбрано
|
||
первое — снять фейсы, а не скоупить `:lang()`:** языковая ветка в CSS — это ровно та
|
||
пар-специфика в коде, которую запрещает §2 канона, и новой паре понадобилась бы правка стилей.
|
||
Токен удалён, у колонки оригинала гарнитуры больше нет: подбор делает браузер по `lang`
|
||
из данных пары.
|
||
2. **`org` выкинут из `BankTerm['kind']`.** Движок его не знает: закрытый словарь —
|
||
`terminology/classify.go:15` (четыре типа) и `pipeline/banknote.go:74` (те же плюс `nickname`).
|
||
Исправлена и строка журнала, из которой этот тип приехал: она приписывала перечень
|
||
`migrate.go:176`, где перечня нет — там свободный текст и открытый комментарий.
|
||
3. **Ключ ряда банка был `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-panels` 4.12.2 · статус-полоса ·
|
||
сворачивание левой и правой кнопками полосы · персист раскладки через `useDefaultLayout`.
|
||
`Panel` — скруглённая карточка с рядом вкладок, телом и прибитым к низу подвалом; ориентир
|
||
страницы у каждой панели свой (`nav` · `main` · `aside`), иначе документ остаётся без главной
|
||
области и гейт axe это ловит.
|
||
- **`src/ui/`** — семь примитивов на `react-aria-components` 1.20.0, и библиотека видна ТОЛЬКО
|
||
отсюда (теперь машинно): `Button` (два вида: строка и иконка) · `Tabs` (ряд + область содержимого
|
||
+ «+») · `Tree` (виртуализованное дерево) · `List` (виртуализованный список) · `FilterField` ·
|
||
`Callout` (выноска замечания) · `Locale` (русские служебные строки библиотеки). Вид задаётся
|
||
атрибутом `data-*`, а не набором классов: так имя класса остаётся одно и опечатка в нём ловится
|
||
структурным тестом, а не уезжает в `className="undefined"`.
|
||
- **Витрина переехала на настоящую оболочку** и разложена на наполнение трёх панелей
|
||
(`Library` · `Reader` · `Context` · `Blank`). Второй маршрут **`/scale`** — тот же экран
|
||
на настоящем масштабе книги.
|
||
- **Состояние интерфейса** — `zustand` 5.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 записала
|
||
как непокрываемую дыру), закрыты системные цветовые ключевые слова во ВСЕХ свойствах, включая
|
||
сокращённые записи, и заведён замок полноты токенов — новый токен обязан попасть либо в замеры,
|
||
либо в явный список подобранных.
|
||
|
||
**Где отошёл от референса и почему.**
|
||
|
||
1. **Крестик у вкладки-документа — не кнопка.** У роли `tab` дети презентационные (ARIA), поэтому
|
||
вложенный фокусируемый элемент выпадает из дерева доступности, оставаясь в порядке табуляции;
|
||
axe роняет на этом сборку правилом `nested-interactive`, и это не придирка инструмента, а спека.
|
||
Мышь закрывает крестиком, клавиатура — `Delete` на выбранной вкладке. Объявить сочетание
|
||
скринридеру нечем — строка Ф-17.
|
||
2. **Иконка выгрузки ушла из верхней полосы** до S7: в витрине S1 она висела без действия,
|
||
а правило Ф-7 «кнопка только вместе с действием» относится не только к «+». Строка Ф-16.
|
||
3. **У книг без глав нет шеврона.** В фикстуре у них честный ноль разделов, и рисовать шеврон
|
||
значило бы обещать содержимое, которого нет; витрина S1 его рисовала.
|
||
4. **Крестик резервирует место всегда** (`visibility: hidden`, не `display: none`), поэтому имя
|
||
вкладки не прыгает при наведении — но ряд вкладок выходит шире, чем во Fleet.
|
||
5. **Три вкладки-документа — заготовки** (`Blank`): «Добавить книгу», «Банк памяти», «Настройки».
|
||
Это форма пустого состояния из `antigravity_main.png`, а не экраны: их строят S4, S5 и S7.
|
||
Заготовки существуют ровно потому, что действия оболочки обязаны быть настоящими.
|
||
6. **Замечания не виртуализованы** — обоснование в Ф-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` из npm `prepare` — каждый `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 движка.
|
||
|
||
## Пинг оркестратора №16 — 09.08.2026 (кросс-ссылка зонных половинок, строка 167)
|
||
|
||
Движковая половина темы Ф-30/В-4 («глава без заголовка») — **строка 160 единого бэклога** (`titleRaw` в манифест · provenance у heading · тип chapter/fragment; research/27 §6 Этап 0). До её лендинга `heading` манифеста = ВРЕМЕННЫЙ рендер движка (D39.122 п.2д) — честный заголовок из данных приедет строкой 160, продуктовое слово — В-4 на владельце. На экране ничего не менять до обоих.
|