Отзыв с iPhone после первого живого прогона. Масштабирование запрещено (ADR-066): viewport как в соседнем sixlines плюс viewport-fit=cover, мета-теги установленного приложения, touch-action против двойного тапа и страховка на JS — гашение жестов iOS, потому что мета-строку Safari вправе проигнорировать. Размеры шрифтов не тронуты: 14 px из брифа остаются, поднимать поля до 16 px владелец отклонил. Цена — доступность для тех, кому зум нужен — записана в ADR и в ui.md честно. Версия в подвале (ADR-065, 067): GET /api/config отдаёт короткую ревизию и время коммита. Именно коммита, а не сборки: штамп компиляции в бинаре means каждая пересборка даёт новый хеш, и обещание ADR-022 «бинарь собирается из тега байт в байт» перестаёт проверяться. Самообновление PWA (ADR-068, 070): в режиме приложения на iPhone перезагрузки нет, и старая версия жила бы вечно. Проверка при возврате в приложение, включение ждущей оболочки, перезагрузка — но не тогда, когда в строке набран текст. Замок от петли сравнивает версию оболочки, а не считает перезагрузки: счётчик отменял саму фичу, долгоживущая вкладка переставала обновляться. Замерено: было 30, 60, 120, 240 секунд между релизами, стало ровно 30. Строка ввода — редактируемый блок (ADR-069): iOS рисует над клавиатурой полосу со стрелками и «готово» для полей формы, и убрать её со страницы нельзя. Для contenteditable не рисует. Режим plaintext-only с проверкой, что он применился, и запасным путём с разбором вставки: невалидное значение атрибута в части браузеров делает блок нередактируемым вовсе. Чтение и запись только через textContent, innerHTML в клиенте по-прежнему нет. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
7.3 KiB
ADR-069: Строка сообщения — редактируемый блок, а не поле формы
Уточняет ADR-024 («Чат» и «Доступность» в docs/ui.md) и ADR-066: в перечне элементов управления, по которым второй тап не гасится, прибавился редактируемый блок.
Уточнён ADR-071: предел держится до вставки и режет приходящее, а не хвост блока.
Контекст
На iPhone при фокусе в строке сообщения над клавиатурой висит системная полоса помощника форм: две стрелки перехода между полями и «готово». Стрелки неактивны — поле на экране одно, переходить некуда, — а полоса занимает место и ломает ощущение приложения, ради которого принят ADR-066.
Убрать её со страницы нечем: полосу рисует система, и для input и textarea она бывает всегда. Единственный работающий приём — перестать быть полем формы: у редактируемого блока (contenteditable) iOS полосы не показывает.
Цена приёма известна заранее. Блок принимает вставку с разметкой, ставит на Enter <div> и <br>, не знает ни placeholder, ни maxLength, ни disabled, а innerHTML в этом клиенте запрещён (ADR-001, CSP): сообщения — пользовательские данные.
Решение
- Блоком становится только строка сообщения в чате. Вход, регистрация, смена пароля и «новый чат» остаются на настоящих
input: там нуженtype="password"для менеджеров паролей, а полоса на разовом экране не мешает. - Блок открывается значением
plaintext-only: в нём браузер кладёт внутрь только текст, чем бы ни был буфер обмена. Значение не назначается, а проверяется чтением — незнакомое значение атрибута делает блок нередактируемым вовсе, то есть строка ввода перестала бы работать молча. Не применилось — остаётсяtrue, и тогда вставку, перетаскивание и перенос строки разбирает сам клиент: текст берётся изtext/plainи вписывается узлом текста. - Значение читается и пишется только
textContent.innerHTMLне появляется ни здесь, ни где-либо ещё. - Подсказка — правило CSS
:empty::beforeсcontent: attr(...); текст кладётся атрибутомdata-*. Опустевший блок очищается от<br>, который оставляет в нём браузер: с лишним узлом:emptyне срабатывает и подсказки не видно. - Клавиши прежние: на десктопе Enter отправляет, Shift+Enter переносит; на мобильном Enter переносит, отправляет кнопка «>».
enterkeyhint—enter: подпись клавиши обещает то, что клавиша делает, аsendобещал бы отправку, которой по нажатию не будет. autocapitalize="sentences"иautocorrect="on": сообщение — обычная речь, и заглавная в начале предложения с исправлением опечаток тут к месту. В форме входа и в «новом чате» они, наоборот, выключены — там ник и пароль.- Предел 4000 символов и счётчик после 3500 держит сам блок: длина считается по
textContent, лишнее обрезается у того, что приходит в блок, — так же, как считалmaxLengthуtextarea(ADR-071). - Заблокированный ввод (предупреждение о ключе, уход из комнаты) перестаёт быть редактируемым и говорит об этом
aria-disabled:disabledу блока нет. - Доступность:
role="textbox",aria-multiline="true", подписьaria-labelтем же текстом, что подсказка; цель нажатия на мобильном не меньше 44 px; фокус при открытии чата на десктопе — как был. web/js/zoom.js: блок добавлен в перечень элементов управления. Гашение второго тапа уносит с собойclick, а с ним и курсор — второй тап подряд не ставил бы курсор в строку ввода.web/js/pwa.js: проверка «есть ли черновик» (ADR-068) смотрит и в редактируемый блок —valueу него нет, набранное лежит вtextContent. Считается и заблокированный блок: недописанное в нём остаётся.
Следствия
- Полоса помощника форм в чате исчезает. На экранах входа и «нового чата» она остаётся — там она и не мешает.
- Проверить это можно только на iPhone: Chrome такой полосы не рисует вовсе, и никакой прогон её не увидит.
- Строка ввода перестала быть элементом формы. Отправку это не меняет: Enter и раньше обрабатывал клиент, а кнопка «>» остаётся кнопкой отправки формы.
- В запасном пути (браузер без
plaintext-only) отмена набранного (cmd+z) не помнит нашей вставки: она делается руками, а неexecCommand— тот в Chrome разбирает перенос строки в<div>, чего в блоке быть не должно. Там же держится<br>-заполнитель последней строки: перенос в самом конце браузер не рисует, и без заполнителя курсор оставался бы на прежней строке. - Автозаполнение и менеджеры паролей блока не касаются — в чате им нечего заполнять.
- Записано в
docs/ui.md, «Чат» и «Доступность».