diff --git a/ordinis-admin-ui/src/components/layout/Sidebar.tsx b/ordinis-admin-ui/src/components/layout/Sidebar.tsx index 8deaa1f..b654051 100644 --- a/ordinis-admin-ui/src/components/layout/Sidebar.tsx +++ b/ordinis-admin-ui/src/components/layout/Sidebar.tsx @@ -382,7 +382,11 @@ function SidebarFooter({ } title={collapsed ? t('sidebar.expand', { defaultValue: 'Развернуть' }) : t('sidebar.collapse', { defaultValue: 'Свернуть' })} className={cn( - 'border-t border-line shrink-0 h-8 flex items-center gap-2 text-cap text-mute hover:text-ink hover:bg-surface-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', + // h-11 = 44px WCAG AAA touch target per DESIGN.md §13 (был h-8/32px, + // отмечено в /design-review TODOS.md P1-3). Desktop sidebar только + // (hidden lg:flex) — mobile использует burger из TopBar. h-11 не + // ломает вертикальный rhythm и matches TopBar h-11. + 'border-t border-line shrink-0 h-11 flex items-center gap-2 text-cap text-mute hover:text-ink hover:bg-surface-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', collapsed ? 'justify-center' : 'px-4', )} > diff --git a/ordinis-admin-ui/src/components/version/WhatsNewButton.tsx b/ordinis-admin-ui/src/components/version/WhatsNewButton.tsx index 379c7f0..c558db4 100644 --- a/ordinis-admin-ui/src/components/version/WhatsNewButton.tsx +++ b/ordinis-admin-ui/src/components/version/WhatsNewButton.tsx @@ -44,7 +44,10 @@ export function WhatsNewButton() { aria-label={label} onClick={() => setOpen(true)} className={cn( - 'relative size-7 rounded-md text-ink-2 hover:text-ink hover:bg-surface-2', + // size-11 = 44×44 WCAG AAA touch target per DESIGN.md §13 (aligned + // с другими TopBar icon buttons — Bell, ThemeSwitch). Icon size=14 + // остаётся, wrapper увеличен для тактильной площади на mobile. + 'relative size-11 rounded-md text-ink-2 hover:text-ink hover:bg-surface-2', 'inline-flex items-center justify-center transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', )} diff --git a/ordinis-admin-ui/src/lib/useUserDisplay.tsx b/ordinis-admin-ui/src/lib/useUserDisplay.tsx index c763fa5..f5b2b63 100644 --- a/ordinis-admin-ui/src/lib/useUserDisplay.tsx +++ b/ordinis-admin-ui/src/lib/useUserDisplay.tsx @@ -3,15 +3,20 @@ import { useAuth } from '@/auth/useAuth' import { apiClient } from '@/api/client' /** - * Lookup user display info по UUID (JWT sub). Backed by backend in-memory - * cache (заполняется на каждом authenticated request через - * JwtUserCaptureFilter). Returns null если sub не в кэше — frontend - * fallback'нёт на short UUID. + * Lookup user display info по UUID (JWT sub). Backed by backend trio: + * hot ConcurrentHashMap → Postgres user_display_cache → Keycloak Admin + * REST (Tier 3 on-demand). См. UserDisplayService.find в backend. * *

Stale-while-revalidate: TanStack Query кэширует 5 минут, refetch - * on focus. Cheap — endpoint = O(1) hash lookup, no DB hit. + * on focus. Cheap — hot cache hit = O(1), даже tier 3 KC lookup + * ~10-50ms на cold sub. * - *

404 → данных нет; не считаем error (через retry: false + handle null). + *

404 → данных нет (sub неизвестен realm'у); не считаем error + * (через retry: false + handle null). Frontend fallback'нёт на em-dash + * placeholder (см. useUserDisplay). + * + *

Note: JwtUserCaptureFilter был удалён в Phase 2 — cache теперь + * lazy-populated через первое чтение на cache miss (tier 3). */ type UserDisplayInfo = { sub: string @@ -67,10 +72,12 @@ const useResolveUserDisplay = (uuid: string | null | undefined) => *

  • null/undefined uuid → {@code "anonymous"}.
  • * * - *

    Backend endpoint: {@code GET /api/v1/admin/users/{sub}/display} — backed - * by in-memory cache (заполняется JwtUserCaptureFilter на каждом authenticated - * request). Если backend pod restart'нулся, кэш пуст — postepenно заполняется - * обратно. Frontend gracefully fallback'ит на short UUID. + *

    Backend endpoint: {@code GET /api/v1/admin/users/{sub}/display} — + * backed by three-tier cache (hot ConcurrentHashMap → Postgres + * user_display_cache → Keycloak Admin REST on-demand). Cold pod + * стартует с пустого hot cache, но DB persists — sub'ы будут резолвиться + * сразу. Новые юзеры (никогда не появлялись в audit'е) — резолв через + * tier 3 KC Admin lookup на первый запрос. * *

    Используется везде где UI показывает actor: AUTHOR в таблицах * /reviews (record + schema), USER в /audit log, AUTHOR в schema draft