From 5845bea9add5ca3e30086181b83baa40195bb96f Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 19:20:37 +0300 Subject: [PATCH] Rewrite editorial article 190 to 10/10 standard --- editorial/agent-rewrites/190.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editorial/agent-rewrites/190.json b/editorial/agent-rewrites/190.json index c025f8b..642fcfd 100644 --- a/editorial/agent-rewrites/190.json +++ b/editorial/agent-rewrites/190.json @@ -3,5 +3,5 @@ "slug": "editorial-2022-09-field-mobile-ux", "title": "Мобильный интерфейс: как вернуть скрытое действие и не сломать откат", "excerpt": "Если на узком экране основной шаг виден только при наведении или теряется за таблицей, сначала восстановите наблюдаемое действие, затем проверьте причину и только после этого меняйте layout.", - "contentHtml": "

Пользователь открывает карточку на телефоне и видит данные, но не может перейти к подтверждению. Кнопка появляется только при наведении, уезжает за горизонтальный scroll или растворяется после смены ориентации. Иногда экран выглядит аккуратно: шрифт не наезжает, карточки складываются в одну колонку. Но сценарий всё равно обрывается. Цена ошибки — не только плохой отзыв. Человек может повторить операцию, обратиться в поддержку или подтвердить не то действие. Команда потратит релиз на косметический breakpoint и сохранит исходную зависимость.

\n

Тезис простой: мобильный интерфейс готов, когда основной путь остаётся видимым, понятным и обратимым при зафиксированных условиях ввода и ширины. Media query помогает выбрать представление. Она не доказывает, что действие достижимо. Для проверки нужно разделить четыре вещи: наблюдаемый симптом, техническую причину, проверку реализации и действие с понятным откатом.

\n

Начните с конкретного разрыва

\n

Фраза «мобильная версия неудобна» не задаёт следующую проверку. Запишите один разрыв: «в состоянии narrow карточка показывает summary, но переход к details доступен только через hover» или «confirm находится справа от таблицы, а контейнер не сообщает, что его можно прокрутить». Добавьте экран, состояние данных, viewport, масштаб, язык, способ ввода и ожидаемый результат, если эти факты известны. Не дополняйте отчёт модельным телефоном или выдуманным пользовательским наблюдением.

\n

Затем отделите симптом от причины. Скрытая кнопка может быть следствием display, переполнения, неверного component state, условного рендера или решения считать hover достаточным маршрутом. Один и тот же симптом требует разных действий. Если сразу менять ширину breakpoint, вы проверите только одну гипотезу и можете замаскировать ошибку состояния.

\n

Механизм: среда, компонент и продуктовый контракт

\n

Среда сообщает ограниченные свойства. Media Queries Level 4 описывает media features, включая pointer, hover, any-pointer и any-hover. Эти признаки относятся к возможностям указателей и user agent. Они не описывают намерение человека, его зрение, размер пальца или то, заметил ли он control. В гибридном устройстве primary pointer и любой доступный pointer могут вести к разным значениям.

\n

Компонент отвечает за маршрут и состояние. В нём должны существовать label действия, summary перед необратимым шагом, details до подтверждения, ошибки и отмена. Продуктовый контракт отвечает за правило: главное действие нельзя оставлять только в hover-ветке, если выбранный путь должен работать без наведения. Это правило не следует автоматически из CSS-спецификации. Его нужно явно принять для конкретной операции.

\n

Такое разделение убирает ложный вывод «hover: none означает touch-only». Запрос описывает capability среды, а не тип человека. Он может включить дополнительную панель или изменить плотность layout. Он не должен удалять единственный путь к действию. Pointer Events задаёт модель событий, но наличие события не подтверждает, что пользователь увидел control или смог завершить операцию.

\n
Разделение границ перед исправлением
СлойЧто можно наблюдатьЧто проверятьЧего нельзя заключать
Средаmedia feature или тип pointerкакое представление применилосьчто человеку удобно
КомпонентDOM, CSS и stateвидимы ли label, details и confirmчто сценарий прошёл на всех устройствах
Продуктглавный и необратимый шагесть ли явный маршрут и отменачто изменится конверсия
Исследованиенаблюдение по описанному методуусловия, выборка и сигналчто локальная fixture заменила людей
\n

Учебный пример: запретить hover-only до браузерной проверки

\n

