diff --git a/frontend/.lint-ceiling.json b/frontend/.lint-ceiling.json index f11b6590..7114491f 100644 --- a/frontend/.lint-ceiling.json +++ b/frontend/.lint-ceiling.json @@ -1,13 +1,12 @@ { - "total": 132, + "total": 99, "rules": { - "@typescript-eslint/no-unused-vars": 39, - "@typescript-eslint/no-explicit-any": 26, - "react-hooks/set-state-in-effect": 16, - "react-hooks/static-components": 14, + "@typescript-eslint/no-unused-vars": 27, + "@typescript-eslint/no-explicit-any": 21, + "react-hooks/set-state-in-effect": 13, "react-hooks/preserve-manual-memoization": 12, - "react-refresh/only-export-components": 12, - "no-irregular-whitespace": 1, + "react-refresh/only-export-components": 10, + "react-hooks/static-components": 6, "react-hooks/purity": 3, "prefer-const": 2, "@typescript-eslint/ban-ts-comment": 1, diff --git a/frontend/e2e/empty-states.spec.ts b/frontend/e2e/empty-states.spec.ts index 5b5594ce..cbee8330 100644 --- a/frontend/e2e/empty-states.spec.ts +++ b/frontend/e2e/empty-states.spec.ts @@ -164,7 +164,7 @@ async function assertNoNonZeroMetrics(page: Page, where: string) { if (!text) continue; // Strip thousands separators (space, NBSP, narrow NBSP, comma) so "1 200" // is read as 1200 rather than as 1 and 200. - const digits = text.replace(/[\s  ,]/g, ''); + const digits = text.replace(/[\s\u00a0\u202f,]/g, ''); // "N/M" ratios — "0/4 étapes", "0/12 risques chiffrés". Only the numerator // is tenant data; the denominator is a total (onboarding steps, registered diff --git a/frontend/src/features/onboarding/PersonalizeCard.tsx b/frontend/src/features/onboarding/PersonalizeCard.tsx index b463ca6d..d112fb03 100644 --- a/frontend/src/features/onboarding/PersonalizeCard.tsx +++ b/frontend/src/features/onboarding/PersonalizeCard.tsx @@ -11,9 +11,12 @@ import { useUIStore, type Theme, type Variant } from '../../store/uiStore'; import { ACCENT_LABELS, ACCENT_PRESETS } from '../../shared/accentPresets'; // Swatch colours only; the list of accents comes from shared/accentPresets. +// The preview tokens in tokens.css (--swatch-*), which mirror each variant's +// real accent per theme. The literals that stood here (#0a84ff, #7c6cff) +// matched neither theme's accent. const SWATCH: Record = { - azure: '#0a84ff', - iris: '#7c6cff', + azure: 'var(--swatch-azure)', + iris: 'var(--swatch-iris)', }; const ACCENTS = ACCENT_PRESETS.map((key) => ({ key, diff --git a/frontend/src/features/settings/SettingsScreen.tsx b/frontend/src/features/settings/SettingsScreen.tsx index e4a14c4c..9837e16f 100644 --- a/frontend/src/features/settings/SettingsScreen.tsx +++ b/frontend/src/features/settings/SettingsScreen.tsx @@ -7,9 +7,8 @@ // Integrations, Notifications, Security, Billing, Danger. Endpoints whose tables // aren't migrated yet (roles/tenants/audit) degrade to an honest unavailable state. -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useSearchParams, useNavigate, useLocation } from 'react-router'; -import { toast } from 'sonner'; import { Settings as SettingsIcon, Users, @@ -22,13 +21,9 @@ import { Shield, CreditCard, AlertTriangle, - Plus, FileText, Check, - Trash2, - Copy, Database, - PowerOff, type LucideIcon, } from 'lucide-react'; import { @@ -46,8 +41,6 @@ import { useAuthStore } from '../../hooks/useAuthStore'; import { SessionsPanel } from '../auth/SessionsPanel'; import { ChangePasswordCard } from '../auth/ChangePasswordCard'; import { MembersView } from '../organization/MembersView'; -import { relTime } from '../risks/riskMap'; -import { api } from '../../lib/api'; import { useCustomFields, useTenants } from './adminData'; import { ApiTokensPanel } from './ApiTokensPanel'; import { @@ -57,14 +50,6 @@ import { import type { NotificationPreferencePatch } from '../notifications/notificationService'; import { useChannelConfig } from '../automation/useAutomation'; import { useVulnIntegrations, useVulnTicketing } from '../vulnerabilities/useVulnIntegrations'; -import { - DataTable, - useTableState, - type Column, - type Facet, - type RowAction, -} from '../../shared/datatable'; -import { DangerConfirm } from '../../shared/DangerConfirm'; import { PersonalizeCard } from '../onboarding/PersonalizeCard'; import { BillingPanel } from '../billing/BillingPanel'; import { DangerZonePanel } from '../billing/DangerZonePanel'; @@ -74,7 +59,6 @@ import { OrganizationProfileForm } from './OrganizationProfileForm'; import { OrganizationLogoField } from './OrganizationLogoField'; import { OrgLogo } from '../organization/OrgLogo'; import { ProfileTab } from '../profile/ProfileTab'; -import type { LocaleCode } from '../../i18n/locales'; import { useI18n } from '../../hooks/useI18n'; import { localeTag } from '../../i18n/locales'; diff --git a/frontend/src/i18n/__tests__/format.test.ts b/frontend/src/i18n/__tests__/format.test.ts index c3e57113..ab22811d 100644 --- a/frontend/src/i18n/__tests__/format.test.ts +++ b/frontend/src/i18n/__tests__/format.test.ts @@ -14,7 +14,7 @@ import { } from '../format'; /** Intl inserts narrow/non-breaking spaces; compare on the digits and marks. */ -const norm = (s: string) => s.replace(/[  ‎‏]/g, ' '); +const norm = (s: string) => s.replace(/[\u00a0\u202f\u200e\u200f]/g, ' '); describe('locale-aware formatting', () => { it('groups numbers in the reader’s conventions', () => { diff --git a/frontend/src/i18n/__tests__/translate.test.ts b/frontend/src/i18n/__tests__/translate.test.ts index b321e020..b13a87de 100644 --- a/frontend/src/i18n/__tests__/translate.test.ts +++ b/frontend/src/i18n/__tests__/translate.test.ts @@ -16,7 +16,7 @@ const fixture: Partial> = { }, }; -const norm = (s: string) => s.replace(/[  ‎‏]/g, ' '); +const norm = (s: string) => s.replace(/[\u00a0\u202f\u200e\u200f]/g, ' '); describe('translate', () => { it('reads a dotted key', () => { diff --git a/frontend/src/shared/useHotkeys.ts b/frontend/src/shared/useHotkeys.ts index 3b9d99bc..29929996 100644 --- a/frontend/src/shared/useHotkeys.ts +++ b/frontend/src/shared/useHotkeys.ts @@ -8,7 +8,7 @@ // while ⌘/Ctrl/Alt are held (those belong to the browser or ⌘K). Shift is allowed // because `?` is Shift+/. Modal-scoped keys (Esc, ⌘Enter) keep using useKeyboard. -import { useEffect, useRef } from 'react'; +import { useEffect, useLayoutEffect, useRef } from 'react'; export interface Hotkey { /** Single key to match, case-insensitive: 'n', '/', '?', 't', 'g'. */ @@ -25,8 +25,13 @@ function isTyping(target: EventTarget | null): boolean { export function useHotkeys(hotkeys: Hotkey[], enabled = true): void { // Keep the latest handlers without re-binding the listener every render. + // Updated in a layout effect, not during render (react-hooks/refs): it runs + // after every commit and before any later keydown, so the listener never + // sees stale handlers. const ref = useRef(hotkeys); - ref.current = hotkeys; + useLayoutEffect(() => { + ref.current = hotkeys; + }); useEffect(() => { if (!enabled) return;