diff --git a/frontend/docs/S37_SESSION_PROMPT.md b/frontend/docs/S37_SESSION_PROMPT.md index 54cabf6f..12a22b69 100644 --- a/frontend/docs/S37_SESSION_PROMPT.md +++ b/frontend/docs/S37_SESSION_PROMPT.md @@ -14,8 +14,10 @@ > живой поток идёт по SSE (`src/showcase/useRunStream.ts`), чтения — TanStack Query > (`src/api/queries.ts`), контракт — зонная копия `frontend/docs/api-contract/openapi.yaml` > (0.2.1), типы генерятся из неё. Эта сессия НЕ строит новых экранов: она закрывает названный -> техдолг слоя данных (Ф-49…Ф-51), одну ратифицированную правку контракта (Ф-47) и мелкие -> хвосты. S4 стартует после неё отдельным промтом. По всем задачам — прогон в отчёте; +> техдолг слоя данных (Ф-49…Ф-51), одну ратифицированную правку контракта (Ф-47), +> интернационализацию строк интерфейса (Ф-35: словарь + гейт, без хардкода русского) +> и мелкие хвосты. S4 стартует после неё отдельным промтом. +> По всем задачам — прогон в отчёте; > **молча пропустить задачу нельзя**: сделано / диспозиция с обоснованием / вопрос В-строкой. ## Обязательное чтение до кода (порядок) @@ -180,6 +182,30 @@ text arrives with a read after the boundary»). - `BACKLOG.md` Ф-47 — «`?? ''` в трёх местах» завышено: вхождение одно (`src/showcase/Bank.tsx:166`); поправить при закрытии строки. +### 9. Ф-35 — строки интерфейса без хардкода: словарь + гейт + +Сегодня ~90 русских литералов живут прямо в разметке (Ф-35); локаль примитивов уже сведена +в одно место (`src/ui/Locale.tsx`, `interfaceLocale`). Сделать интерфейс переводимым +МЕХАНИЗМОМ, не переводом: + +- все строки интерфейса уезжают в КАТАЛОГ сообщений, механизм — ОТРАСЛЕВОЙ i18n-инструмент, + не самописная поделка: компонент берёт строку по ключу, локаль = данные, новый язык + добавляется файлом перевода без захода в компоненты. Кандидатов сравнить и выбор обосновать + в журнале (у react-aria уже есть свой слой `@internationalized/string`; react-intl / lingui — + альтернативы); новая зависимость — с точным пином и записью в `STACK_DECISIONS.md` §3, + как того требует политика пинов; цену бандла замерить; +- **гейт против нового хардкода:** кириллический литерал в JSX/разметке = ошибка линта + (исключения — модуль словаря и фикстуры); гейт проверить ЖИВЫМ нарушением, как Ф-4/Ф-9; +- хранилище выбора языка подключить к словарю; каркасный пункт настроек «язык интерфейса» + уже есть — честное состояние «сейчас интерфейс только русский» остаётся, пока локаль + содержимого одна (ru); +- границы прежние: фикстурная проза (тексты мок-книг, названия глав, термины банка) — ДАННЫЕ, + не интерфейс, в словарь не уезжает; продуктовый список языков — решение владельца (если + нужен ответ сейчас — В-строкой); переводы строк на другие языки НЕ выдумывать. + +Приёмка: гейт ловит подсунутый литерал (показать нарушением); кадры семи маршрутов +не изменились — это переезд строк, не перевод. + ## Что НЕ делать - Не строить экраны S4–S7 (загрузка книги, механизм подписи, содержимое настроек, экспорт). @@ -204,5 +230,6 @@ text arrives with a read after the boundary»). адверсариальное ревью диффа (author≠reviewer) в конце; находки с диспозициями — в журнал. - Прогон по всем задачам таблицей в `frontend-PROGRESS.md`: задача → что сделано → тест/замер → либо диспозиция. Хвосты — строками Ф-N в `BACKLOG.md` (Ф-49/50/51/40/47 закрыть или - переформулировать честно), вопросы владельцу — В-строками. + переформулировать честно; Ф-35 и Ф-25 закрываются в своей код-половине, продуктовая половина + Ф-35 «какие языки» остаётся на владельце), вопросы владельцу — В-строками. - Промт S4 не писать — его выдаёт оркестратор после приёмки этого пака. diff --git a/frontend/docs/frontend-PROGRESS.md b/frontend/docs/frontend-PROGRESS.md index 28c9fd76..d41ad146 100644 --- a/frontend/docs/frontend-PROGRESS.md +++ b/frontend/docs/frontend-PROGRESS.md @@ -103,6 +103,18 @@ > ресёрчу отдельным агентом и проверены по коду; два моих собственных предложения тот же ресёрч > опроверг, и это записано в разделе 15 вместе с долгами сессии. +> **Запись фронт-сессии S3.7, 10.08.** Долги слоя данных закрыты тестами, падающими на до-фиксном +> коде (Ф-49…Ф-51), контракт зонной копии бампнут до **0.2.2** (`BankTerm.sense` обязателен), +> строки интерфейса уехали в КАТАЛОГ (`src/i18n/`, 147 ключей, механизм — `@internationalized/string`, +> уже лежавший в дереве под react-aria-components), и весь код зоны переведён на английский: +> комментарии, имена тестов, сообщения ошибок, логи скриптов. Держат это два гейта, и каждый +> проверен ЖИВЫМ нарушением — ESLint ловит текст в разметке МЕСТОМ, а не алфавитом (ловит и русский, +> и английский литерал), `src/i18n/catalogue.test.ts` подметает комментарии, CSS и python. +> `npm run check` зелёный: **266 тестов** (было 160), `npm run check:full` — 7 маршрутов и +> **9 сценариев** (добавлены `hidden` и `zoom`: раскладка при 125/150/200% с прогоном axe). +> ⚠ Находка приёмки 8.1 (HIGH) **не воспроизвелась** — замер в браузере в записи ниже, механизм, +> написанный было под неё, снят; сценарий на тот же путь оставлен сторожем (Ф-53). + > **Четвёртый круг замечаний, 10.08 (раздел 12 хроники).** Четыре бага владельца по таблице банка > закрыты, и один из них был МОЙ механизм: заморозка ширин (`useSettledWidth`) оставляла таблицу > сжатой после драга, а перезамер показал, что она ничего не покупала — драг держит p90 17 мс и @@ -115,8 +127,10 @@ > **Запись оркестратора, 10.08 — ПРИЁМКА S3.6: принято и залендено (D39.127).** Гейты > пере-прогнаны исполнением, 12 находок ревью (1 high) и поправки к записи S3.6 — первой записью > хроники ниже. Промт **S3.7** выдан (`S37_SESSION_PROMPT.md`: Ф-49…Ф-51, Ф-47 ратифицирован, -> Ф-40, Ф-25, находки §8), запуск по слову владельца; **S4 — после приёмки S3.7.** Экспорт банка -> заведён строкой 169 единого бэклога (Ф-43). +> Ф-40, Ф-25, Ф-35 — словарь строк + гейт против хардкода русского; ⚠ Ф-35 в первой редакции +> промта отсутствовала — пропуск поймал владелец при ревью промта, D39.127 п.2г), запуск +> по слову владельца; **S4 — после приёмки S3.7.** Экспорт банка заведён строкой 169 +> единого бэклога (Ф-43). - **Пройдено:** S0 (план), S1 (инструменты, скриншот-цикл, `tokens.css`, витрина), S2 (оболочка трёх панелей, слой `src/ui/`, масштаб списков, хвосты гейтов), @@ -345,12 +359,91 @@ NDJSON — это шов ДВИЖОК↔ПЛАТФОРМА (D39.85), а фрон | В-6 | **Упёрся в свой потолок ≠ кончились деньги, а интерфейс их не различает.** Владелец 07.08 дал человеку поставить перед стартом колпак в ГЛАВАХ; платформа берёт холд на его сумму. Но машинная причина паузы в контракте ровно одна — `credit_exhausted`, и она про кредит. Значит прогон, честно доехавший до выбранной человеком границы, и прогон, у которого кончился баланс, приходят на экран одной фразой «перевод остановлен: лимиты исчерпаны». Первому она врёт: лимит не исчерпан, человек сам так решил, и следующее действие у него другое — поднять колпак, а не пополнить счёт. Нужно ли второе значение и какая у него фраза — продуктовое. Заведено К-13, строка Ф-31 | продуктовая формулировка, которую видит пользователь, и разное следующее действие | | В-8 | **Какое слово о состоянии книги стоит в дереве.** Замечание 23 потребовало бейджу ФИКС-место, и оно сделано; но две метки словаря в него не помещаются и усекаются с тултипом — «остановлена: лимиты» (19 знаков) и ветка неизвестного «неизвестное состояние» (21). Укоротить их сессия НЕ вправе: фраза паузы зарезервирована за владельцем (В-6), и первый заход S3.5 её укоротил и был откачен собственным ревью. Варианты: (а) оставить усечение с тултипом, (б) владелец даёт короткое слово для дерева, а полная фраза остаётся в статус-полосе, (в) расширить колонку бейджа за счёт названия книги | продуктовая формулировка, которую видит пользователь | | В-7 | **Физический масштаб интерфейса на мониторе.** Замечание 1 говорит «масштабы великоваты, особенно на 27″ 2K, на 24″ терпимее». Замер при вьюпорте референса расхождения с Fleet не нашёл (§5.5 плана): промежутки, ширины панелей, шаг строки и высота вкладки совпадают. Значит речь про физический размер на конкретном экране, а он зависит от масштабирования ОС, и «просто уменьшить токены» означало бы уехать от референса, который владелец принял. Правильное лекарство — ручка «плотность интерфейса» в настройках (3 ступени), но она требует перевода ВСЕХ размерных токенов в rem и пере-замера, то есть отдельного прохода (Ф-36). Вопрос: нужна ли ручка, и какой масштаб ОС стоит на 27″ 2K — по нему видно, во сколько раз расходится физика | продуктовое решение и цена отдельного пакета | +| В-9 | **Какие языки интерфейса продукт обещает.** Механизм переводимости построен (S3.7, Ф-35): слова интерфейса живут в каталоге `src/i18n/ru.ts`, второй язык — второй файл того же вида плюс строка в `src/i18n/language.ts`, ни один компонент при этом не трогается. Чего фронт сделать не может и не должен: решить, какие языки продукт обещает, и выдумать сами переводы. Пока список из одного значения (`ru`), и каркасный пункт настроек честно говорит «сейчас интерфейс только русский» | продуктовое решение и обещание пользователю | | В-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 единого бэклога | продуктовое: обещание срока пользователю | ## Хроника +### 10.08 — восьмая фронт-сессия (S3.7): долги слоя данных, контракт 0.2.2, строки в каталог + +Вход: промт `S37_SESSION_PROMPT.md` (девять задач) плюс правило владельца, пришедшее по ходу пака: +**в КОДЕ никакого русского** — комментарии, сообщения ошибок, логи, имена тестов, служебные строки +сценариев по-английски; русский UI-текст живёт ТОЛЬКО в словаре. + +#### 1. Прогон по задачам + +| Задача | Что сделано | Тест / замер | +|---|---|---| +| **Ф-49** свежесть текста главы | кадр `chapter` инвалидирует юниты ИМЕННО своей главы, кадр `status` — юниты всей книги (границы стадий: пока `units_done` единый счётчик, кадра главы за волну может не быть ни одного) | `useRunStream.test.tsx`, 2 случая по счёту запросов к MSW; оба падают на до-фиксном коде | +| **Ф-50** потерянный кадр | кадр, не нашедший снимка, зовёт `resetQueries` по своему ключу; ревизия многостраничного списка = МИНИМУМ из страниц | 2 теста, оба падают на до-фиксном коде. **Выбор reset вместо invalidate — замер, а не вкус:** `invalidateQueries` на запросе без данных ничего не делает (в исходнике библиотеки: перезапуск в полёте только при `state.data !== undefined`), то есть возвращает промис того самого устаревшего чтения | +| **Ф-51** resync | `resetQueries` по скоупу КНИГИ (`bookScope`), отметка ревизии уходит вместе со снимком; чужие скоупы целы | 2 теста, оба падают на до-фиксном коде | +| **Ф-47** `sense` обязателен | зонная копия `openapi.yaml` → **0.2.2**, правило пустой строки в описании, типы перегенерены, костыль `?? ''` снят | `npm run contract` зелёный; ⚠ костыль был в ОДНОМ месте, а не в трёх — счёт строки бэклога был завышен | +| **Ф-40** общий модуль поля | `src/ui/Field.module.css` (`.input` + модификатор `.framed`), примитивы остались разными | `cssModules.test.ts` (пары по импортам, сироты) | +| **Ф-25** язык кода | английские комментарии/тесты/сообщения во ВСЁМ дереве зоны, включая конфиги, `index.html` и python | гейт `src/i18n/catalogue.test.ts`, проверен живым нарушением | +| **Ф-35** строки интерфейса | каталог `src/i18n/ru.ts` (147 ключей), механизм `@internationalized/string` 3.2.10, стор языка с персистом, локаль оттуда же берут `I18nProvider` и весь `Intl` | гейт ESLint проверен живым нарушением; цена бандла 716131 → 723102 (+6971 байт, из них ~7 КБ — сам текст) | +| **сторожа** зум и узкая панель | сцена `zoom` (125/150/200% = вьюпорты 1152/960/720 в CSS-пикселях): перелив окна, три панели, статус-полоса в окне, таблица банка не за краем, axe на каждом шаге | `npm run scenes zoom` — 20 проверок | +| **замер `measure.py`** | палитра S3.6 воспроизведена по `fleet_2.png` (`#181818` 63.97%, `#292929` 7.17%), кадр `/showcase` сведён с `fleet.png` при 1280×764 | числа и вердикты — `FRONTEND_PLAN.md` §5.7; окружение запинено (`scripts/requirements.txt`) | + +#### 2. Находки приёмки S3.6 + +| № | Диспозиция | +|---|---| +| 8.1 (HIGH) | **НЕ ВОСПРОИЗВЕЛАСЬ.** Замер в браузере: таблица, смонтированная в скрытой keep-alive вкладке `/scale`, держит 116 ячеек и ширины `121·122·133·232` — те же, что у вкладки, с которой не уходили, и на до-фиксном коде тоже; `content-visibility: hidden` подтверждён на самой панели (`data-inert`, computed `hidden`). Причина: запрос геометрии внутри пропущенного поддерева заставляет браузер посчитать раскладку. Мой первый заход добавил под эту находку `IntersectionObserver` — **механизм снят**, чинить нечего. Сцена `hidden` (уход с вкладки ДО прихода банка и возврат) оставлена сторожем: путь реален и не был покрыт. Строка Ф-53 | +| 8.2 контраст выбранной строки | починены пары на синем: счётчик и бейдж → `--color-text` (7.27:1), значок и дорожка → `--color-text-secondary` (3.52:1), заполнение → `--color-text` . Сторож в сцене `tabs` считает контраст ВНУТРИ страницы и падает на до-фиксном коде четырьмя парами (1.97 · 3.52 · 1.55 · 1.97). ⚠ Названо честно: пара «заполнение ↔ дорожка» даёт 2.07:1 — выше палитрой владельца не поднять, поэтому выбрана та пара, где обе половины читаются на самом фоне | +| 8.3 сирота в «Замечаниях» | гард поднят в `open`/`pin` самого экрана (одно место вместо двух у зовущих), сирота помечается и когда раздел НАЗВАН, но отсутствует в списке | +| 8.4 съёмка | (а) флаг не пишется, пока не полетел первый запрос, и защёлка ждёт 150 мс тишины — **замер показал дефект хуже описанного:** `/scale` снимался со строкой «загрузка разделов…», потому что между волнами чтений счётчик честно падает в ноль; (б) `shot.mjs` и `scenes.mjs` ждут доигрывания анимаций перед axe | +| 8.5 минорные | кэш канвас-замера запоминает ширину только если шрифт реально доступен (`document.fonts.check`); пин вкладки — только левая кнопка и выделение ВНУТРИ этой главы; `termStatus` больше не носит нерендерящихся слов; комментарии токенов исправлены числами (3.58:1), `--color-note` поднят до 3.07:1, `.paused` перестал быть no-op, `.offline` снят; сторож обрезки вкладок падает, если заголовок перестал быть `span`; PIL запинен | +| 8.5 ре-рендер экрана | **замер до/после:** флаг съёмки переехал в лист (``), потому что `useIsFetching` перерисовывал ВЕСЬ экран на каждый старт и финиш запроса. Открытие 12 глав на `/scale`: p90 кадра 133 → 50‥67 мс, длинных кадров 16‥19 → 7‥13, длинных задач 22 (≈2080 мс) → 16 (≈1110 мс) | +| 8.6 доки | `FRONTEND_PLAN.md` §5.6 поправлен (модель «вырез в хроме» описана как СНЯТАЯ, в коде пилюля), §4 — сценариев девять, команда замера через venv; `README.md` — карта сессий и раздел про язык кода; `BACKLOG.md` Ф-47 — счёт костылей исправлен | + +#### 3. Что нашла проверка исполнением, а не рассуждение + +- **Каталог печатал `показано {shown} из {total}` буквально.** `@internationalized/string` подставляет + переменные ТОЛЬКО в скомпилированные функции (прочитано в исходнике библиотеки), а плоскую строку + отдаёт как есть; ICU-компилятор — это отдельный шаг сборки, от которого стек отказался, когда + взвешивал lingui. Подстановка `{name}` теперь наша, четыре строки, и на неё стоит тест; поймал + дефект сценарий `bank`, юнит-батарея его не видела. +- **Гейт строк сначала был написан по алфавиту** — и это было хуже, чем кажется: одно правило жило + в двух носителях (селектор ESLint и регексп теста), а перечень букв `ПТ|Ф|В|К|Д` был заплаткой под + конкретные строки бэклога. Переделано: ESLint ловит текст МЕСТОМ (буква в `JSXText` или в читаемом + атрибуте) — ловит и русский, и английский литерал, то есть строго сильнее; алфавит остался ровно + в одном месте и для ДРУГОГО правила («код зоны английский»), где комментарии иначе не проверить. +- **`/scale` снимался недоделанным** (см. 8.4а) — нашлось при первой же проверке нового флага кадром. + +#### 4. Как шла работа + +Свод языка и переезд строк — двенадцать параллельных агентов по непересекающимся файлам, каждый +вернул свои ключи структурой, каталог собран одним местом. Их находки, которые я разобрал руками: +JSDoc `look` у кнопки описывал три значения из четырёх (дописано), комментарий подвала панели +ссылался на токен, который стоит не в этом правиле (переписан по коду), комментарий панели цитировал +замер Fleet рядом с нашим изменившимся токеном (разведено явно). Ни одного комментария, оказавшегося +ВРАНЬЁМ против кода, свод не нашёл. + +#### 5. Красный гейт, который я НЕ подогнал + +Сцена `perf` на загруженной машине (в тот момент шло адверсариальное ревью двенадцатью агентами) +показала отклик поля 63‥79 мс при пороге 60 и уронила `check:full`. Порог не трогал — вместо этого +поставил A/B тем же зондом по двум сборкам подряд, чтобы нагрузка била в обе одинаково: + +| Сборка | Отклик на символ, пять прогонов | +|---|---| +| до этой сессии (`git archive HEAD`) | 44 · 47 · 50 · 54 · 59 и 42 · 43 · 44 · 46 · 48 | +| эта сессия | 39 · 41 · 43 · 44 · 47 и 39 · 41 · 42 · 44 · 47 | + +То есть регрессии нет — сборка стала БЫСТРЕЕ, а красный был машиной: сам замер сцены (три символа +через playwright с обратной дорогой) чувствительнее к нагрузке, чем зонд. На тихой машине сцена +даёт 43‥49 мс и проходит. Что с этим делать дальше — вопрос не подгонки порога, а устойчивости +замера; строку заводить не стал, потому что чинить пока нечего: гейт ловит то, ради чего заведён. + +#### 6. Гейты на сдаче + +`npm run check` — зелёный, **266 тестов** (было 160). `npm run check:full` — 7 маршрутов, +**9 сценариев**, ноль блокирующих нарушений axe (включая три зум-шага), драг панели p90 17 мс, +1 длинный кадр из 81. Оба новых гейта проверены живым нарушением: ESLint ловит `title="Настройки"` +И `title="Settings"`; сторож языка ловит русский комментарий в `api/revision.ts`. + ### 10.08 — приёмка S3.6 оркестратором: ПРИНЯТО и залендено; поправки к записи S3.6 Гейты пере-прогнаны исполнением: `npm run check:full` EXIT=0, vitest **160**, `npm run shot` ×3,