Add S3 handoff prompt gated on the API contract with F-14 fallback, plus new backlog row for unknown-value branches in closed unions

This commit is contained in:
heaven 2026-08-04 03:31:54 +03:00
parent e6147d5aa4
commit 8b6f107cfa
3 changed files with 183 additions and 0 deletions

View file

@ -21,6 +21,7 @@
| Ф-17 | **Закрытие вкладки с клавиатуры не объявлено скринридеру.** Крестик у вкладки-документа сознательно НЕ кнопка: у роли `tab` дети презентационные (ARIA), вложенный фокусируемый элемент выпал бы из дерева доступности, оставшись в порядке табуляции (это же ловит axe правилом `nested-interactive`). Мышь закрывает крестиком, клавиатура — `Delete` на выбранной вкладке. Объявить сочетание нечем: `aria-keyshortcuts` не входит в типизированный API `Tab`/`TabList` в react-aria-components 1.20.0. Пересмотреть при обновлении библиотеки либо при появлении палитры команд | триггер: мажор RAC или экран горячих клавиш | S2 |
| Ф-19 | **Переключение вкладки размонтирует её содержимое, и прокрутка длинного списка теряется.** Нашло адверсариальное ревью S2 и подтвердило независимым прогоном: `react-aria-components` не рендерит невыбранную `TabPanel` (`dist/private/Tabs.mjs:276``if (!isSelected && !shouldForceMount) return null`), поэтому на `/scale` прокрутка дерева `scrollTop=9000` → вкладка «Поиск» → обратно даёт `scrollTop=0`, а пересборка коллекции из 2284 узлов стоит 70140 мс (два независимых замера). Ф-12 это не переоткрывает: там речь про поведение списка при 10³, и оно держится. **Чинить сейчас не стали:** `shouldForceMount` не лечит (панель остаётся видимым `div` и легла бы поверх соседней в той же ячейке грида), а настоящее лекарство — keep-alive либо запоминание офсета на вкладку — это решение об устройстве оболочки, и принимать его вслепую, до настоящих экранов, значит строить механизм не под задачу | S4 (экран глав) либо раньше, если владелец увидит потерю места | адверсариальное ревью S2 |
| Ф-20 | **Мёртвый шрифт и мёртвые токены в поставке.** `@fontsource-variable/jetbrains-mono` грузится в бандл, но `--font-mono` не выбран ни одним правилом: художественную прозу моноширинным не набирают, а во Fleet на этом месте был код. Туда же несколько токенов, которые ни разу не используются (`--color-current-line`, `--color-hint`). Замок полноты токенов (S2) требует классифицировать токен, но не требует его использовать. Решать на S6, когда читалка станет настоящей: либо моноширинный находит работу, либо импорт и токены уходят | S6 | адверсариальное ревью S2 |
| Ф-22 | **У закрытых юнионов нет ветки неизвестного значения, и тайпчек здесь не страхует.** `runStates[book.state]` (`showcase/Library.tsx:103`) — обращение к `Record` по КОНЕЧНОМУ юниону, а `noUncheckedIndexedAccess` добавляет `undefined` только индексным сигнатурам: проверено пробой `tsc` — обращение по юниону компилируется молча, ругается лишь `Record<string, …>`. Значение вне юниона (новое состояние прогона из контракта API) даёт `undefined.tone`, то есть падение дерева библиотеки, а не пустую метку; тем же классом `noteTone` тихо уводит незнакомую ступень замечания в спокойную ветку. Лечение — ветка неизвестного значения НА ШВЕ `src/api/`, где значение входит, а не в каждом компоненте. В S2 находку отвели скептики формулировкой «это работа S3» — она и есть работа S3, но хвост записан здесь, чтобы не жить только в промте | S3 (вместе с приведением типов к контракту) | адверсариальное ревью S2 → ревью оркестратора 04.08 |
| Ф-21 | **Ступень замечания различается только цветом полоски** (`attention` синяя, `glance` серая). Зрячему разница читается ещё и светлотой, но скринридеру приоритет не достаётся вовсе. Лечение — слово, как это уже сделано для состояния прогона книги; **какое именно слово, решает владелец**: словарь замечаний продуктовый (ПТ-33), а §3.8 требует, чтобы экран не выглядел тревожным, так что «важно/неважно» не годится | решение владельца | адверсариальное ревью S2 |
| Ф-18 | **Бандл 545 КБ (168 КБ gzip) одним куском** — замер 04.08 после установки `react-aria-components`; Rolldown предупреждает про порог 500 КБ. Для десктопного SPA за логином это не авария (индексации нет, сеть локальная), но одним куском едут и экраны, которых пользователь ещё не открыл. Лечение — разбиение по маршрутам, и делать его осмысленно после S4S7, когда экраны появятся: сейчас резать нечего | после S7 либо при жалобе на первый экран | S2, замер |
| Ф-5 | **Tauri 2.x вторым шагом** — по явным триггерам (трей · глобальные горячие клавиши · распространяемый .exe · офлайн · хранилище учёток ОС); до того — установимое PWA, только браузерные API | триггеры названы | STACK_DECISIONS §6, D39.84 |

