# Задача: слот заголовка и ручки колонок у `GnTable`

**Статус:** не начата.
**Тип:** API компонента Vue-адаптера (слоты + ручки колонок). Vanilla-разметка не
затрагивается.
**Повод:** потребитель кита (gntodo) собирает на `GnTable` журнал действий, и шапке
понадобился заголовок-иконка вместо слова — сейчас это достижимо только передачей
готового узла в `label`, нигде не описанной. Там же понадобилось прятать колонки на
узком экране, и сделано это по номеру колонки. Запись ведётся по правилу платформы:
находка про общий ресурс живёт в доке ресурса, у потребителя остаётся только рабочий
обход и ссылка сюда.

**Источник:** gntodo — `frontend/src/views/JournalView.vue`, страница `/journal`
(правки 0.94); ui-kit 1.0.0 (коммит `e28f382`).
**Автор находки:** ИИ-агент (сессия разработки gntodo). **Автор задачи:** владелец
gntodo («замени текст на иконку коммента», «попробуй лучше адаптировать эту табличку
на моб»).
**Дата:** 2026-10-10. **Задача в трекере:** не заведена.

## Проблема

`src/vue/components/GnTable.js` рендерит шапку так:

```js
h("tr", { class: "table-row" }, props.columns.map(column => h("th", { scope: "col" }, column.label)))
```

То есть у шапки **нет ни одного слота** (объявлены только `cell-${column.key}` и
`empty`), а `column.label` кладётся как есть. Отсюда два следствия, оба пойманы на
живом приложении:

1. **Заголовок-иконка достижим только через `label`.** В gntodo в `label` передан
   узел: `h('i', { class: 'ph ph-chat-text', role: 'img', 'aria-label': … })` — слово
   «Комментарий» было шире колонки, в которой стоит одна кнопка. Работает (и это
   спасибо — слот не понадобился), но поведение нигде не описано: в `component-api.md`
   у `GnTable` перечислены только слоты ячеек, и потребитель сначала ищет несуществующий
   `header-*`. Ничего, кроме `label`, в шапку положить нельзя — ни сортировку, ни
   фильтр по колонке, ни кнопку.
2. **Адаптив таблицы потребитель делает по номеру колонки.** В gntodo на телефоне
   скрыты две колонки:
   `:deep(.table-row > :nth-child(4))`, `:nth-child(5)` — потому что у `columns`
   нет ни `class`, ни `align`, ни `hidden`, а таблице целиком `attrs.class`
   передаётся (`table data-list`), но колонкам — нет. Стоит поменять порядок колонок
   в массиве — и стиль молча скроет не ту колонку, ошибка не проявится ни сборкой,
   ни типами.

## Предложение

Минимальный шаг — **слот заголовка**, по аналогии с ячейками:

```js
h("th", { scope: "col", class: column.class }, slots[`header-${column.key}`]?.({ column }) || column.label)
```

Плюс `column.class` в тот же `th`/`td` (у `td` сейчас тоже пустой объект атрибутов) —
тогда потребитель прячет колонку по стабильному имени, а не по индексу, и та же ручка
годится для выравнивания и ширины через его собственный CSS.

Если решите делать слот — объявлять его в JSDoc и в `component-api.md` обязательно:
именно отсутствие строчки в доке и заставило искать обход.

Необязательные шаги на будущее (не обязательны к этой задаче): `column.align`
(`left`/`right`), `column.width`, и сортировка по клику на `th` — последнее уже
потребует событий и состояния сортировки в компоненте, а сейчас `GnTable` полностью
управляется пропами.

## Границы задачи

- **Разметка и классы не меняются**: `.table-wrapper`, `table.table.data-list`,
  `thead.table-head`, `tr.table-row`, `td.is-empty`, `caption.table-caption` — на них
  завязаны стили кита и потребители.
- Слоты `cell-${column.key}` и `empty` — контракт не трогаем (`cell-*` получает
  `{ row, column, value }`; у нового `header-*` достаточно `{ column }`).
- `columns` без новых полей обязан работать как раньше: `column.class` не задан —
  атрибута нет вовсе (не `class="undefined"`).
- Vanilla-разметка таблиц в демо — обычный `<table>`, ей ручки колонок не нужны;
  но строку «заголовок можно отдать узлом» в `docs/components/data-display.md` стоит
  добавить, потому что этот файл читают и потребители vanilla-сборки.

## Что нужно сдать (приёмка по правилам репозитория)

1. `src/vue/components/GnTable.js` — слот `header-${key}` и `column.class` в `th`/`td`,
   JSDoc с новыми слотами.
2. `docs/vue/component-api.md` — строки про слоты `GnTable` (сейчас там только
   `cell-*` и `empty`) и про то, что `label` рендерится как есть.
3. `docs/components/data-display.md` — пример таблицы с иконкой в заголовке.
4. `docs/catalog.json` (`npm run gen:catalog`) — если в каталоге у `GnTable` есть
   список слотов.
5. Юнит-тест контракта в `tests/unit/`: слот `header-*` перекрывает `label`;
   `column.class` попадает и в `th`, и в `td`; без слота и класса разметка прежняя.
6. `npm run release:check`.

## Как проверить глазами

Таблица из трёх колонок, у средней — `:class="'col-comment'"`, заголовок передан
узлом-иконкой, слот `#header-comment` передан кнопкой:

- в DOM у `th` видно `class="col-comment"` и тот же класс у `td` во всех строках;
- при ширине 767px и меньше потребительский CSS скрывает колонку по имени класса, и
  шапка остаётся согласованной с телом (число `th` равно числу `td`);
- колонки без `class` и без слота рендерятся ровно как в 1.0.0 (сравнить скриншотом).

## Почему именно так (контекст от потребителя)

В gntodo обе правки уже живут и проверены живым прогоном на 390px: заголовок-иконка
через `label`, скрытие колонок по `:nth-child`. Как только кит отдаст `header-*` и
`column.class`, потребитель заменит оба обхода — и вёрстка колонок перестанет
зависеть от порядка массива `columns`. До этого момента обход остаётся, а ссылка на
эту запись — в комментарии рядом с ним.
