Агентный слой
Лог
Поток строк от машины: время, уровень, сообщение. Моноширинный набор и колонки фиксированной ширины, поэтому лог читается столбцами, а не сплошным текстом.
Разметка
<div class="inst-log" role="log" aria-label="Лог прогона">
<div class="inst-log-line"><span class="inst-log-time">14:32:07</span><span class="inst-log-level">info</span><span>Запуск worldgen-01</span></div>
<div class="inst-log-line"><span class="inst-log-time">14:32:09</span><span class="inst-log-level">info</span><span>Прочитано 4 файла</span></div>
<div class="inst-log-line" data-tone="warn"><span class="inst-log-time">14:32:11</span><span class="inst-log-level">warn</span><span>chunks.bin занят, повтор через 1 с</span></div>
<div class="inst-log-line" data-tone="error"><span class="inst-log-time">14:32:16</span><span class="inst-log-level">error</span><span>EBUSY: не удалось прочитать chunks.bin</span></div>
</div>
Контракт
| Что | Обязательно | Почему |
|---|---|---|
role="log" и aria-label |
да | Строки, прибывающие во время работы, иначе не объявляются вовсе |
| Три узла в строке: время, уровень, сообщение | да | Колонки заданы шириной в ch; узел не на своём месте ломает выравнивание всех строк |
data-tone только у warn и error |
да | Обычная строка тона не несёт: в потоке из тысячи строк подсвечено то, что требует внимания |
| Моноширинный набор | да | Приходит от inst-log. Пропорциональный шрифт рассыпает колонку времени |
Доступность
role="log" |
Область, в которую добавляются записи. Скринридер объявит новые строки, не перечитывая весь лог |
| Живость | role="log" подразумевает aria-live="polite". Для потока в сотни строк в секунду это надо отключить явно: непрерывное озвучивание делает интерфейс неработоспособным |
| Имя области | aria-label обязателен: двa лога на экране без имён неразличимы на слух |
| Время — данные | inst-log-time берёт --text-muted (4.5:1), а не --text-faint: таймстемп читают, а не разглядывают |
| Не только цвет | Уровень написан словом (warn, error) рядом с цветом |
| Прокрутка | Область достижима с клавиатуры. Автопрокрутка вниз обязана останавливаться, когда пользователь прокрутил вверх |
| Перенос | white-space: pre-wrap и overflow-wrap: anywhere только на ячейке сообщения: на контейнере это схлопнуло бы колонки |
Устройство
Сетка объявлена на строке, то есть у каждой строки она своя. Если задать
колонку уровня по содержимому, её ширина будет меняться от слова к слову:
info уже, чем error, и сообщения поедут лесенкой. Поэтому
inst-log-level получает фиксированную ширину в 5ch.
Варианты
<div class="inst-log-line" data-tone="error">…</div>
| Тон | Что красится |
|---|---|
| нет атрибута | Только уровень, приглушённо |
warn |
Уровень |
error |
Уровень и всё сообщение |
Ошибка красится целиком: её ищут в тысяче строк, пролистывая глазом, а не читая.
JS
Подключите модуль один раз на страницу — инициализировать компоненты по
отдельности не нужно, instrument.js работает делегированием и видит узлы, пришедшие
позже.
<script type="module" src="instrument.js"></script>
Что делает instrument.js
Пример в шапке живой: наведите на лог и нажмите кнопку копирования.
Копирование: кнопка .inst-copy внутри .inst-copyable кладёт текст в буфер
и отвечает цветом. Подробности — на странице блока
кода.
<div class="inst-log inst-copyable">
<button class="inst-copy" type="button" aria-label="Скопировать лог"></button>
…
</div>
Что остаётся приложению
| Что | Почему не instrument.js |
|---|---|
| Прокрутка за хвостом | «Держаться низа, пока человек не отлистал вверх» — политика, а не оформление |
| Виртуализация | Лог агента идёт десятками тысяч строк; способ зависит от источника |
| Фильтр по уровню | Данные |
Прокрутку к хвосту приложение делает само, и это одна строка:
if (atBottom) log.scrollTop = log.scrollHeight;
API
| Имя | Что делает |
|---|---|
| класс | |
inst-log | Контейнер, прокручивается |
inst-log-line | Строка: три колонки |
inst-log-time | Отметка времени |
inst-log-level | Уровень, ширина 5ch |
| Имя | Значение | Что делает |
|---|---|---|
| атрибут | ||
data-tone | — | warn · error |
| Имя | Значение | Откуда |
|---|---|---|
| переменная | ||
--level-ink | --text-muted | задаёт приложение |
| токен | ||
--font-mono | ui-monospace, " , " , " , Consolas, " , monospace | одно на все ячейки |
--text-xs | 0.78125rem | база +4: масштаб |
--surface-sunken | light-dark(var(--n-2), var(--n-14)) | база +1: тема |
--radius-md | 8px | база +4: масштаб |
--space-4 | 8px | одно на все ячейки |
--pad-panel | var(--space-5) | база +14: плотность · масштаб |
--gap-inline | var(--space-4) | база +14: плотность · масштаб |
--warn-text | light-dark(var(--warn-5), var(--warn-3)) | база +1: тема |
--err-text | light-dark(var(--err-5), var(--err-3)) | база +1: тема |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument