web-ui: сбой без фрагмента и запрос без поля поверхности

- R34: глобальный слушатель htmx:responseError — на 5xx htmx ничего не
  свопит, и интерфейс замирает без признака сбоя
- R35: 400 вместо поверхности по умолчанию, иначе своп идёт с чужим id
- в R14 снято противоречие: слушатель больше не числится среди
  отвергаемых настроек
This commit is contained in:
av
2026-07-25 20:15:36 +03:00
parent 477499877d
commit f073a68f75
+40 -5
View File
@@ -203,16 +203,35 @@ s.render(w, "source_block", view) // фрагмент = тот же шаб
сообщением; доменная ошибка на htmx-пути не транслируется в HTTP-статус.
**Почему.** В htmx 2.x ответы 4xx/5xx по умолчанию не свопят DOM — то есть
пользователь не увидит ничего. Настроить это можно
(`htmx.config.responseHandling`, расширение `response-targets`, слушатель
`htmx:responseError`), но любая настройка — свой JS-конфиг на клиенте, и
платится она из R1 и R2. Для REST API и не-JS редиректа с `?err=` статус
по-прежнему используется: там его кто-то читает.
пользователь не увидит ничего. Своп ошибочных ответов настраивается
(`htmx.config.responseHandling`, расширение `response-targets`), но любая
такая настройка — свой JS-конфиг на клиенте, и платится она из R1 и R2.
Сообщить о сбое, для которого фрагмента нет вовсе, — отдельная задача, и
её решает глобальный слушатель (R34). Для REST API и не-JS редиректа с
`?err=` статус по-прежнему используется: там его кто-то читает.
Цена решения: в логе доступа провалившееся действие выглядит как `200`.
Искать его надо по доменной записи об исходе операции (`lang/go/logging.md`),
а не по коду ответа.
### R34. Сбой без ответа-фрагмента показывается глобальным слушателем
**ДОЛЖЕН.** Один глобальный слушатель `htmx:responseError` и
`htmx:sendError` показывает нейтральное сообщение о неудаче запроса; своп
ошибочных ответов в целевые регионы (`htmx.config.responseHandling`,
`response-targets`) не настраивается.
**Почему.** R14 закрывает доменный отказ, до которого обработчик дошёл.
Паника, сбой шаблона и обрыв сети отдают 5xx или ничего, htmx 2.x такое не
свопит — регион не меняется, интерфейс замирает без единого признака сбоя,
и пользователь повторяет действие, которое могло уже примениться. Слушатель
— несколько строк без доменного состояния, то есть внутри границы R2, и он
не спорит с R14: там настройки отвергнуты как замена фрагменту, который
обработчик в состоянии отдать, а здесь фрагмента нет по определению. Своп
тела ошибки в целевой регион стоил бы дороже: страница 500 не несёт
целевого `id`, и после первого же такого свопа регион перестаёт находиться
(R5).
### R15. Наружу идёт сообщение публичного канала
**ДОЛЖЕН.** Во фрагмент попадает нейтральный текст по правилам
@@ -394,6 +413,22 @@ htmx-атрибутов при этом само работает маркеро
действием, поэтому связь «эта страница → этот фрагмент» читается там, где
её заводят.
### R35. Запрос без поля поверхности получает 400
**ДОЛЖЕН.** Обработчик, различающий поверхности (R28), отвечает статусом
400, когда поля `surface` в запросе нет; поверхность по умолчанию не
выбирается.
**Почему.** Поле кладёт в форму наш же шаблон, поэтому его отсутствие —
дефект формы, а не вход пользователя. Поверхность по умолчанию маскирует
такой дефект молча неверным фрагментом: своп с чужим `id` проходит, после
чего регион перестаёт находиться таргетом (R5), и ошибка воспроизводится
как «интерфейс иногда застывает». 400 не свопится и всплывает сообщением
глобального слушателя (R34) — сразу и на той странице, где форму сломали.
Вкладка, открытая до появления поля, получает тот же 400 и чинится
перезагрузкой; это дешевле, чем молча неверный фрагмент в актуальной
разметке.
## Статика, вендоринг, кэш
Раздел не про htmx — это упаковка любого server-rendered приложения;