# Веб-UI (htmx) Конвенция: *как* мы пишем код веб-UI — частичная замена фрагментов, опрос живых обновлений, обработчики действий, деградация без JS, ошибки. Это правила оформления кода (How), а не спецификация поведения — что именно UI показывает и какие действия обязан поддерживать, живёт в спеке OpenSpec. **Взято из проекта jellybit и записано наперёд: веб-UI в transcriber нет вовсе.** Есть только HTTP API на gin. Ни одного расхождения назвать нельзя — нечему расходиться; правила действуют с первой страницы, которую заведём. Логирование запросов — [logging.md](logging.md). Трансляция доменных ошибок наружу — [errors.md](errors.md). Здесь — только особенности htmx-транспорта, без повторения. ## Стек и границы htmx-first: `gin` плюс `html/template` (рендер на сервере) плюс htmx. Ничего сверх этого: **без шага сборки, без Node и сборщика, без реактивных фреймворков**. htmx вендорится и раздаётся с нашего же хоста (`go:embed`, `/static/vendor/`), без CDN. - Свой JS сведён к минимуму: только то, чего серверу знать не нужно. **Клиентского пересчёта доменного состояния нет** — состояние считает сервер, клиент лишь подменяет присланную разметку. - Alpine.js и SPA сознательно **не вводим**. Понадобится реактивный клиентский виджет — вводим отдельным изменением и записываем решение, не раньше. ## Единый источник разметки: партиал равен странице равен фрагменту Переиспользуемый кусок — это `{{define "name"}}` в каталоге партиалов. Тот же `{{define}}` рендерится **и** внутри страницы (`{{template "name" .}}`), **и** как ответ-фрагмент того же обработчика. Отдельной разметки под фрагмент не заводим — иначе она разъедется со страницей. **Инвариант: корень `{{define}}` — это элемент с целевым `id`**, например `#job-{id}`. `hx-swap="outerHTML"` заменяет весь корневой узел; если ответный фрагмент не несёт тот же корневой `id`, следующее действие или опрос не найдёт цель. Разметку и `id` держим в одном партиале. Сборку данных для шаблона выносим в отдельную функцию и зовём её и на полной странице, и во фрагменте — чтобы htmx-ветка не копировала сборку. ## Обработчик действия: ветвление htmx и редирект htmx-запрос определяем по заголовку `HX-Request: true`. Обработчик действия зовёт доменную операцию **одинаково** в обеих ветках, а дальше ветвится: без htmx — привычный редирект после POST (303); с htmx — перечитать актуальное состояние, собрать данные тем же сборщиком и отдать фрагмент. Рендер именованного шаблона идёт **в буфер** и только затем пишется в ответ: при ошибке шаблона клиент не получит полстраницы. ## Деградация без JS обязательна Формы действий остаются обычными `