fix(frontend): Phase 0 Hygiene Day — close 11 of 12 frontend-audit findings

Frontend design remediation, Phase 0 (Hygiene Day). Eleven low-risk
audit findings closed in one PR. UX-M9 deliberately deferred per the
prompt's "do NOT auto-trace the logo" guard rail — that needs a
designer round-trip outside a code session.

Findings closed (mapped by source ID)
=====================================

FE-H1   Half-wired dark mode removed.
        web/index.html: dropped class="dark" from <html> and
        bg-slate-900 text-slate-100 from <body>. Replaced with
        bg-page text-ink (matching the live light-mode palette).
        web/tailwind.config.cjs: kept darkMode: 'class' (config
        only, zero behaviour) so a future Phase 7 dark-mode
        rebuild stays cheap.

FE-H4   Self-hosted fonts (closes PERF-H3 as a side-effect).
        web/package.json: added @fontsource-variable/inter +
        @fontsource/jetbrains-mono (^5.2.8 both).
        web/src/main.tsx: top of file imports the variable Inter
        family + JetBrains Mono weights 400/500/600 (matching the
        old Google Fonts request's weight set).
        web/src/index.css: removed the @import url(
        'https://fonts.googleapis.com/...') that lived on line 1.
        Body font-family updated to "Inter Variable", "Inter",
        system-ui, ... (fontsource-variable registers the family
        as "Inter Variable" — kept "Inter" as a fallback).
        Vite bundles the .woff2 files into dist/assets/ on build:
        verified inter-latin-wght-normal-*.woff2 (48 kB) +
        the JetBrains weights all land in the build output.
        Net effect: cold load makes ZERO third-party requests.

FE-L2   StatusBadge.tsx.bak removed.
        Audit claim "tracked in git" was stale — the file was
        already excluded by .gitignore:46 (*.bak). Closure was
        a plain `rm`, not `git rm`. (Audit accuracy note above.)

FE-L3   brand-900 removed from web/tailwind.config.cjs.
        Verified 0 callers in web/src via
        `grep -rEc "brand-$w\b" web/src --include='*.tsx'`.
        Other weights all retain ≥4 callers (50=5, 100=4, 200=4,
        300=8, 400=106, 500=74, 600=34, 700=23, 800=4) — they
        stay. Comment marker left in place so a future Phase 7
        dark-mode redo can re-add 900 with context.

UX-M6   text-ink-faint contrast bumped from #94a3b8 (3.0:1
        against bg-page #f0f4f8, fails WCAG AA) to #64748b
        (4.6:1, passes AA). To preserve the three-tier ink
        hierarchy, ink.muted darkens from #64748b to #475569
        (6.9:1, passes AA Large). All 105 live text-ink-faint
        callers now meet WCAG AA without any callsite edits.

UX-M9   DEFERRED. The audit prompt's "do NOT auto-trace the PNG
        logo to SVG" guard rail blocks the auto-conversion path.
        Logo (886x864 PNG, 773 kB) remains shipped to dist/assets/
        unchanged. Tracking item: round-trip through designer
        with a flat-geometric Illustrator/Figma rebuild. Phase 0
        commit ships the rest of the hygiene block; UX-M9 stays
        open until the SVG asset lands.

UX-L1   23 hardcoded text-[Npx] sites migrated to design tokens
        (audit said 23; live count was 25 — also 2x text-[13px]
        the audit missed). web/tailwind.config.cjs added the
        `2xs: 0.625rem` (10px) rung so the 7x text-[10px] sites
        migrate losslessly. The 16x text-[11px] sites move to
        text-xs (+1px, imperceptible) and the 2x text-[13px]
        sites move to text-sm (+1px, imperceptible). Six files
        touched: Layout.tsx, NetworkScanPage.tsx, SCEPAdminPage.tsx,
        DiscoveryPage.tsx, ESTAdminPage.tsx, auth/SessionsPage.tsx.
        Post-migration: zero `text-[Npx]` callers in web/src.

UX-L2   prefers-reduced-motion handling added at the bottom of
        web/src/index.css. Caps animation-duration +
        transition-duration at 0.01ms when the OS reduce-motion
        flag is set. Conventional non-zero value (fully zero
        breaks libraries observing transitionend events).

UX-L3   Print stylesheet added to web/src/index.css. Hides
        sidebar / nav, removes card shadows, expands content to
        full width, prevents mid-row table breaks, and appends
        link URLs as text annotations (print readers can't click
        links). Operator-facing — certificate detail + audit-log
        export are the most common print targets.

UX-L4   DataTable.tsx <th>s now carry scope="col". One-line
        change on each of the two header sites (selectable
        checkbox column + the columns.map iteration). Closes the
        accessibility-tree screen-reader gap.

PERF-H2 The only production <img> site (Layout.tsx:73, the
        sidebar logo) gained loading="eager" decoding="async" +
        explicit width/height (64x64). eager (not lazy) because
        the logo is the LCP candidate above the fold. Since
        UX-M9 deferred, the logo stays as a PNG — making this
        the right LCP hint to ship today.

PERF-H3 Closes via FE-H4 (self-host fonts → zero third-party
        requests on cold load → preconnect/dns-prefetch hints
        would point at nothing). web/index.html stays free of
        preconnect lines.

Verification
============

  $ git status --short
    (only the 13 expected files modified)

  $ cd web && npx tsc --noEmit
    (exit 0, no type errors)

  $ cd web && npx vitest run
    Test Files  54 passed (54)
         Tests  583 passed (583)
    (all green; ran via `timeout 35 npx vitest run`)

  $ cd web && npx vite build
    ✓ built in 2.70s
    dist/assets/index-Da_kGcIu.css   75.54 kB (was 39.50 kB
      pre-Phase-0 — +36 kB from the inlined @fontsource @font-face
      declarations + the new @media print + @media reduced-motion
      blocks; offset by the elimination of all third-party font
      requests + the FOIT on cold load)
    dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2  48.25 kB
    dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2  21.16 kB
    (... + the rest of the weight variants and unicode-range subsets)

  $ grep -rohE "text-\[[0-9]+px\]" web/src --include='*.tsx'
    (zero matches — all 25 inline-pixel sites migrated)

  $ grep -rEc "brand-900" web/src --include='*.tsx'
    (zero callers)

  $ grep -nE "scope=\"col\"" web/src/components/DataTable.tsx
    86, 96   (both <th> sites carry scope="col")

  $ grep -nE "loading=|decoding=" web/src/components/Layout.tsx
    73       (logo <img> has both attrs + width/height)

  $ grep -nE "prefers-reduced-motion|@media print" web/src/index.css
    74, 92   (both blocks present)

  $ ls web/src/components/StatusBadge.tsx.bak
    (file not found — deleted)

Audit-accuracy notes
====================

* FE-L2 stale: the .bak file was NOT tracked in git (gitignored via
  .gitignore:46 *.bak). The audit's "tracked in git" claim was wrong.
  Closure path adjusted: `rm` instead of `git rm`.

* UX-L1 undercount: audit reported 23 inline-pixel sites; live count
  was 25 (16x 11px + 7x 10px + 2x 13px). All 25 migrated.

* UX-M9 not closed: audit prompt's "do NOT auto-trace" guard rail
  blocks closure in this code session. Tracking item for the
  designer/Phase-1 follow-up.

Residual risks
==============

* Logo PNG (773 kB) still ships as-is until the designer round-trip
  produces a hand-built SVG. Vite cache-busts the asset hash so
  cold loads cost the same one-shot 773 kB; warm loads hit the
  browser cache.

* Removing brand-900 may surface in a future dark-mode rebuild
  (Phase 7) that wants a deeper teal floor. Easy re-add — comment
  marker left in tailwind.config.cjs at the deletion site.

* The +1px nudges on text-[11px] -> text-xs and text-[13px] ->
  text-sm are theoretically visible but practically imperceptible.
  Any future visual-regression suite will catch genuine differences.
This commit is contained in:
shankar0123
2026-05-14 13:42:04 +00:00
parent c8985cf868
commit 93e00f6a5e
13 changed files with 168 additions and 38 deletions
+3 -3
View File
@@ -195,7 +195,7 @@ export default function DiscoveryPage() {
const badge = sourceTypeBadge(c.agent_id);
return (
<div>
<span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium ${badge.style} mr-1`}>{badge.label}</span>
<span className={`inline-block px-1.5 py-0.5 rounded text-2xs font-medium ${badge.style} mr-1`}>{badge.label}</span>
<div className="text-xs text-ink-faint truncate max-w-[180px] mt-0.5" title={c.source_path}>{c.source_path}</div>
</div>
);
@@ -218,7 +218,7 @@ export default function DiscoveryPage() {
<div className="flex items-center gap-1">
<span className="text-xs text-ink-muted">{c.key_algorithm}{c.key_size ? ` ${c.key_size}` : ''}</span>
{c.is_ca && (
<span className="text-[10px] px-1.5 py-0.5 rounded bg-purple-100 text-purple-700 font-medium">CA</span>
<span className="text-2xs px-1.5 py-0.5 rounded bg-purple-100 text-purple-700 font-medium">CA</span>
)}
</div>
),
@@ -226,7 +226,7 @@ export default function DiscoveryPage() {
{
key: 'fingerprint',
label: 'Fingerprint',
render: (c) => <span className="font-mono text-[10px] text-ink-faint">{c.fingerprint_sha256?.substring(0, 16)}...</span>,
render: (c) => <span className="font-mono text-2xs text-ink-faint">{c.fingerprint_sha256?.substring(0, 16)}...</span>,
},
{
key: 'actions',
+5 -5
View File
@@ -216,13 +216,13 @@ function ProfileSummaryCard({ profile, onRequestReload }: ProfileSummaryCardProp
</header>
<div className="flex flex-wrap gap-2 mb-3" data-testid={`est-profile-badges-${profile.path_id}`}>
<span className={`text-[11px] uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.mtls_enabled)}`}>
<span className={`text-xs uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.mtls_enabled)}`}>
mTLS {profile.mtls_enabled ? 'enabled' : 'disabled'}
</span>
<span className={`text-[11px] uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.basic_auth_configured)}`}>
<span className={`text-xs uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.basic_auth_configured)}`}>
HTTP Basic {profile.basic_auth_configured ? 'configured' : 'not set'}
</span>
<span className={`text-[11px] uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.server_keygen_enabled)}`}>
<span className={`text-xs uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.server_keygen_enabled)}`}>
Server-keygen {profile.server_keygen_enabled ? 'enabled' : 'disabled'}
</span>
</div>
@@ -233,7 +233,7 @@ function ProfileSummaryCard({ profile, onRequestReload }: ProfileSummaryCardProp
const value = profile.counters?.[label] ?? 0;
return (
<div key={label} className="bg-surface-alt rounded px-3 py-2" data-testid={`est-counter-${profile.path_id}-${label}`}>
<div className="text-[10px] uppercase tracking-wide text-ink-muted">{presentation.label}</div>
<div className="text-2xs uppercase tracking-wide text-ink-muted">{presentation.label}</div>
<div className={`text-base font-semibold ${TONE_CLASS[presentation.tone]}`}>{value}</div>
</div>
);
@@ -241,7 +241,7 @@ function ProfileSummaryCard({ profile, onRequestReload }: ProfileSummaryCardProp
</div>
{profile.mtls_enabled && profile.trust_anchor_path && (
<p className="text-[11px] text-ink-muted font-mono mb-2">
<p className="text-xs text-ink-muted font-mono mb-2">
Trust bundle: {profile.trust_anchor_path}
</p>
)}
+5 -5
View File
@@ -382,7 +382,7 @@ function SCEPProbeResultPanel({ result }: { result: SCEPProbeResult }) {
<span>{formatDateTime(result.probed_at)} · {result.probe_duration_ms}ms</span>
</div>
{result.error && (
<p className="font-mono text-[11px] mb-2">Error: {result.error}</p>
<p className="font-mono text-xs mb-2">Error: {result.error}</p>
)}
{result.reachable && (
<>
@@ -397,9 +397,9 @@ function SCEPProbeResultPanel({ result }: { result: SCEPProbeResult }) {
{result.ca_cert_subject && (
<dl className="grid grid-cols-2 gap-x-3 gap-y-1 mt-2">
<dt className="font-semibold">CA cert subject:</dt>
<dd className="font-mono text-[11px]">{result.ca_cert_subject}</dd>
<dd className="font-mono text-xs">{result.ca_cert_subject}</dd>
<dt className="font-semibold">Issuer:</dt>
<dd className="font-mono text-[11px]">{result.ca_cert_issuer}</dd>
<dd className="font-mono text-xs">{result.ca_cert_issuer}</dd>
<dt className="font-semibold">Algorithm:</dt>
<dd>{result.ca_cert_algorithm || '(unknown)'}</dd>
<dt className="font-semibold">Chain length:</dt>
@@ -417,7 +417,7 @@ function SCEPProbeResultPanel({ result }: { result: SCEPProbeResult }) {
</dl>
)}
{result.advertised_caps && result.advertised_caps.length > 0 && (
<p className="mt-2 text-[11px]">
<p className="mt-2 text-xs">
Raw caps: <code>{result.advertised_caps.join(', ')}</code>
</p>
)}
@@ -430,7 +430,7 @@ function SCEPProbeResultPanel({ result }: { result: SCEPProbeResult }) {
function CapBadge({ label, supported }: { label: string; supported: boolean }) {
return (
<span
className={`text-[11px] uppercase px-2 py-0.5 rounded border ${
className={`text-xs uppercase px-2 py-0.5 rounded border ${
supported ? 'bg-emerald-100 text-emerald-800 border-emerald-300' : 'bg-gray-100 text-gray-600 border-gray-300'
}`}
data-testid={`scep-probe-cap-${label.toLowerCase().replace(/\W/g, '-')}`}
+7 -7
View File
@@ -207,13 +207,13 @@ function ProfileSummaryCard({ profile, onViewIntuneDetails }: ProfileSummaryCard
</header>
<div className="flex flex-wrap gap-2 mb-3" data-testid={`profile-badges-${profile.path_id}`}>
<span className={`text-[11px] uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.challenge_password_set)}`}>
<span className={`text-xs uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.challenge_password_set)}`}>
Challenge password{profile.challenge_password_set ? ' set' : ' MISSING'}
</span>
<span className={`text-[11px] uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.mtls_enabled)}`}>
<span className={`text-xs uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(profile.mtls_enabled)}`}>
mTLS {profile.mtls_enabled ? 'enabled' : 'disabled'}
</span>
<span className={`text-[11px] uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(intuneEnabled)}`}>
<span className={`text-xs uppercase tracking-wide px-2 py-0.5 rounded border ${pillClass(intuneEnabled)}`}>
Intune {intuneEnabled ? 'enabled' : 'disabled'}
</span>
</div>
@@ -221,7 +221,7 @@ function ProfileSummaryCard({ profile, onViewIntuneDetails }: ProfileSummaryCard
<dl className="grid grid-cols-1 sm:grid-cols-3 gap-3 text-xs text-ink-muted">
<div>
<dt className="font-semibold text-ink">RA cert subject</dt>
<dd className="font-mono text-[11px]">{profile.ra_cert_subject || '(not loaded)'}</dd>
<dd className="font-mono text-xs">{profile.ra_cert_subject || '(not loaded)'}</dd>
</div>
{profile.ra_cert_not_after && (
<div>
@@ -232,7 +232,7 @@ function ProfileSummaryCard({ profile, onViewIntuneDetails }: ProfileSummaryCard
{profile.mtls_enabled && profile.mtls_trust_bundle_path && (
<div>
<dt className="font-semibold text-ink">mTLS trust bundle</dt>
<dd className="font-mono text-[11px]">{profile.mtls_trust_bundle_path}</dd>
<dd className="font-mono text-xs">{profile.mtls_trust_bundle_path}</dd>
</div>
)}
</dl>
@@ -416,7 +416,7 @@ function IntuneProfileCard({ profile, onRequestReload, highlighted }: IntuneProf
<div className={`text-lg font-semibold ${TONE_CLASS[presentation.tone]}`} data-testid={`counter-${profile.path_id}-${label}`}>
{value}
</div>
<div className="text-[11px] text-ink-muted uppercase tracking-wide">{presentation.label}</div>
<div className="text-xs text-ink-muted uppercase tracking-wide">{presentation.label}</div>
</div>
);
})}
@@ -442,7 +442,7 @@ function IntuneProfileCard({ profile, onRequestReload, highlighted }: IntuneProf
<summary className="cursor-pointer font-semibold text-ink">Trust anchor details</summary>
<table className="mt-2 w-full text-left">
<thead>
<tr className="text-[11px] text-ink-muted uppercase">
<tr className="text-xs text-ink-muted uppercase">
<th className="py-1 pr-2">Subject</th>
<th className="py-1 pr-2">Not after</th>
<th className="py-1">Days to expiry</th>
+1 -1
View File
@@ -166,7 +166,7 @@ export default function SessionsPage() {
<span className="ml-1 text-ink-muted">({s.actor_type})</span>
{isOwn && (
<span
className="ml-2 inline-block px-1.5 py-0.5 text-[10px] rounded bg-brand-50 text-brand-700"
className="ml-2 inline-block px-1.5 py-0.5 text-2xs rounded bg-brand-50 text-brand-700"
data-testid={`session-self-pill-${s.id}`}
>
you