View file

@ -22,6 +22,9 @@
по ревью оркестратора 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`.

View file

@ -0,0 +1,179 @@
# Промт: фронт-сессия S3 — слой данных (вход под замком)
Ты — фронтенд-сессия TextMachine, четвёртая по счёту. Зона записи — **только `frontend/`**;
сессия не коммитит — лендит оркестратор (протокол — стоящий промт `FRONTEND_SESSION_PROMPT.md`
§«Зона и git», он в силе целиком). Пре-коммит хук стоит и самоустанавливается при `npm install`.
> Промт написан сессией S2 по просьбе владельца — как передача с той точки, где S2 встала.
> Ратифицирует и правит его оркестратор; при расхождении с его хендофф-промтом побеждает тот.
## Входное состояние (04.08, проверено исполнением, а не заявлено)
- **Пройдено S0S2:** план · инструменты и скриншот-цикл · `tokens.css` по замерам референса ·
оболочка трёх панелей (`src/shell/`) · примитивы `src/ui/` на `react-aria-components` ·
витрина `/showcase` и нагрузочный `/scale` (2284 раздела, 1200 терминов).
- `npm run check` — 5 гейтов (prettier · eslint тип-осведомлённый · stylelint · tsc · vitest,
**77 тестов**), `npm run check:full` — + сборка + три скриншота с гейтом доступности axe.
Всё зелёное на 04.08.
- **Данные всё ещё синхронная фикстура:** `src/api/` отдаёт объекты из `src/mock/` напрямую.
Это диспозиция Ф-15, а не недосмотр: асинхронность приезжает вместе с MSW, то есть в S3.
- **Гейты, которые ты застаёшь:** одно место для цвета и размера (TSX-половина в
`eslint.config.js`, CSS-половина в `stylelint.config.js`) · три шва — фикстуры видит только
`src/api/`, глобальный CSS только `src/main.tsx`, `react-aria-components` только `src/ui/`
закрытые и для статического, и для динамического импорта · сеть запрещена вне `src/api/`
во всех транспортах (`fetch`/`EventSource`/`WebSocket`/`XMLHttpRequest`, включая
`window.*`) · тест общности: код языка не зашивается в разметку, стили не ветвятся по
`:lang(…)` · контракт-тест токенов и структурные тесты CSS-модулей.
- ⚠ **На момент написания промта в дереве лежали незакоммиченные правки** по ревью оркестратора
(`frontend/src/{api/types.ts,generality.test.ts,mock/book.ts,mock/scale.ts,showcase/Context.tsx,
showcase/Reader.module.css,tokens/tokens.css,tokens/tokens.test.ts}`, `eslint.config.js`,
`frontend/docs/*`). Их лендит оркестратор. **Первым делом сверься с `git log` и `git status`:**
если правок в дереве нет — они уже уехали, ничего восстанавливать не нужно.
## Замок на входе — первое действие сессии
**Проверь сам, промту не верь** (он написан 04.08 и мог устареть):
1. Существует ли `docs/architecture/14-api-contract.md`?
2. Что говорит строка **95** единого бэклога в `docs/PROGRESS.md` (контракт API v0 + продуктовый
словарь статусов) — она всё ещё «дизайн-промт по слову владельца» или уже исполнена?
**Открыт** (контракт есть и ратифицирован) → скоуп S3 ниже.
**Закрыт** (на 04.08 — закрыт: файла нет) → раздел «Если замок закрыт». Не начинай S3 «пока
на своих типах, потом поправим»: моки, снятые не с того контракта, разойдутся с API — ровно
то, ради чего строка 95 заведена.
## Обязательное чтение до кода (порядок)
1. `frontend/docs/FRONTEND_SESSION_PROMPT.md` — стоящий промт: сценарий §3, жёсткие ограничения
§4, «как работать» §7. **Его не редактировать.** ⚠ Раскладка §2 частично устарела — её
перевешивают решения владельца из журнала зоны.
2. `frontend/docs/PROGRESS.md` целиком: «Текущее состояние» · «Решения владельца по продукту» ·
«Открытые вопросы» · хронику 04.08 (в ней — что S2 построила, где отошла от референса и что
поправило ревью оркестратора).
3. `frontend/docs/FRONTEND_PLAN.md` — §0.1 (карта канона: что читать перед чем), **§0.2
(транспорт: с движком фронт не разговаривает никогда — это ратифицированный анти-паттерн,
D39.81/D39.85)**, §5.4 и §5.4.1 (формы обхода гейтов и названные границы), **§6 (что уже
известно про движок и как это меняет форму данных — читать до первой строки типов)**.
4. `frontend/docs/STACK_DECISIONS.md` — §2 пины (там уже названы `@tanstack/react-query` и `msw`),
§5 транспорт и правила стрима, §7 риск ре-ингеста. Библиотеки сам не выбирай и версии
по памяти не «обновляй»: твои знания об экосистеме устарели, сверка — live по npm.
5. `docs/research/23-engine-platform-seam.md``docs/README.md` требует читать его **перед любым
кодом стыка**; форма событий и правило ре-синка оттуда.
6. `frontend/docs/BACKLOG.md` — строки **Ф-14** (вход фронта в контракт), **Ф-15** (закрывается
этой сессией), Ф-19, Ф-21.
7. `references/*.png` и `.shots/*.png` — открой и посмотри до первой правки.
## Скоуп S3 — слой данных, и ни шагом дальше
1. **`src/api/` становится асинхронным**: MSW как сетевой мок + серверное состояние на пинах
из STACK_DECISIONS §2 (версии сверить live на дату сессии; `.npmrc` держит `save-exact`).
Каждый новый пин — строкой в таблицу `FRONTEND_PLAN.md` с датой релиза и «зачем нам».
2. **Фикстуры всех состояний, а не одного счастливого:** ожидание · ошибка · пусто ·
частично · длинный хвост · потеря соединения на стриме. Правило S1/S2, которое здесь
действует сильнее всего: **фикстура по умолчанию — трудный случай.** Наивный счётчик глав
должен на ней давать ноль (идёт черновая волна), иначе скриншот-цикл смотрит на удобное.
3. **Живой прогресс — `EventSource` за швом `src/api/`** (STACK_DECISIONS §5; гейт сети уже
запрещает его где-либо ещё). Докачка по `Last-Event-ID`/ревизии — часть контракта, не выдумка.
4. **Ф-15 закрывается здесь:** ветки ожидания/ошибки/пустоты появляются вместе с MSW, а не
после первого продуктового экрана.
5. **Типы `src/api/types.ts` приводятся к контракту.** Что в них сегодня заведомо временное:
булев `signed` вместо трёхзначного `auto|draft|approved` · ключ строки банка, собранный
из (`src`, `dst`, `type`), — у движка термин уникален по (`src`, `sense`, `since_ch`,
`until_ch`) · отсутствие ревизии для докачки. Всё это перечислено строкой Ф-14.
**Закрытые юнионы получают явную ветку неизвестного значения** (строка Ф-22) — сегодня её нет
ни у одного, и строгость тайпчека здесь НЕ страхует: `runStates[book.state]`
(`showcase/Library.tsx:103`) — обращение к `Record` по КОНЕЧНОМУ юниону, а
`noUncheckedIndexedAccess` добавляет `undefined` только индексным сигнатурам (проверено `tsc`
на пробе: обращение по юниону компилируется молча, ругается только `Record<string, …>`).
Значит состояние прогона из будущего контракта даёт `undefined.tone` — падение дерева
библиотеки, а не пустую метку; `noteTone` тем же классом тихо уводит незнакомую ступень
замечания в СПОКОЙНУЮ ветку. **Место ветки — шов `src/api/`, где значение входит**, а не
каждый компонент: перевод «вердикт → вид» уже расползался по экранам, и контрольный вопрос
владельца требует, чтобы новое значение правило один файл.
6. **Поля не выдумывать.** Нет в контракте — это вопрос владельцу/оркестратору в
«Открытые вопросы» журнала зоны, а не догадка в типе. Дефект «фикстура выдумала поле»
в этой зоне случался дважды и оба раза стоил экрана.
**Не в скоупе:** экраны S4S7 · редактор текста · светлая тема · мобильная раскладка ·
React Compiler (Ф-2) · Tauri (Ф-5).
## Если замок закрыт
Это ожидаемое состояние на 04.08, и правильный ход — не ждать молча и не строить экраны
на синхронных моках (порядок этапов Ф-1 стоит именно на слое данных).
- **Доведи Ф-14 до артефакта, который можно отдать автору контракта:** не список намерений,
а перечень с обоснованием на каждый пункт — язык кодами · состояние прогона отдельно от
выполнения главы · прогресс пофазно по ЮНИТАМ · ревизия/`Last-Event-ID` · трёхзначный статус
подписи · поля банка (`type`/`sense`/`since_ch`) и ключ строки · продуктовый словарь вердиктов
(ПТ-33; §4.1 запрещает протечку внутренностей конвейера на экран). Половина уже написана
в строке Ф-14 и в §6 плана — собери, а не сочиняй заново.
- Отдай его оркестратору пингом в `docs/PROGRESS.md` и **остановись**: заводить контракт своим
решением фронт не может (шапка `BACKLOG.md`).
- Ф-16, Ф-17, Ф-18, Ф-20 — **не трогать**: у каждой в бэклоге стоит свой триггер, и ни один
из них не наступил.
## Ловушки, купленные S2 — не переоткрывать
- **`react-aria-components`:** `Tab` не принимает клавиатурных обработчиков (обработчик живёт
на ряду вкладок) · у роли `tab` дети презентационные, поэтому крестик закрытия — не кнопка
(axe роняет `nested-interactive`), закрытие с клавиатуры — `Delete`, объявить его нечем (Ф-17) ·
**невыбранная `TabPanel` не рендерится вовсе** (`dist/private/Tabs.mjs:276`), отсюда потеря
прокрутки при переключении вкладки (Ф-19, замерено дважды) · `Tree` — это `treegrid`/`row`,
а не `treeitem`; раскрытие — `<Button slot="chevron">` · `renderEmptyState` обёрнут
в `role="option"` (зонд S2 принял это за дефект кода).
- **`react-resizable-panels` v4:** `className`/`style` панели уезжают во ВЛОЖЕННЫЙ div, у него
всегда inline `overflow:auto` · `Separator` обязан быть прямым ребёнком `Group` · ключ
хранилища раскладки содержит состав видимых панелей, поэтому сворачивание удалением панели
меняет ключ (сделано осознанно: так поле оболочки остаётся ровно замеренным).
- **Скриншот-цикл:** `vite build` и `preview` в один момент дают пустой экран — это гонка,
а не регрессия; перезапусти прогон, прежде чем «чинить» рабочий код.
- **Зонды и живые нарушения:** файлы зондов удалять за собой (в S2 агенты ревью оставили свои
в чужом дереве), правленые файлы восстанавливать из памяти процесса, **а не `git checkout`**
в дереве могут лежать незакоммиченные правки параллельных сессий.
- **Первому красному результату зонда не верить:** три «находки» первого прогона S2 оказались
дефектами зонда (селектор бил по трём панелям сразу), и соблазн «починить» рабочий код был
вполне реальный.
- **esquery:** селектор вида `Property[key.value=/^--/] > Literal[…]` ловит собственный ключ
свойства — в TSX-гейте это записано комментарием, не убирай его.
## Технические рамки
- Новый маршрут → сразу в `KNOWN_ROUTES` (`scripts/shot.mjs`); тест сверяет списки и упадёт.
- Гейты не ослаблять. Точечное подавление — только именованное правило с причиной.
- Файл = компонент + модуль стилей рядом; >150 строк — делить. Комментарии — одна-две строки
«почему», не «что».
- Тексты фикстур — настоящие пропорции кириллицы и иероглифов, объём минимальный: вопрос
авторского права (В-2) у владельца, цитаты не расширять.
## Как работать
1. **Ревью исполнением — мандат проекта, не пожелание:** приложение поднимается, скриншоты
сняты и ПРОСМОТРЕНЫ, каждый гейт проверен живым нарушением с перечнем форм, `npm run check:full`
зелёный. «Должно работать» ревью не является.
2. **Скриншот-цикл — с первого изменения**, критерий сведения — не растровое совпадение,
а плотность и впечатление (стоящий промт §8).
3. **Адверсариальная самопроверка перед финишем** (author≠reviewer): пройди по своим правкам
с установкой опровергать. S1 так поймала пять дефектов в собственных гейтах, S2 — восемь.
4. **Спорное с каноном или новое продуктовое решение не принимай сам:** вопрос в «Открытые
вопросы к владельцу» журнала зоны, и продолжай то, что вопроса не требует.
## Готово — это когда
- Замок проверен и результат записан: либо S3 сделана, либо в журнале лежит, чем сессия
занималась вместо неё и почему.
- Если S3 сделана: `src/api/` асинхронен, MSW отдаёт все состояния, ветки ожидания/ошибки/пустоты
живые, Ф-15 закрыта, типы сведены с контрактом, а расхождения — не «поправим потом»,
а строки бэклога с триггером.
- `npm run check:full` зелёный; новые маршруты в `shot.mjs`; скриншоты просмотрены.
- `frontend/docs/PROGRESS.md`: обновлено «Текущее состояние», добавлена хроника сессии
(что построено · что видно на снимках · где отошёл от канона и почему · что осталось);
строки бэклога получили диспозиции.
- Ничего не закоммичено: дерево подготовлено и передано оркестратору.
## Что ждёт не тебя (не решай сам)
**В-1** данные привязаны к одной машине (вопрос на движок/платформу) · **В-2** копирайт фикстур ·
**В-3/Ф-21** каким словом называть ступень замечания (словарь продуктовый, ПТ-33) ·
**Ф-11** контраст палитры ниже WCAG на четырёх узлах — принято как есть до слова владельца.