Ниже — ограниченный пример. Он принимает заранее заданный профиль и контракт действия. Он не вызывает matchMedia, не рендерит DOM, не создаёт pointer events и не сообщает о поведении пользователей. Его задача — проверить порядок решения: если основной шаг зависит только от hover в профиле без надёжного наведения, модель отклоняет маршрут и сохраняет текущий путь.

\n
function chooseMobileRoute(profile, action) {\n  if (!profile || !action?.label) {\n    return { status: 'invalid-contract' };\n  }\n\n  const narrowInput = profile.width === 'narrow' || profile.hover === 'none';\n  const hoverOnly = action.activation === 'hover-only';\n\n  if (narrowInput && hoverOnly) {\n    return { status: 'blocked', reason: 'explicit-control-required', rollback: 'keep-current-path' };\n  }\n\n  return { status: 'allowed', route: ['summary', 'details', 'confirm'] };\n}
\n

В этом примере blocked не означает, что браузер сломан. Он означает, что выбранный контракт не принимает единственный hover-маршрут при заданном учебном профиле. Значение keep-current-path тоже не запускает автоматический rollback. Оно запрещает принять новую ветку без решения о безопасном возвращении. В рабочем приложении откат может быть feature flag, старым route, revert commit или другим механизмом. Его выбирает владелец состояния операции.

\n
\"Диагностический
Схема показывает границу между локальным контрактом и проверкой реальной реализации. Она не изображает снимок браузера и не содержит production-результатов.
\n

Симптом → причина → проверка → действие

\n
Диагностическая карта мобильного маршрута
СимптомПричинаПроверкаДействие
Кнопка видна только при hoverГлавный путь привязали к hover-веткеПроверить DOM, CSS visibility и keyboard pathДобавить видимый labelled control
Confirm уехал за таблицуКонтейнер скрывает overflow или порядок выбран неверноПроверить размеры, scroll boundary и порядок шаговВынести confirm после summary/details или дать явный scroll
Иконка есть, смысл неясенLabel заменили декоративным знакомПроверить accessible name и текст до кликаВернуть короткую метку и предмет действия
После раскрытия теряется focusState меняет DOM без управляемого focusПройти клавиатурой и проверить focus orderСохранить фокус и объявить новое состояние
После исправления непонятно, что откатыватьНовый route смешан с серверным состояниемНазвать владельца состояния и необратимый шагСохранить прежний route до проверки и описать rollback
\n

Порядок действий

\n
  1. Зафиксируйте симптом. Запишите одно недоступное действие и условия, в которых оно исчезает. Не заменяйте описание ярлыком «плохой mobile UX».
  2. Найдите владельца перехода. Посмотрите DOM, component state, CSS visibility, overflow и route contract. Установите, построен ли control и только скрыт или он вообще не рендерится.
  3. Отделите capability от предположения. Если код читает media features, запишите, что именно они сообщают. Не называйте их классификатором пользователя и не делайте из них доказательство удобства.
  4. Проверьте контракт. Убедитесь, что путь сохраняет summary → details → confirm, имеет label, ошибку, отмену и явный способ активации. Учебный код проверяет только этот порядок.
  5. Исправьте маршрут. Добавьте видимый control, сохраните контекст перед подтверждением и не прячьте отмену или ошибку за hover. Не начинайте с декоративных отступов.
  6. Проверьте реализацию в браузере. Зафиксируйте viewport, zoom, шрифты, локаль, данные, клавиатуру, pointer и orientation. Пройдите normal, loading, error, empty и long-content состояния.
  7. Проверьте отрицательный путь. Остановитесь, если неизвестен владелец state, отсутствует label, данные меняются конкурентно, focus теряется или новый путь может повторить необратимое действие.
  8. Оформите откат. До выпуска назовите старый route, условие остановки и способ возврата. Не удаляйте прежнюю ветку только потому, что локальная модель вернула allowed.
\n

Что проверка должна показать

\n

В browser-check основной action должен быть виден без hover. Перед ним должна быть понятна предметная операция. Details должны открываться явным управляемым способом. После открытия focus не должен исчезать, а ошибка должна оставаться читаемой в узком контейнере. Если таблица требует прокрутки, интерфейс должен сообщать о границе и не прятать единственный confirm за ней. Эти утверждения относятся к конкретной реализации и набору условий проверки.

\n

Проверьте не только узкий viewport. Медленный шрифт, длинная локаль, крупный системный текст, пустые данные, длинное имя и landscape могут сломать маршрут иначе, чем условные 320 или 375 CSS-пикселей. Проверяйте фактический текст. «Одна колонка» не является доказательством reflow, а большой touch target не объясняет, что именно он подтверждает.

\n

Нужен usability-test — проведите отдельный метод с задачей, условиями набора и способом записи наблюдений. Нужен accessibility review — зафиксируйте критерии и scope. Учебная функция и unit assertion не заменяют ни то ни другое. Нельзя писать «девять пользователей успешно прошли сценарий», если есть только девять проходов функции.

\n

Ограничения и отрицательный путь

\n

Media query не решает проблему данных, авторизации, сетевой задержки, серверного конфликта или повторного подтверждения. Pointer event не проверяет зрительную заметность, фокус, semantics и размер текста. Browser test не доказывает потребности всех аудиторий. Accessibility-критерий не обещает коммерческий эффект. Каждый результат нужно связывать с тем артефактом и условиями, которые его получили.

\n

Остановитесь, если причина остаётся неразличимой. Например, если кнопка не видна, но неизвестно, отсутствует ли она в DOM или скрыта стилем, сначала получите этот факт. Остановитесь также, если новый маршрут меняет смысл операции и откат не возвращает прежнее состояние. Нельзя называть отсутствие production-данных доказательством отсутствия проблемы. В этом случае записывают неизвестное и не усиливают формулировку.

\n

Проверяемый критерий готовности

\n

Работа готова к следующему этапу, если другой инженер без устного пояснения может ответить на пять вопросов: какое действие было недоступно; при каких условиях; где находится причина; каким тестом проверяется исправление; что произойдёт при отказе или откате. В browser-check есть видимый label, путь без hover, сохранённый focus, обработанные loading/error состояния и проверенная граница overflow. В задаче указаны владелец состояния и точка остановки.

\n

Если остаётся только фраза «на телефоне стало лучше», готовности нет. Если модель прошла, но браузерный путь не проверен, готов локальный контракт, а не интерфейс. Если браузерный путь прошёл, но неизвестно, как вернуть старый route, выпуск не готов. Такой критерий не обещает успех продукта. Он показывает, что команда проверила именно тот разрыв, который заявила, и умеет остановиться на отрицательной ветке.

\n

Проверяемые источники

\n" + "contentHtml": "

Возьмём учебный случай: на телефоне пользователь открывает карточку, видит данные, но не может перейти к подтверждению. Кнопка появляется только при наведении, уезжает за горизонтальный scroll или исчезает после смены ориентации. Экран может выглядеть аккуратно: шрифт не наезжает, карточки складываются в одну колонку. Но сценарий всё равно обрывается. Цена ошибки — повторная операция, обращение в поддержку или неверное подтверждение. Команда потратит релиз на косметический breakpoint и сохранит исходную зависимость.

\n

Главный критерий здесь не «страница стала адаптивной». Мобильный интерфейс готов, когда основной путь остаётся видимым, понятным и обратимым при зафиксированных условиях ввода и ширины. Media query помогает выбрать представление, но не доказывает достижимость действия. Поэтому разбор нужно вести в четыре шага: наблюдаемый симптом, техническая причина, проверка реализации и действие с понятным откатом.

\n

Начните с конкретного разрыва

\n

Фраза «мобильная версия неудобна» не задаёт следующую проверку. Запишите один разрыв: «в состоянии narrow карточка показывает summary, но переход к details доступен только через hover» или «confirm находится справа от таблицы, а контейнер не сообщает, что его можно прокрутить». Добавьте экран, состояние данных, viewport, масштаб, язык, способ ввода и ожидаемый результат, если эти факты действительно известны. Не дополняйте отчёт модельным телефоном или выдуманным пользовательским наблюдением.

\n

Затем отделите симптом от причины. Скрытая кнопка может быть следствием display, переполнения, неверного component state, условного рендера или решения считать hover достаточным маршрутом. Один симптом требует нескольких гипотез. Если сразу менять ширину breakpoint, вы проверите только одну из них и можете замаскировать ошибку состояния.

\n

Механизм: среда, компонент и контракт действия

\n

Media Queries Level 4 описывает media features, включая pointer, hover, any-pointer и any-hover. Это свойства user agent и устройства вывода, а не классификация человека. В гибридном устройстве primary pointer и любой доступный pointer могут иметь разные значения. Поэтому условие hover: none нельзя читать как «это точно touch-only» и нельзя использовать как доказательство удобства.

\n

