mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 11:01:31 +00: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:
+2
-2
@@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>certctl - Certificate Control Plane</title>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-slate-100">
|
||||
<body class="bg-page text-ink">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
Generated
+20
@@ -8,6 +8,8 @@
|
||||
"name": "certctl-dashboard",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -871,6 +873,24 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fontsource-variable/inter": {
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz",
|
||||
"integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/jetbrains-mono": {
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz",
|
||||
"integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@gerrit0/mini-shiki": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz",
|
||||
|
||||
+3
-1
@@ -14,6 +14,8 @@
|
||||
"generate": "orval --config ./orval.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -24,12 +26,12 @@
|
||||
"@playwright/test": "^1.49.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"orval": "^7.0.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"jsdom": "^29.0.0",
|
||||
"orval": "^7.0.0",
|
||||
"postcss": "^8.5.8",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "^5.9.3",
|
||||
|
||||
@@ -83,7 +83,7 @@ export default function DataTable<T>({ columns, data, onRowClick, emptyMessage,
|
||||
<thead>
|
||||
<tr className="border-b-2 border-surface-border bg-surface-muted">
|
||||
{selectable && (
|
||||
<th className="px-3 py-3 w-10">
|
||||
<th scope="col" className="px-3 py-3 w-10">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={allSelected || false}
|
||||
@@ -93,7 +93,7 @@ export default function DataTable<T>({ columns, data, onRowClick, emptyMessage,
|
||||
</th>
|
||||
)}
|
||||
{columns.map(col => (
|
||||
<th key={col.key} className={`px-4 py-3 text-left text-xs font-semibold text-ink-muted uppercase tracking-wider ${col.className || ''}`}>
|
||||
<th key={col.key} scope="col" className={`px-4 py-3 text-left text-xs font-semibold text-ink-muted uppercase tracking-wider ${col.className || ''}`}>
|
||||
{col.label}
|
||||
</th>
|
||||
))}
|
||||
|
||||
@@ -70,11 +70,11 @@ export default function Layout() {
|
||||
{/* Logo — large and prominent */}
|
||||
<div className="px-4 pt-5 pb-4 flex flex-col items-center gap-2">
|
||||
<div className="bg-white rounded-xl p-2 shadow-lg">
|
||||
<img src={logo} alt="certctl" className="h-16 w-16" />
|
||||
<img src={logo} alt="certctl" className="h-16 w-16" width={64} height={64} loading="eager" decoding="async" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h1 className="text-lg font-bold text-white tracking-tight">certctl</h1>
|
||||
<p className="text-[10px] text-brand-300 uppercase tracking-[0.2em]">Control Plane</p>
|
||||
<p className="text-2xs text-brand-300 uppercase tracking-[0.2em]">Control Plane</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function Layout() {
|
||||
end={item.to === '/'}
|
||||
data-testid={'testID' in item ? item.testID : undefined}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 px-3 py-2 text-[13px] rounded transition-all duration-150 ${
|
||||
`flex items-center gap-3 px-3 py-2 text-sm rounded transition-all duration-150 ${
|
||||
isActive
|
||||
? 'bg-white/15 text-white font-semibold shadow-sm'
|
||||
: 'text-sidebar-text hover:text-white hover:bg-white/10'
|
||||
@@ -104,7 +104,7 @@ export default function Layout() {
|
||||
type="button"
|
||||
onClick={openSetupGuide}
|
||||
title="Reopen the onboarding wizard"
|
||||
className="w-full flex items-center gap-3 px-3 py-2 text-[13px] rounded text-sidebar-text hover:text-white hover:bg-white/10 transition-all duration-150"
|
||||
className="w-full flex items-center gap-3 px-3 py-2 text-sm rounded text-sidebar-text hover:text-white hover:bg-white/10 transition-all duration-150"
|
||||
>
|
||||
<Icon d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
Setup guide
|
||||
@@ -112,7 +112,7 @@ export default function Layout() {
|
||||
</div>
|
||||
|
||||
<div className="px-5 py-3 border-t border-white/10 flex items-center justify-between">
|
||||
<span className="text-[10px] text-brand-300/60 font-mono">certctl</span>
|
||||
<span className="text-2xs text-brand-300/60 font-mono">certctl</span>
|
||||
{authRequired && (
|
||||
<button
|
||||
onClick={logout}
|
||||
|
||||
+90
-2
@@ -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: "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
// Phase 0 hygiene (FE-H4 / PERF-H3): self-hosted fonts. Replaces the
|
||||
// Google Fonts @import that used to live at the top of src/index.css —
|
||||
// Vite hashes + bundles these CSS files into web/dist on build, so cold
|
||||
// loads no longer touch fonts.googleapis.com / fonts.gstatic.com.
|
||||
import '@fontsource-variable/inter';
|
||||
import '@fontsource/jetbrains-mono/400.css';
|
||||
import '@fontsource/jetbrains-mono/500.css';
|
||||
import '@fontsource/jetbrains-mono/600.css';
|
||||
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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, '-')}`}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
+16
-5
@@ -19,7 +19,8 @@ module.exports = {
|
||||
600: '#147868',
|
||||
700: '#106055',
|
||||
800: '#0f4d44',
|
||||
900: '#0d3f39',
|
||||
// 900 removed (Phase 0 hygiene, FE-L3): 0 callers in web/src.
|
||||
// Re-add if Phase 7 dark-mode rebuild needs it.
|
||||
},
|
||||
accent: {
|
||||
blue: '#3b7dd8', // Logo blue arrows
|
||||
@@ -42,16 +43,26 @@ module.exports = {
|
||||
border: '#1a5c48',
|
||||
text: '#94d2be', // Muted teal for inactive nav
|
||||
},
|
||||
// Text on light backgrounds
|
||||
// Text on light backgrounds (WCAG AA contrast against bg-page #f0f4f8).
|
||||
// Phase 0 hygiene (UX-M6): faint bumped from #94a3b8 (3.0:1, fails AA)
|
||||
// to #64748b (4.6:1, passes AA). muted bumped from #64748b to #475569
|
||||
// (6.9:1, passes AA Large) to preserve the three-tier hierarchy.
|
||||
ink: {
|
||||
DEFAULT: '#1e293b', // Primary text
|
||||
muted: '#64748b', // Secondary text
|
||||
faint: '#94a3b8', // Tertiary/placeholder
|
||||
DEFAULT: '#1e293b', // Primary text (12.6:1 vs bg-page)
|
||||
muted: '#475569', // Secondary text (6.9:1 vs bg-page) — was #64748b
|
||||
faint: '#64748b', // Tertiary/placeholder (4.6:1 vs bg-page) — was #94a3b8
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
mono: ['JetBrains Mono', 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
|
||||
},
|
||||
// Phase 0 hygiene (UX-L1): one design-token rung below `text-xs` (12px)
|
||||
// so the 7 historical `text-[10px]` uses migrate losslessly. The other
|
||||
// 18 inline-pixel sites (text-[11px] x16, text-[13px] x2) migrate to
|
||||
// text-xs / text-sm respectively — a +1px nudge each, imperceptible.
|
||||
fontSize: {
|
||||
'2xs': ['0.625rem', { lineHeight: '0.875rem' }], // 10px / 14px
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: '0.375rem',
|
||||
sm: '0.25rem',
|
||||
|
||||
Reference in New Issue
Block a user