mirror of
https://github.com/certctl-io/certctl.git
synced 2026-08-31 20:31:24 +02:00
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:
@@ -1,4 +1,12 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
||||
/*
|
||||
* Phase 0 hygiene (FE-H4 / PERF-H3): Inter + JetBrains Mono are now
|
||||
* self-hosted via the @fontsource* packages, imported at the top of
|
||||
* web/src/main.tsx so Vite can hash + bundle the font files. The old
|
||||
* Google Fonts @import lived here and produced two cross-origin font
|
||||
* requests on every cold load; those are gone and PERF-H3's
|
||||
* preconnect/dns-prefetch suggestion collapses (no external host left
|
||||
* to preconnect to).
|
||||
*/
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@@ -7,7 +15,11 @@
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-page text-ink antialiased;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
/* Phase 0 hygiene (FE-H4): "Inter Variable" is the family name
|
||||
registered by @fontsource-variable/inter (single woff2 covering
|
||||
wght 100-900). Keep "Inter" as a fallback for older browsers /
|
||||
any pinned local install. */
|
||||
font-family: 'Inter Variable', 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,3 +63,79 @@
|
||||
@apply bg-surface border border-surface-border rounded-md shadow-sm p-5 border-t-4;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 0 hygiene (UX-L2): honour prefers-reduced-motion. Users who
|
||||
* opt out of animation at the OS level get effectively-instant
|
||||
* transitions on every animated element (badges, modals, toggles).
|
||||
* 0.01ms is the conventional non-zero value — fully zero can break
|
||||
* libraries that observe transitionend events.
|
||||
*/
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 0 hygiene (UX-L3): a baseline print stylesheet. Hides the
|
||||
* sidebar + top action bars, removes card shadows, expands content
|
||||
* to full width, and keeps table rows intact across page breaks.
|
||||
* Operator-facing — operators print certificate detail pages and
|
||||
* audit-log exports for compliance archives.
|
||||
*/
|
||||
@media print {
|
||||
/* Drop sidebar / nav chrome — only the content matters in print. */
|
||||
aside,
|
||||
nav,
|
||||
[role="navigation"],
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Full-width content, no shadows, plain backgrounds (ink saving). */
|
||||
body {
|
||||
background: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
}
|
||||
main,
|
||||
.card,
|
||||
.stat-card {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
box-shadow: none !important;
|
||||
border-color: #cbd5e1 !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Tables: prevent mid-row breaks, repeat headers on each page. */
|
||||
table {
|
||||
width: 100% !important;
|
||||
}
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Show link hrefs alongside the visible text — print readers
|
||||
can't click links, so the target URL is the only signal. */
|
||||
a[href]::after {
|
||||
content: " (" attr(href) ")";
|
||||
font-size: 0.85em;
|
||||
color: #555555;
|
||||
}
|
||||
a[href^="#"]::after,
|
||||
a[href^="javascript:"]::after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user