Компонент отвечает за маршрут и состояние. В нём должны существовать label действия, summary перед необратимым шагом, details до подтверждения, ошибка и отмена. Продуктовый контракт отвечает за отдельное правило: главное действие нельзя оставлять только в hover-ветке, если выбранный путь должен работать без наведения. Это правило не следует автоматически из CSS-спецификации; его принимают для конкретной операции.

\n

Pointer Events задаёт модель событий и тип указателя. Наличие события ещё не подтверждает, что control виден, назван и позволяет завершить операцию. WCAG 2.1 помогает проверить клавиатурный путь и reflow, но соответствие относится к конкретной разметке и условиям. Ни одна из этих спецификаций не заменяет проверку фактического маршрута в браузере.

\n
Разделение границ перед исправлением
СлойЧто можно наблюдатьЧто проверятьЧего нельзя заключать
Средаmedia feature или тип pointerкакое представление применилосьчто человеку удобно
КомпонентDOM, CSS и stateвидимы ли label, details и confirmчто сценарий прошёл на всех устройствах
Продуктглавный и необратимый шагесть ли явный маршрут и отменачто изменится конверсия
Исследованиенаблюдение по описанному методуусловия, выборка и сигналчто локальная fixture заменила людей
\n

Учебный контракт с воспроизводимым результатом

\n

Ниже — самостоятельный пример на JavaScript. Он не вызывает matchMedia, не рендерит DOM, не создаёт pointer events и не сообщает о поведении пользователей. Профиль и действие заданы вручную, поэтому функция проверяет только локальный порядок решения: hover-only блокируется на ограниченном пути, а явный control сохраняет маршрут summary → details → confirm.

\n
function chooseMobileRoute(profile, action) {\n  if (!profile || !action?.label) {\n    return { status: 'invalid-contract' };\n  }\n\n  const constrainedPath = profile.width === 'narrow' || profile.hover === 'none';\n  const hoverOnly = action.activation === 'hover-only';\n\n  if (constrainedPath && hoverOnly) {\n    return {\n      status: 'blocked',\n      reason: 'explicit-control-required',\n      rollback: 'keep-current-path',\n    };\n  }\n\n  if (action.activation !== 'explicit') {\n    return { status: 'blocked', reason: 'unknown-activation' };\n  }\n\n  return { status: 'allowed', route: ['summary', 'details', 'confirm'] };\n}\n\nconst cases = [\n  {\n    profile: { width: 'narrow', hover: 'none' },\n    action: { label: 'Открыть подтверждение', activation: 'hover-only' },\n  },\n  {\n    profile: { width: 'narrow', hover: 'none' },\n    action: { label: 'Открыть подтверждение', activation: 'explicit' },\n  },\n];\n\nconsole.log(cases.map(({ profile, action }) => chooseMobileRoute(profile, action)));
\n

Ожидаемый результат — сначала объект со статусом blocked, причиной explicit-control-required и rollback-маркером keep-current-path; затем объект со статусом allowed и маршрутом summary, details, confirm. Этот вывод можно получить обычным запуском файла в Node.js. Он не означает, что браузер уже проверен: keep-current-path запрещает принять новую ветку, но не выполняет автоматический откат приложения.

\n
\"Диагностический
Схема разделяет локальный контракт и проверку реальной реализации. Она не изображает снимок браузера и не содержит production-результатов.
\n

Симптом → причина → проверка → действие

\n
Диагностическая карта мобильного маршрута
СимптомПричинаПроверкаДействие
Кнопка видна только при hoverГлавный путь привязали к hover-веткеПроверить DOM, CSS visibility и keyboard pathДобавить видимый labelled control
Confirm уехал за таблицуКонтейнер скрывает overflow или порядок выбран неверноПроверить размеры, scroll boundary и порядок шаговВынести confirm после summary/details или дать явный scroll
Иконка есть, смысл неясенLabel заменили декоративным знакомПроверить accessible name и текст до кликаВернуть короткую метку и предмет действия
После раскрытия теряется focusState меняет DOM без управляемого focusПройти клавиатурой и проверить focus orderСохранить фокус и объявить новое состояние
После исправления непонятно, что откатыватьНовый route смешан с серверным состояниемНазвать владельца состояния и необратимый шагСохранить прежний route до проверки и описать rollback
\n

Порядок действий

