0.134.48

Fixed

  • Баги страниц аудита: обрезанные «Аргументы»/«Результат», URL-табы, имя пользователя. Три проблемы на страницах /agents/[id]/audit и /settings/audit: (1) колонки «Аргументы»/«Результат» в табе инструментов отображались через max-w-[200px] truncate — полный текст был невидим; (2) на /settings/audit каждый вид лога не имел своего URL (таб жил только в $state, перезагрузка сбрасывала его, шаринг ссылки невозможен); (3) колонка «Пользователь» показывала числовой user_id вместо имени.
  • Бэкенд (internal/store/{sqlite,postgres}.go, ListAuditLogFiltered): SELECT … FROM audit_log заменён на LEFT JOIN users u ON u.id = a.user_id с COALESCE(u.display_name,'') AS user_name, COALESCE(u.username,'') AS user_login. Все ссылки на колонки в WHERE/ORDER BY квалифицированы алиасом a. (после JOIN id/action/resource_type стали неоднозначными — ORDER BY id падал с ambiguous column name). Структура AuditLogEntry (internal/store/store.go) расширена полями UserName/UserLogin (godoc на английском, без cross-stack ссылок). Миграция не нужнаaudit_log.user_id уже FK на users(id) ON DELETE SET NULL (миграция 002), users.display_name/username существуют. CreateAuditLog и точки вызова middleware не тронуты (JOIN заполняет поля при чтении).
  • Frontend — URL-табы (web/src/lib/features/audit/useAuditTab.svelte.ts): композабл по образцу usePermissionTab — чтение ?tab= из window.location.search при инициализации + history.replaceState при смене. VALID-массивы объявлены as const с типом (typeof)[number] (один источник правды для union, как в useMemoryData). Два класса: AgentAuditTab (tools/cron/system) и SettingsAuditTab (system/tools/cron/logs).
  • Frontend — PageTabs generic (web/src/lib/components/PageTabs.svelte): компонент параметризован <K extends string> (через generics="K extends string"), что убрало небезопасные касты as AgentAuditTabKey на обеих страницах и даст type-safety будущим потребителям.
  • Frontend — аккордеон (ExpandableToggle.svelte): клик по строке разворачивает detail-row (<pre class="whitespace-pre-wrap break-words">) с полным содержимым. Применён ко всем длинным полям: tools (args_summary/result_summary), system (details), cron (error), logs (message/attrs). Раскрытие — реальный <button> внутри первой ячейки (а не role="button" на <td>), с aria-expanded, aria-label с контекстом строки (timestamp) и aria-hidden на глифе. Состояние — SvelteSet<string> с composite-ключом ${tab}:${id} (исключает коллизии ID между таблицами).
  • Frontend — имя пользователя (web/src/lib/utils/formatUser.ts): shared-утилита userDisplay: display_name → иначе username → иначе числовой user_id → иначе «—». Используется на обеих страницах вместо entry.user_id ?? '—'.
  • Прочие правки по ocr review (7 прогонов): $derived для currentCount, COLS_BY_TAB record с комментариями-перечислением колонок, title={x || undefined} (вместо || '' — пустой tooltip), восстановлены sticky left-0 на ячейках времени, loading-spinner во всех табах, expandedIds.clear() консолидирован в loadCurrentTab, очистка entry-массивов при switchTab (stale data cross-tab), сброс фильтров при смене таба, onMount c try/finally для loading, async switchTab c await loadCronNames, button «Далее» disabled при currentCount < limit.
  • Тесты: Go internal/store/audit_join_test.goTestSQLiteStore_ListAuditLogFiltered_UserJoin: NULL user_id → пустые user_name/user_login, user с display_name → заполненные поля, удаление user (FK ON DELETE SET NULL) → строка остаётся с пустыми полями без паники. Vitest web/src/lib/utils/formatUser.test.ts — 5 кейсов fallback-цепочки.
  • Проверки: golangci-lint — 0 issues, go test ./internal/... — 27 пакетов green, npm run check — 0 errors (309 warnings — все pre-existing a11y), npm test — 420/420 passed, make build — OK. Оставлены как архитектурный scope-creep: snapshot-at-write-time для forensic integrity (задокументировано в godoc), server-side total/hasMore для точного disabled кнопки пагинации, глобальный AbortSignal для async-loader races (pre-existing паттерн всего приложения).