diff --git a/editorial/production/README.md b/editorial/production/README.md index 6ab4488..d1ea87c 100644 --- a/editorial/production/README.md +++ b/editorial/production/README.md @@ -1,6 +1,6 @@ # Производство редакционных партий -На 31 июля 2026 года строгий аудит проходит 154 из 358 созданных материалов. Остальные 204 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить. +На 31 июля 2026 года строгий аудит проходит 157 из 358 созданных материалов. Остальные 201 не считаются «почти готовыми»: их нужно заменить, а не косметически удлинить. ## Одна партия diff --git a/editorial/reviews/2022-05-draft.md b/editorial/reviews/2022-05-draft.md new file mode 100644 index 0000000..9f37f36 --- /dev/null +++ b/editorial/reviews/2022-05-draft.md @@ -0,0 +1,69 @@ +# П51 · 2022-05 · Маленькая дизайн-система — три прохода саморевью + +## Рамка пакета + +- Slug: `editorial-2022-05-practice-design-system`, `editorial-2022-05-mechanism-design-system`, `editorial-2022-05-field-design-system`. +- Голос: М5, системный практик 2022 года. Тема продолжает доступный control: внешняя форма, состояния и семантика должны иметь явный контракт, но не раздуваются до выдуманной универсальной платформы. +- Граница компетентности: в текстах нет существующей библиотеки, команды, production-дефекта, пользовательского исследования, скриншота, visual-regression запуска или результата accessibility-проверки. +- Fixture: детерминированные локальные объекты и массивы. `visualPayload` — объявленные входы будущей проверки, не screenshot, score или тестовый результат. DOM, браузер, Playwright, screen reader, CSS compilation, HTTP и реальные visual tests не запускаются. + +## Проход 1 — факты и техника + +- Историческая рамка сверена по датированным W3C-снимкам: [CSS Custom Properties Level 1, Candidate Recommendation Draft, 11.11.2021](https://www.w3.org/TR/2021/CRD-css-variables-1-20211111/), [WAI-ARIA 1.2, Candidate Recommendation Draft, 08.12.2021](https://www.w3.org/TR/2021/CRD-wai-aria-1.2-20211208/) и неизменяемой [WCAG 2.1 Recommendation, 05.06.2018](https://www.w3.org/TR/2018/REC-WCAG21-20180605/). Ссылки существуют до мая 2022 года; современные mutable docs не используются как доказательство прошлого состояния. +- Уточнена граница CSS Custom Properties: спецификация задаёт `--*` и `var()`, но не taxonomy дизайн-токенов. WAI-ARIA/WCAG не выданы за правила конкретного token API, инвентаря или visual pipeline. +- Fixture проверяет named tokens, пять required states, name/role/state, три именованных usage, declared payload с каждым required state, два invalid configuration и безопасную пару correction/rollback. В ней нет симуляции CSS, пикселей, accessibility tree или browser behavior. +- После финальной правки успешно выполнены `node --check web/scripts/upgrade-2022-05.mjs`, `node web/scripts/upgrade-2022-05.mjs --verify-fixture` (13/13) и `npm run audit:draft -- scripts/upgrade-2022-05.mjs`. + +## Проход 2 — редактура и голос + +- В первых двух абзацах каждой статьи есть конкретный симптом и цена: рассинхрон кнопок, разрыв владельцев contract или опасная общая правка token. +- Речь построена как «симптом → причина → проверка → действие». М5 проявляется в разделении слоёв, явном радиусе изменения, тестовой границе и обратимой правке, без роли автора как владельца большой дизайн-платформы. +- Во всех трёх статьях есть таблица, рисунок с содержательным `alt` и caption, исполнимый JS-пример, нумерованный маршрут, ограничение и следующий проверяемый шаг. +- Основной текст без источников проверен редакционным аудитом: 6 862 / 8 576 / 8 495 знаков — внутри диапазона 5 000–15 000. + +## Проход 3 — визуал и выпуск + +- SVG разделены по задаче: token flow, четыре слоя component contract и diagnosis с обратимой правкой. Каждый открыт после Sharp-рендера на ширине 375 px: заголовки, ключевые ветки, подписи и границы модели читаемы. +- XML-проверка прошла для всех трёх SVG. Safety scan подтвердил отсутствие `script`, `foreignObject`, внешних URL и `data:image`; изображения содержат только встроенную vector-разметку. +- Sidecar содержит ровно пять новых файлов. Registry, README, `articles.json`, QUALITY_STANDARD, очередь, `docs/`, Git и чужие sidecar-файлы не менялись. Интеграция и публикация намеренно не выполнялись. + +## Независимый редакторский приём + +### Проход 1 — фактчек и модель + +Проверка исторических ссылок обнаружила неверный URL WAI-ARIA: вариант с +суффиксом `20211209` возвращал `300`, хотя текст правильно называл документ +от 8 декабря. Ссылка заменена на +`CRD-wai-aria-1.2-20211208`; она, как и датированные CSS Custom Properties +CR Draft и WCAG 2.1 Recommendation, отвечает `200`. Статус WAI-ARIA не +повышен до будущей Recommendation. + +В code review обнаружен второй разрыв: `requiredStates` содержал пять +значений, а declared visual payload не включал `hover`. Payload дополнен +пятым state, fixture получила отдельный assertion о покрытии каждого +required state. Это по-прежнему только вход будущего visual test, а не +screenshot, diff или результат browser run. Fixture завершилась с **13/13 +assertions**. + +### Проход 2 — текст и полнота + +Повторно проверены problem/cost, таблицы, исполнимые примеры, ordered route, +rollback и ограничения трёх статей. Тексты не выдают CSS custom properties за +taxonomy токенов, а visual payload — за факт visual regression. Draft audit +подтверждает 6 862 / 8 576 / 8 495 знаков в требуемом диапазоне; import-safe +проверка вернула три revision-объекта без `date` и `author`. + +### Проход 3 — визуал и выпуск + +`xmllint` прошёл; safety scan не нашёл script, `foreignObject`, внешних URL +или `data:image`. Sharp-рендеры на 375 px просмотрены: token flow, четыре +слоя contract и correction/rollback отвечают на разные вопросы и явно +отделяют declared payload от реального результата. После подключения мая +registry содержит 148 ревизий. Строгий slug-audit прошёл с одной figure, +таблицей и code example на статью; production build успешно сгенерировал 374 +страницы. Материалы июня и позже не затрагиваются. + +## Финальный вердикт + +П51 принята и интегрирована после трёх независимых проходов. В коммит войдут +только пять файлов мая и два точечных файла интеграции. diff --git a/web/data/editorial-revisions.mjs b/web/data/editorial-revisions.mjs index cd98bc9..6c629fa 100644 --- a/web/data/editorial-revisions.mjs +++ b/web/data/editorial-revisions.mjs @@ -47,6 +47,7 @@ import { revisions as january2022Revisions } from '../scripts/upgrade-2022-01.mj import { revisions as february2022Revisions } from '../scripts/upgrade-2022-02.mjs'; import { revisions as march2022Revisions } from '../scripts/upgrade-2022-03.mjs'; import { revisions as april2022Revisions } from '../scripts/upgrade-2022-04.mjs'; +import { revisions as may2022Revisions } from '../scripts/upgrade-2022-05.mjs'; // This layer replaces archived source entries without losing their stable slug and date. export const editorialRevisions = [ @@ -99,4 +100,5 @@ export const editorialRevisions = [ ...february2022Revisions, ...march2022Revisions, ...april2022Revisions, + ...may2022Revisions, ]; diff --git a/web/public/assets/editorial/2022/design-system-component-contract-2022.svg b/web/public/assets/editorial/2022/design-system-component-contract-2022.svg new file mode 100644 index 0000000..1272b9f --- /dev/null +++ b/web/public/assets/editorial/2022/design-system-component-contract-2022.svg @@ -0,0 +1,35 @@ + diff --git a/web/public/assets/editorial/2022/design-system-diagnosis-rollback-2022.svg b/web/public/assets/editorial/2022/design-system-diagnosis-rollback-2022.svg new file mode 100644 index 0000000..f0b673b --- /dev/null +++ b/web/public/assets/editorial/2022/design-system-diagnosis-rollback-2022.svg @@ -0,0 +1,32 @@ + diff --git a/web/public/assets/editorial/2022/design-system-token-flow-2022.svg b/web/public/assets/editorial/2022/design-system-token-flow-2022.svg new file mode 100644 index 0000000..1c1ce59 --- /dev/null +++ b/web/public/assets/editorial/2022/design-system-token-flow-2022.svg @@ -0,0 +1,37 @@ + diff --git a/web/scripts/upgrade-2022-05.mjs b/web/scripts/upgrade-2022-05.mjs new file mode 100644 index 0000000..5d6aa5b --- /dev/null +++ b/web/scripts/upgrade-2022-05.mjs @@ -0,0 +1,315 @@ +function escapeHtml(value) { + return String(value) + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); +} + +function paragraph(text) { return '
' + text + '
'; } +function heading(text) { return '' + escapeHtml(Array.isArray(lines) ? lines.join('\n') : lines) + ''; }
+function orderedList(items) { return '-- и подстановку через var(). Это полезный механизм, но он не создаёт за команду словарь design tokens. Имя button.primary.background в этой статье — соглашение пакета: оно говорит, что значение относится к роли primary button, а не ко всем синим пикселям проекта. Поэтому не стоит сразу делать brand.blue.500 единственным входом для компонента: у роли должна быть собственная граница, даже если сегодня она ссылается на тот же цвет.'),
+ paragraph('Проверка проста: у каждой величины есть имя, владелец и место потребления. Если в pull request появляется #2457D6 рядом с кнопкой, сначала спросите, это новый token или обход существующего. Если ответ «временно», зафиксируйте срок и конкретный rollback. Не нужно объявлять каждую тень и каждый margin глобальным token. Глобальность оправдана только повторяемым contract; одиночная геометрия остаётся локальной, пока не появится второй подтверждённый use case.'),
+ heading('Учебная fixture: проверить данные до сборки CSS'),
+ paragraph('Fixture создаёт in-memory объект с пятью named tokens, матрицей required states, declared name/role/state и usage inventory из трёх кнопок. Ещё в ней есть visual payload: ширины 375 и 1280, набор states и список token names. Это вход для будущего snapshot-процесса, а не screenshot, diff или PASS реального инструмента. Граница записана в самом объекте: DOM, browser, CSS compilation, HTTP и visual regression не запускались.'),
+ codeBlock(fixtureCommand),
+ paragraph('Такой тест ловит дешёвую ошибку раньше рендера: кто-то добавил usage без имени, убрал loading из состояния или стал использовать token, которого contract не объявляет. Он не ловит контраст на реальном фоне, порядок клавиатуры, cascade в существующем CSS или изменение пикселей на устройстве. Это разные проверки. Их полезно добавлять следующими, но нельзя дорисовывать их результат к локальному объекту числом score или словом «доступно».'),
+ figure('/assets/editorial/2022/design-system-token-flow-2022.svg', 'Поток малого button contract: пять named tokens поступают в компонент primary button с пятью обязательными состояниями; затем usage inventory перечисляет профиль, оплату и диалог; справа visual payload объявляет viewports и states, но помечен как не являющийся screenshot или test result.', 'Схема отделяет источник значения, contract компонента и будущий вход visual-проверки. Между ними нет выдуманного production-результата.'),
+ heading('Маршрут: симптом → причина → проверка → действие'),
+ orderedList([
+ 'Симптом. Найдите одну повторяющуюся кнопку, у которой расходятся color, focus или label. Не группируйте сразу все controls.',
+ 'Причина. Выпишите, где лежат literal values, состояния и semantic fields. Обычно они принадлежат разным локальным файлам без общего contract.',
+ 'Проверка. Соберите usage inventory: context, name, role, состояние, локальные overrides. Затем запустите fixture и убедитесь, что declared payload не называют результатом visual test.',
+ 'Действие. Внесите один named token и одну state matrix для primary button. Оставьте native button там, где не требуется другой host.',
+ 'Откат. Сохраните прежнее значение token до правки. Если один usage изменился неожиданно, верните только token и разберите его локальный override.',
+ 'Следующая проверка. После contract запустите отдельную реальную visual и a11y-проверку в согласованной среде. Её артефакт должен содержать версии и наблюдения.',
+ ]),
+ heading('Где маленький contract заканчивается'),
+ paragraph('Этот подход не выбирает типографику бренда, не строит темизацию, не мигрирует legacy CSS и не заменяет дизайн-ревью. Он также не доказывает WCAG-conformance: WCAG содержит проверяемые критерии, но локальная fixture не наблюдает страницу. Числа, hex-значения и названия из примера — учебные проектные решения. В другом продукте focus ring может иметь другое имя и значение; важнее, чтобы его существование и ответственность были явными.'),
+ paragraph('Следующий проверяемый шаг — выбрать три настоящих usage одной primary button, составить inventory до изменения и договориться о минимальном payload для внешнего visual review. Если один usage требует другого состояния или семантики, не расширяйте contract по умолчанию. Сначала зафиксируйте причину: это variant той же кнопки или другой control. Такой вопрос экономит больше времени, чем ранний каталог из двадцати компонентов.'),
+ heading('Историческая граница мая 2022'),
+ paragraph('Текст опирается на Candidate Recommendation Draft CSS Custom Properties от 11 ноября 2021 года и Candidate Recommendation Draft WAI-ARIA 1.2 от 8 декабря 2021 года — оба снимка доступны до мая 2022-го. WCAG 2.1 здесь приведён как стабильная Recommendation 2018 года. Эти документы описывают CSS-механизм и accessibility semantics, но не утверждают, что названия tokens, inventory или payload из fixture существовали в конкретной команде.'),
+ ], commonSources,
+);
+
+const mechanismArticle = createRevision(
+ {
+ slug: 'editorial-2022-05-mechanism-design-system',
+ title: 'Контракт кнопки: как связать токены, состояния и доступную семантику',
+ categories: ['Frontend', 'Архитектура'],
+ cover: '/assets/editorial/2022/design-system-component-contract-2022.svg',
+ excerpt: 'Разбор малого component contract: какие данные принадлежат токенам, состояниям, семантике и usage inventory, а какие нельзя подменять модельным visual payload.',
+ readingMinutes: 13,
+ },
+ [
+ paragraph('Кнопка начинает расходиться не потому, что в ней много CSS. Обычно один код владеет className, другой — disabled, третий — текстом, а четвёртый копирует цвет. Симптом проявляется после безопасной на вид правки: новая loading-версия смотрится правильно, но action уже доступен для повторного запуска; focus ring пропадает в одном варианте; иконка получает label только в profile. Цена — review видит фрагменты, а пользователь получает разный contract для одного знакомого действия.'),
+ paragraph('Минимальный component contract собирает эти фрагменты в данные: token names отвечают за значения, required states — за допустимые ветки, semantic fields — за смысл control, usage inventory — за известный радиус изменения. Это не универсальная система и не готовая React API. В учебном скрипте нет JSX, DOM, CSS cascade и assistive technology. Он лишь показывает, как проверить, что один договор не пропустил обязательную часть до того, как команда начнёт спорить о структуре библиотек.'),
+ heading('Четыре владельца вместо одного большого объекта'),
+ paragraph('У contract есть четыре слоя. Первый — token layer: он знает только именованные значения и не должен решать, в каком состоянии находится кнопка. Второй — state layer: default, hover, focus-visible, disabled и loading; он определяет, какие ветки продукт обязан обсудить. Третий — semantic layer: name, role, state и disabled. Он не выводится из цвета, потому что одинаковый серый может означать disabled, loading или ошибочно применённый style. Четвёртый — inventory: он хранит известные точки применения и не выдаёт себя за поиск по всему репозиторию.'),
+ dataTable('Границы малого component contract', ['Слой', 'Владеет', 'Не доказывает', 'Нужная внешняя проверка'], [
+ ['Tokens', 'имена и значения background, foreground, focus ring, radius, gap', 'что все pixels в браузере обновились', 'собранный CSS и visual diff в выбранной среде'],
+ ['States', 'разрешённые default/hover/focus-visible/disabled/loading', 'что browser реально получил hover или focus', 'ручной keyboard/mouse scenario или автоматизация'],
+ ['Semantics', 'declared name, role button, declared state', 'что screen reader произнёс ожидаемую фразу', 'проверка DOM/accessibility tree и выбранной технологии'],
+ ['Inventory', 'три явно перечисленных usage', 'что больше usage не существует', 'поиск в кодовой базе и review migration'],
+ ['Visual payload', 'viewports, states и token names для будущего снимка', 'реальный screenshot, diff, score или regression', 'настоящий visual-regression runner с сохранённым артефактом'],
+ ]),
+ heading('Почему CSS variable не является semantic token автоматически'),
+ paragraph('Спецификация CSS Custom Properties говорит о custom properties и подстановке var(). Она не назначает им смысл. Поэтому --button-primary-background может быть технически валиден, но неясен как договор, если никто не определил, для какой роли он существует, кто меняет его значение и какие variants от него зависят. Обратная ошибка тоже частая: назвать произвольное значение «semantic» и считать, что оно должно жить в глобальном файле. Семантика появляется не в пунктуации имени, а в повторяемом решении и известном owner.'),
+ paragraph('Для маленькой системы полезен направленный путь: button.primary.background → primary button → конкретные usage. Он позволяет отдельно решить, как component получает CSS variable. В одном коде это может быть custom property, в другом объект theme, в третьем stylesheet. Contract не требует выбрать транспорт заранее. Он требует не потерять связь между ролью значения и точками, на которые повлияет изменение. Такая граница оставляет миграцию обратимой.'),
+ heading('Name, role и state — не оформление'),
+ paragraph('WAI-ARIA 1.2 описывает роли, состояния и свойства для интерфейсных объектов. Для простой кнопки лучший путь обычно начинается с native button: браузер уже знает базовую keyboard-модель. Если вместо него нужен custom host, команда обязана явно воспроизвести поведение, а не только написать role="button". Однако даже native host не решает вопрос имени: «Сохранить изменения» и иконка без text alternative не равны для человека, который не видит layout.'),
+ paragraph('В модели semantic contract намеренно мал: name, role, state, disabled и список permitted states. Поле state — declared data, не прочитанное browser tree. Такое различие нужно сохранить в тексте review: fixture может показать, что state loading предусмотрен в contract, но не может сказать, что конкретный browser запретил повторный click или что screen reader сообщил disabled. Для этого понадобятся платформенные тесты, которых здесь нет.'),
+ heading('Исполнимый пример: проверить contract без UI'),
+ paragraph('Функция checkContract сопоставляет required states с permitted states, проверяет, что payload не ссылается на отсутствующий token, и что каждое известное usage имеет name и роль button. Она не ищет реальные файлы и не запускает построение styles. Именно поэтому результат true означает «данные учебной модели согласованы», а не «кнопка доступна и визуально одинакова». Это узкое утверждение легко повторить и трудно неверно истолковать.'),
+ codeBlock(contractExample),
+ figure('/assets/editorial/2022/design-system-component-contract-2022.svg', 'Схема component contract primary button: слева перечислены named tokens, сверху обязательные states, справа semantic fields name/role/state, снизу usage inventory. Пунктирная рамка visual payload показывает viewports и snapshots как объявленные входы, а не проверенный результат.', 'Компонент получает несколько независимых видов данных. Схема показывает границы ответственности, чтобы token, состояние и семантика не превращались в один неразличимый объект.'),
+ heading('Маршрут: симптом → причина → проверка → действие'),
+ orderedList([
+ 'Симптом. Запишите один разрыв: кнопка теряет focus, loading не блокирует повтор, label отличается в одинаковом действии или literal color появился в новом usage.',
+ 'Причина. Разложите изменение по четырём владельцам. Если token пытается хранить state, а CSS class несёт name, граница уже размыта.',
+ 'Проверка contract. Сверьте required states, token names, semantic fields и inventory. Запустите --verify-fixture; он должен отклонить undeclared token и неверное значение.',
+ 'Проверка платформы. Отдельно создайте реальный control и пройдите согласованный keyboard/mouse/a11y сценарий. Результат сохраните как новый артефакт, не внутри model.',
+ 'Действие. Сначала поменяйте один owner: вынесите literal в named token, добавьте state или верните native host. Не объединяйте это с переписыванием всей библиотеки.',
+ 'Откат. Применяйте token correction с сохранённым previous value. Если inventory показывает неожиданный effect, откатите малый change и сузьте variant.',
+ ]),
+ heading('Против ложной универсальности'),
+ paragraph('Слово «Button» не делает все действия одним компонентом. Link-like navigation, destructive confirmation, toggle, split button и async submit имеют разные риски. У них могут совпадать radius и gap, но не обязательно name, behavior или state matrix. Универсальный API, который принимает двадцать optional props ради такого сходства, обычно скрывает больше решений, чем экономит. Малый contract ценнее, когда он допускает честный ответ: этот control пока не входит в primary button.'),
+ paragraph('Не стоит и использовать fixture как gate для чужого продукта. Её inventory полностью создан внутри примера, а values выбраны для объяснения. В настоящем проекте сначала нужно получить существующие usage и владельца правила. Затем выбрать, какие values public, какие variants поддерживаются, как маркируется deprecation и кто проводит visual review. Это следующий слой T-shape автора 2022 года: не говорить за процесс, которого не наблюдали, а назвать факт, которым можно проверить изменение.'),
+ heading('Ограничение и следующий проверяемый шаг'),
+ paragraph('Модель не меряет contrast, не запускает CSS, не сравнивает image pixels, не читает accessibility tree и не знает всех компонентов в архиве. WCAG 2.1 не разрешает заменить сочетание автоматической и ручной оценки полем role в JavaScript. Поэтому в материале нет claim о доступности или visual stability. Здесь есть только контракт данных, который уменьшает шанс забыть state или нечаянно изменить token без пути назад.'),
+ paragraph('Следующий проверяемый шаг — выбрать один production-like component в отдельном репозитории, составить его реальный inventory и записать маленький test plan: browser/version, viewport, states, keyboard route и ожидаемый результат. После первого наблюдения можно привязать к contract настоящий screenshot или accessibility-tree artifact. До этого visual payload должен оставаться честным списком входов, а не «зелёным» score.'),
+ heading('Историческая граница мая 2022'),
+ paragraph('Здесь использованы датированные версии, существовавшие до мая 2022 года: CSS Custom Properties CR Draft 11.11.2021 и WAI-ARIA 1.2 CR Draft 08.12.2021; WCAG 2.1 Recommendation опубликована в 2018-м. Фразы о native button и roles относятся к нормативным моделям документов. Слои contract, token names и fixture — решения этого учебного пакета, не цитата из существующей библиотеки.'),
+ ], commonSources,
+);
+
+const fieldArticle = createRevision(
+ {
+ slug: 'editorial-2022-05-field-design-system',
+ title: 'Правка токена без сюрпризов: инвентарь кнопок, диагностика и обратимый шаг',
+ categories: ['Frontend', 'Тестирование'],
+ cover: '/assets/editorial/2022/design-system-diagnosis-rollback-2022.svg',
+ excerpt: 'Диагностический маршрут для изменения малого design-system contract: отличить неверную конфигурацию от допустимой правки, не назвать payload результатом visual test и оставить безопасный rollback.',
+ readingMinutes: 13,
+ },
+ [
+ paragraph('Самая дорогая правка design token часто выглядит как одна строка. Например, цвет primary button меняют, чтобы исправить один экран, а после merge другой сценарий теряет ожидаемый contrast или focus ring. Другой симптом: в новом usage написали удобное имя token, которого система не знает, и оно тихо живёт рядом с исходным. Цена не в самом hex-значении. Команда перестаёт понимать, какой change общий, какой локальный и как вернуть предыдущее состояние без отката чужих исправлений.'),
+ paragraph('Диагностика начинается с факта, а не с косметического решения. Нужно назвать usage, state и значение, которые расходятся; затем проверить, существует ли token в контракте, относится ли он к роли button и какие известные места затронет правка. Эта статья не показывает production regression и не делает скриншоты. Она использует локальную модель с deliberate invalid configuration, declared visual payload и обратимым correction. Поэтому выводы ограничены данными модели, а не реальными устройствами или пользовательскими исследованиями.'),
+ heading('Пять причин одинакового визуального симптома'),
+ dataTable('Диагностика разрыва малого design-system contract', ['Наблюдение', 'Вероятная причина', 'Минимальный факт', 'Обратимое действие'], [
+ ['В одном экране другой синий', 'literal value обошёл named token', 'значение не ссылается на button.primary.background', 'вынести только это usage на существующий token и проверить inventory'],
+ ['Focus исчез после refactor', 'focus-visible отсутствует в required states', 'state matrix не содержит focus-visible или не проходит к payload', 'добавить state в contract до CSS-правки'],
+ ['Новая кнопка неясна без иконки', 'name и role добавили после visual слоя', 'usage inventory содержит пустой name либо role не button', 'задать semantic fields и проверить native host'],
+ ['Visual review назван успешным без артефакта', 'payload спутали с фактическим запуском', 'есть viewports, но boundary говорит visualRegression not-run', 'создать отдельный реальный job и хранить его output отдельно'],
+ ['Правка задела платёжный экран', 'изменили общий token без inventory', 'profile-save и billing-pay используют одну роль', 'вернуть previous value, затем выделить variant только по подтверждённой причине'],
+ ]),
+ heading('Сначала построить маленький радиус изменения'),
+ paragraph('Usage inventory — не список «всех кнопок мира». Это честная таблица того, что известно перед правкой: profile-save, billing-pay, dialog-cancel; контекст, name, role, state. Она делает две вещи. Во-первых, reviewer видит возможный blast radius токена. Во-вторых, команда замечает, когда похожий control на самом деле отличается: cancel может не быть primary button, а payment в loading нуждается в дополнительном поведенческом contract. В этом случае не надо включать его ради красивого числа usage.'),
+ paragraph('Инвентарь полезен и при поиске. Сначала ищут известные component entry points и literal values, затем вручную классифицируют найденное. Автоматический поиск не понимает, что text link стилизован под button или что label появляется после локализации. Поэтому результат поиска — вход в review, не доказательство полноты. В fixture inventory задан вручную и прямо помечен как учебный; он не создаёт ложного claim, что репозиторий просканирован.'),
+ heading('Invalid configuration должна останавливаться до изменения'),
+ paragraph('У модели есть два плохих входа. Первый пытается поменять button.primary.shadow, хотя такого named token нет. Второй пытается записать в background строку brand-blue, хотя учебный validator принимает только #RRGGBB или целые px. Оба входа возвращают invalid-configuration и не меняют объект. Это не полный CSS parser и не политика production token format. Это маленькая защита против тихого расширения contract в процессе срочной правки.'),
+ paragraph('Когда допустимая правка всё же нужна, функция сохраняет прежнее значение рядом с результатом. Тогда rollback не «угадывает» цвет из истории, а применяет конкретную пару name/value. Это полезный минимальный инвариант: неожиданный effect можно убрать небольшим обратным действием. Он не равен откату релиза, git revert, CSS build или компенсации серверного платежа. В статье о кнопке достаточно не потерять собственное предыдущее token value; более широкий rollback требует отдельного процесса и артефактов.'),
+ heading('Исполнимый пример: correction и возврат'),
+ codeBlock(rollbackExample),
+ paragraph('После correction значение background становится #1D4ED8, после rollback — снова #2457D6. Fixture дополнительно проверяет, что недекларированный token не появился в объекте и неверная строка не изменила baseline. Это позволяет отделить две причины. Если правка отвергнута — сначала договоритесь о расширении contract. Если она принята, но usage ведёт себя иначе — проблема в радиусе применения или variant, а не в том, что validator обязан был сам выбрать дизайн.'),
+ figure('/assets/editorial/2022/design-system-diagnosis-rollback-2022.svg', 'Диагностическая схема: inventory ведёт к проверке named token и required state. Недекларированный token или неверное значение останавливаются без изменения; допустимая смена background сохраняет previous value и может быть возвращена. Отдельный блок visual payload помечен как объявление входов без запуска visual regression.', 'Диаграмма показывает обратимый путь: сначала остановить неверную конфигурацию, затем менять один известный token и хранить точное значение для возврата.'),
+ heading('Маршрут: симптом → причина → проверка → действие'),
+ orderedList([
+ 'Симптом. Зафиксируйте один экран, state и значение: например, primary button в loading использует другой background или не имеет focus-visible.',
+ 'Причина. Проверьте, это literal, неизвестный token, отсутствующий state или другой component role. Не лечите все варианты одним global rename.',
+ 'Инвентарь. Выпишите известные usage с name, role и state. Отделите подтверждённые места от предположений из поиска.',
+ 'Проверка модели. Запустите node web/scripts/upgrade-2022-05.mjs --verify-fixture. Она должна отклонить invalid configuration и восстановить previous value после rollback.',
+ 'Действие. Внесите один допустимый token correction или заведите отдельный variant после review. Не добавляйте undeclared key как «быстрое исключение».',
+ 'Проверка платформы. В отдельном реальном запуске проверьте собранный CSS, нужные viewports, states и accessibility scenario. Сохраните screenshot/diff/версии там, где это действительно выполнялось.',
+ ]),
+ heading('Visual payload — это очередь работы, не доказательство'),
+ paragraph('Payload перечисляет 375 и 1280, состояния default/hover/focus-visible/disabled/loading и пять token names. Такой формат полезен: он заставляет заранее назвать, что именно должен покрыть будущий visual check. Но пустой payload не видит pixels, а заполненный payload не знает, как конкретный browser применил cascade. Не стоит добавлять к нему synthetic score, ticks или «green» статус. Эти числа создают видимость измерения и затем мешают найти реальный артефакт, когда change нужно объяснить.'),
+ paragraph('Для настоящей visual-regression проверки понадобится другой слой: stable fixture page, поддерживаемый browser/version, viewport, screenshot baseline, правило допустимого diff и путь к output. Для доступности нужны ещё keyboard route и выбранная комбинация технологий. В 2022 году это уже нормальная инженерная дисциплина, но она начинается с честной границы. Нельзя заявить, что control проверен, только потому что его token names красиво лежат в JSON-like объекте.'),
+ heading('Ограничение и следующий проверяемый шаг'),
+ paragraph('Учебная модель намеренно не знает CSS inheritance, media queries, dark theme, locale, permissions, сетевой submit и всех usage в кодовой базе. Она не вычисляет contrast и не определяет, будет ли кнопка удобна. WAI-ARIA и WCAG помогают сформулировать нормы для семантики и доступности, но не превращают token correction в универсальное решение. Если один продукт требует destructive action или progress indicator, ему нужен отдельный contract, а не новый optional flag в primary button без обсуждения.'),
+ paragraph('Следующий проверяемый шаг — выбрать одну фактическую правку и оформить короткий change record: исходный token, причина, inventory до правки, expected states, previous value для rollback и ссылка на настоящий visual/a11y result после запуска. Если этой ссылки пока нет, record должен так и говорить. Такой скромный документ удерживает границу между планом и наблюдением, а затем позволяет расширять малую систему только по повторяющимся доказанным случаям.'),
+ heading('Историческая граница мая 2022'),
+ paragraph('Нормативные ссылки зафиксированы датами до мая 2022 года: CSS Custom Properties Candidate Recommendation Draft 11 ноября 2021 года, WAI-ARIA 1.2 Candidate Recommendation Draft 8 декабря 2021 года и WCAG 2.1 Recommendation 2018 года. Они не описывают данный inventory, token format или rollback API. Это учебные решения пакета; настоящие visual и accessibility результаты здесь сознательно не заявляются.'),
+ ], commonSources,
+);
+
+export const revisions = [practiceArticle, mechanismArticle, fieldArticle].map(({ proseLength, ...revision }) => revision);
+
+function verifyFixture() {
+ const fixture = runDesignSystemFixture();
+ const failed = Object.entries(fixture.assertions).filter(([, passed]) => passed !== true).map(([name]) => name);
+ if (failed.length > 0) {
+ console.error('FAIL fixture: ' + failed.join(', '));
+ process.exitCode = 1;
+ return;
+ }
+ console.log('PASS fixture: ' + Object.keys(fixture.assertions).length + '/' + Object.keys(fixture.assertions).length + ' assertions');
+}
+
+if (process.argv.includes('--verify-fixture')) verifyFixture();
+if (process.argv.includes('--print-revisions')) console.log(JSON.stringify(revisions));