\n
  1. Зафиксируйте симптом. Запишите одно недоступное действие и условия, в которых оно исчезает. Не заменяйте описание ярлыком «плохой mobile UX».
  2. Найдите владельца перехода. Посмотрите DOM, component state, CSS visibility, overflow и route contract. Установите, построен ли control и только скрыт или он вообще не рендерится.
  3. Отделите capability от предположения. Если код читает media features, запишите, что именно они сообщают. Не называйте их классификатором пользователя и не делайте из них доказательство удобства.
  4. Проверьте контракт. Убедитесь, что путь сохраняет summary → details → confirm, имеет label, ошибку, отмену и явный способ активации. Учебный код проверяет только этот порядок.
  5. Исправьте маршрут. Добавьте видимый control, сохраните контекст перед подтверждением и не прячьте отмену или ошибку за hover. Не начинайте с декоративных отступов.
  6. Проверьте реализацию в браузере. Зафиксируйте viewport, zoom, шрифты, локаль, данные, клавиатуру, pointer и orientation. Пройдите normal, loading, error, empty и long-content состояния.
  7. Проверьте отрицательный путь. Остановитесь, если неизвестен владелец state, отсутствует label, данные меняются конкурентно, focus теряется или новый путь может повторить необратимое действие.
  8. Оформите откат. До выпуска назовите старый route, условие остановки и способ возврата. Не удаляйте прежнюю ветку только потому, что локальная модель вернула allowed.
\n

Что браузерная проверка должна показать

\n

В browser-check основной action должен быть виден без hover, а перед ним должна быть понятна предметная операция. Details должны открываться явным управляемым способом. После открытия focus не должен исчезать, ошибка должна оставаться читаемой, а единственный confirm не должен прятаться за overflow. Для клавиатуры отдельно пройдите Tab, активацию и возврат фокуса. Для reflow проверьте узкую область просмотра без потери содержимого и действия.

\n

Проверьте не только условные 320 или 375 CSS-пикселей. Длинная локаль, крупный системный текст, пустые данные, длинное имя и landscape могут сломать маршрут иначе. Фиксируйте фактический текст, browser, zoom, viewport, способ ввода и состояние данных. «Одна колонка» не является доказательством reflow, а большой touch target не объясняет, что именно он подтверждает.

\n

Usability-test требует отдельного метода с задачей, условиями набора и способом записи наблюдений. Accessibility review требует критериев и scope. Учебная функция и unit assertion не заменяют ни то ни другое. Нельзя писать «девять пользователей успешно прошли сценарий», если есть только девять проходов функции.

\n

Ограничения и отрицательный путь

\n

Media query не решает проблему данных, авторизации, сетевой задержки, серверного конфликта или повторного подтверждения. Pointer event не проверяет зрительную заметность, фокус, semantics и размер текста. Browser test не доказывает потребности всех аудиторий. Accessibility-критерий не обещает коммерческий эффект. Каждый результат нужно связывать с артефактом и условиями, которые его получили.

\n

Остановитесь, если причина остаётся неразличимой. Если кнопка не видна, но неизвестно, отсутствует ли она в DOM или скрыта стилем, сначала получите этот факт. Остановитесь также, если новый маршрут меняет смысл операции и откат не возвращает прежнее состояние. Отсутствие production-данных не доказывает отсутствие проблемы: в этом случае записывают неизвестное и не усиливают формулировку.

\n

Проверяемый критерий готовности

\n

Работа готова к следующему этапу, если другой инженер без устного пояснения может ответить на пять вопросов: какое действие было недоступно; при каких условиях; где находится причина; каким тестом проверяется исправление; что произойдёт при отказе или откате. В browser-check есть видимый label, путь без hover, сохранённый focus, обработанные loading/error состояния и проверенная граница overflow. В задаче указаны владелец состояния и точка остановки.

\n

Если остаётся только фраза «на телефоне стало лучше», готовности нет. Если модель прошла, но браузерный путь не проверен, готов локальный контракт, а не интерфейс. Если браузерный путь прошёл, но неизвестно, как вернуть старый route, выпуск не готов. Критерий показывает, что команда проверила заявленный разрыв и умеет остановиться на отрицательной ветке.

\n

Проверяемые источники

\n

Для материала о сентябре 2022 года используются версии, доступные к этой дате: Media Queries Level 4 Candidate Recommendation Draft от 25 декабря 2021 года, Pointer Events Level 2 Recommendation от 4 апреля 2019 года и WCAG 2.1 Recommendation от 5 июня 2018 года. Плавающие текущие страницы не нужны для доказательства исторического утверждения; важны датированные документы и их ограниченный статус.

\n" }