Агентный слой
Прогон
Экран прогона — это не компонент, а сборка: шапка с именем и счётчиками, фазы со свёрткой, таблица участников. Отдельного класса у него нет намеренно — всё уже есть, и заводить свой класс значило бы завести панель под другим именем.
Новое здесь одно: счётная мера.
Враждебный аудит worldbox-1: поиск самообмана по классам A–F.
- Идёт
- 21 с
- Агентов
- 7
- Токенов
- 186 000
Разбор
| Агент | Токенов | Вызовов | Время |
|---|---|---|---|
| разбор:docs-drift | 38 200 | 4 | 18 с |
| разбор:shared-and-chunk | 39 600 | 5 | 18 с |
| разбор:probes-assert | 37 900 | 7 | 18 с |
| разбор:silent-failure | 36 800 | 4 | 18 с |
| разбор:coverage-hole | — | — | — |
| разбор:history | — | — | — |
| разбор:eyes-only | — | — | — |
Опровержение
Разметка
<div class="inst-panel">
<div class="inst-panel-header">
<span class="inst-panel-title">audit-worldbox-1</span>
<span class="inst-badge" data-tone="running"><span class="inst-dot"></span>идёт</span>
<span class="inst-panel-actions">
<button class="inst-btn inst-btn--sm inst-btn--danger" type="button">Остановить</button>
</span>
</div>
<div class="inst-panel-body inst-stack">
<p class="inst-prose">Враждебный аудит worldbox-1: поиск самообмана по классам A–F.</p>
<dl class="inst-kv">
<dt>Идёт</dt><dd>21 с</dd>
<dt>Агентов</dt><dd>7</dd>
<dt>Токенов</dt><dd>186 000</dd>
</dl>
<div class="inst-section">
<div class="inst-section-head">
<span class="inst-section-title">Фазы</span>
</div>
<details class="inst-accordion-item" open>
<summary class="inst-accordion-head">
Разбор
<span class="inst-dots" role="progressbar" aria-valuenow="4" aria-valuemin="0" aria-valuemax="7"
aria-label="Разбор: агентов завершено">
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="running"></span>
<span class="inst-dot"></span>
<span class="inst-dot"></span>
</span>
</summary>
<div class="inst-accordion-body inst-panel-body--flush">
<table class="inst-table">
<thead>
<tr><th>Агент</th><th class="inst-num">Токенов</th><th class="inst-num">Вызовов</th><th class="inst-num">Время</th></tr>
</thead>
<tbody>
<tr><td>разбор:docs-drift</td><td class="inst-num">38 200</td><td class="inst-num">4</td><td class="inst-num">18 с</td></tr>
<tr><td>разбор:shared-and-chunk</td><td class="inst-num">39 600</td><td class="inst-num">5</td><td class="inst-num">18 с</td></tr>
<tr><td>разбор:probes-assert</td><td class="inst-num">37 900</td><td class="inst-num">7</td><td class="inst-num">18 с</td></tr>
<tr><td>разбор:silent-failure</td><td class="inst-num">36 800</td><td class="inst-num">4</td><td class="inst-num">18 с</td></tr>
<tr data-state="running"><td>разбор:coverage-hole</td><td class="inst-num">—</td><td class="inst-num">—</td><td class="inst-num">—</td></tr>
<tr><td>разбор:history</td><td class="inst-num">—</td><td class="inst-num">—</td><td class="inst-num">—</td></tr>
<tr><td>разбор:eyes-only</td><td class="inst-num">—</td><td class="inst-num">—</td><td class="inst-num">—</td></tr>
</tbody>
</table>
</div>
</details>
<details class="inst-accordion-item">
<summary class="inst-accordion-head">
Опровержение
<span class="inst-dots" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="3"
aria-label="Опровержение: агентов завершено">
<span class="inst-dot"></span>
<span class="inst-dot"></span>
<span class="inst-dot"></span>
</span>
</summary>
<div class="inst-accordion-body">
<div class="inst-empty">
<span class="inst-empty-title">Ещё не начиналось</span>
<span class="inst-empty-desc">Фаза стартует, когда разбор закроет все семь агентов.</span>
</div>
</div>
</details>
</div>
</div>
</div>
Использование
Прогон — не отдельный класс, а сборка: панель с шапкой, список свойств, счётная мера и секции с шагами. Своего у него ровно две вещи — счётная мера и карточка-ссылка.
<div class="inst-dots" role="progressbar" aria-label="Шагов выполнено"
aria-valuenow="3" aria-valuemin="0" aria-valuemax="4">
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="running"></span>
<span class="inst-dot"></span>
</div>
| Что | Обязательно | Почему |
|---|---|---|
role="progressbar" с тремя значениями на inst-dots |
да | Иначе «сколько из скольких» существует только как ряд кружков |
data-tone на каждой точке |
да | ok — сделано, running — идёт, без атрибута — ещё не начиналось |
inst-card--link у карточки-ссылки |
да | Без него базовый слой красит её акцентом и подчёркивает как обычную ссылку |
| Число рядом со счётной мерой | да | Семь точек глазом не считаются: «3 из 4» словами обязательно |
Когда использовать
| Используйте | Возьмите другое |
|---|---|
| Единиц мало и они СЧИТАЮТСЯ: агенты фазы, попытки, шарды | Непрерывная величина — мера: байты, проценты, заполненность |
| У каждой единицы своё состояние: сделана, идёт, упала | Именованные шаги мастера — шаги: там у каждого есть слово |
| Больше двух и меньше примерно пятнадцати | Десятки и сотни — мера с числом: пятьдесят точек не считаются глазом |
| Порядок между единицами не важен | Порядок важен и виден — дорожки прогонов: они про время |
Устройство
Разметка
<span class="inst-dots" role="progressbar" aria-valuenow="4" aria-valuemin="0" aria-valuemax="7"
aria-label="Агентов завершено">
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="running"></span>
<span class="inst-dot"></span>
<span class="inst-dot"></span>
</span>
Единиц семь, и каждая либо сделана, либо нет. Доля здесь — выдуманная точность: 4 из 7, нарисованные полосой на 57%, выглядят правдоподобно и сообщают то, чего система не знает. Это та же ложь, что определённая полоса, застрявшая на 90%, только тише.
Своей точки компонент не заводит: единица — обычный inst-dot.
Он уже умеет тон, пульсацию «идёт» и режим принудительных цветов, и второй
кружок в ките был бы вторым именем для того же.
Состояния
| Тон | Что значит |
|---|---|
| без атрибута | Ещё не начиналось |
data-tone="running" |
Идёт. Точка пульсирует |
data-tone="ok" |
Сделано |
data-tone="error" |
Упало |
data-tone="warn" |
Сделано с замечанием |
Пульсирует только идущая, и это та же пульсация, что у строки очереди и у шага: в ките она означает ровно одно, и заводить ей второй смысл нельзя.
JS
Подключите модуль один раз на страницу — инициализировать компоненты по отдельности не нужно, кит работает делегированием и видит узлы, пришедшие позже.
<script type="module" src="src/kit.js"></script>
Что делает кит
Свёртку фаз не делает никто: она на <details> и работает без скрипта.
Что остаётся приложению
Живые числа и остановка — данные и команда, а не оформление.
source.addEventListener('message', (e) => {
const { phase, done } = JSON.parse(e.data);
const dots = run.querySelectorAll('#' + phase + ' .inst-dot');
dots.forEach((dot, i) => {
if (i < done) dot.dataset.tone = 'ok';
else if (i === done) dot.dataset.tone = 'running';
else delete dot.dataset.tone;
});
dots[0].closest('.inst-dots').setAttribute('aria-valuenow', done);
});
aria-valuenow обновляется вместе с точками, а не «когда-нибудь потом».
Точки — это картинка; для скринридера прогресс существует только в атрибуте,
и забытый атрибут означает фазу, которая для него навсегда осталась в нуле.
Композиции
Тот же прогон одной строкой — когда их несколько и нужно выбрать. Здесь счётная мера работает лучше всего: пятнадцать точек читаются одним взглядом, и видно не только «сколько», но и что одна упала с замечанием. Полоса на 20% этого не сообщила бы.
Разметка
<div class="inst-stack inst-stack--tight">
<a class="inst-card inst-card--interactive inst-card--link" href="#">
<span class="inst-card-head">
<span class="inst-card-title">audit-worldbox-1</span>
<svg class="inst-icon" aria-hidden="true"><use href="#i-chevron"/></svg>
</span>
<span class="inst-card-sub">Workflow · 15 агентов · 7 мин 58 с</span>
<span class="inst-dots" role="progressbar" aria-valuenow="4" aria-valuemin="0" aria-valuemax="15"
aria-label="Агентов завершено">
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="warn"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="running"></span>
<span class="inst-dot"></span><span class="inst-dot"></span>
<span class="inst-dot"></span><span class="inst-dot"></span>
<span class="inst-dot"></span><span class="inst-dot"></span>
<span class="inst-dot"></span><span class="inst-dot"></span>
<span class="inst-dot"></span><span class="inst-dot"></span>
</span>
</a>
<a class="inst-card inst-card--interactive inst-card--link" href="#">
<span class="inst-card-head">
<span class="inst-card-title">review-terrain-08</span>
<svg class="inst-icon" aria-hidden="true"><use href="#i-chevron"/></svg>
</span>
<span class="inst-card-sub">Workflow · 3 агента · 41 с</span>
<span class="inst-dots" role="progressbar" aria-valuenow="3" aria-valuemin="0" aria-valuemax="3"
aria-label="Агентов завершено">
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
<span class="inst-dot" data-tone="ok"></span>
</span>
</a>
</div>
Карточка — ссылка целиком. Цель нажатия получается во всю строку, и растить её отдельно не приходится. Шеврон декоративен, имя ссылке даёт заголовок.
Сценарии
Ни одного нового класса, кроме inst-dots:
| Часть экрана | Чем собрана |
|---|---|
| Рамка и шапка | Панель |
| Состояние прогона | Бейдж с точкой |
| Остановка | Кнопка, вариант --danger |
| Счётчики | Список пар |
| Фаза со свёрткой | Аккордеон на <details> |
| Участники | Таблица с inst-num |
| Пустая фаза | Пустое состояние |
Это и есть проверка покрытия. Экран, который не собирается без инлайнового оформления, означает дыру в ките — правило конституции про живую спецификацию распространяется и сюда.
Правила
Так Счётная мера с числом рядом
Семь точек глазом не считаются. «3 из 4» словами обязательно.
Не так Точки без роли
Ряд кружков без role="progressbar" и трёх значений существует только для
зрячих.
Так Прогон собирается из готовых компонентов
Панель, список свойств, секции, шаги. Своего у прогона ровно две вещи — и это правильная пропорция.
Не так Новый класс под каждый экран прогона
Экран, который не собирается из кита, означает дыру в ките, а не потребность в ещё одном классе.
Доступность
| Роль | role="progressbar" на inst-dots плюс aria-valuenow/min/max. Без них «сколько сделано» существует только в числе закрашенных кружков |
| Имя | aria-label с тем, что именно считается. «4 из 7» без предмета не является сообщением |
| Точки | Декоративны для скринридера: значение несёт контейнер, а не они. Отдельных подписей им не нужно |
| Не только цвет | Идущая единица пульсирует, а не просто окрашена; завершённость озвучивается aria-valuenow |
| Уменьшенное движение | Пульсация замедляется, а не гаснет: кит обязан показывать занятость всегда |
| Печать | Точки печатаются: это данные, а не индикатор активности |
API
| Имя | Значение | Что делает |
|---|---|---|
| класс | ||
inst-dots | — | Счётная мера: сколько единиц из скольких. Единица — inst-dot |
inst-card-head | — | Шапка карточки: заголовок и значок перехода у дальнего края |
| модификатор | ||
inst-card--link | — | Карточка целиком является ссылкой. Без него base.css красит её акцентом и подчёркивает |
| атрибут | ||
role | progressbar | На inst-dots. Вместе с aria-valuenow/min/max |
data-tone | neutral · running · ok · warn · error | На каждой точке: ok — сделано, running — идёт, без атрибута — ещё не начиналось |
| токен | ||
--space-1 | 2px | Зазор между точками |
--size-dot | 6px | Сторона точки — от inst-dot |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument