GHSA-vfj7-8cjw-p6xm affects every braces release and has no fix, so the required frontend audit failed with seven high findings on any pull request that changes the dependency graph. Tailwind CSS 3 (chokidar, fast-glob, micromatch) and jscpd 4 (@jscpd/finder, fast-glob) were the only paths in, both dev-only, and no override can remove braces from fast-glob or micromatch. Tailwind CSS 4.3.3 through @tailwindcss/vite and jscpd 5.4.0 leave none; npm audit over the full graph reports zero. The stylesheet keeps the v3 rendering. It pins the v3 palette (inlined, so opacity modifiers compile to static colors), font stacks and line heights, keeps v3's cascade order by placing former component and unlayered rules around the generated utilities, and restores v3 hover on touch devices, gray-200 default borders, the 3px blue ring, placeholder color, button cursor, table-cell padding and the space-* and divide-* child selectors. The 900/950 shades Pulse overrode with 25% alpha are opaque now and their callers say /25, because v4 multiplies an opacity modifier into a translucent color instead of replacing it. Opacity modifiers on var() colors that v3 never generated are removed. Utility order changed from lexical to numeric, so a few caller overrides that silently lost in v3 would start to win. Touch targets keep their authored 44px phone size (the Machines row stays 32px); cosmetic overrides v3 never applied are removed, and overrides it did apply use the important suffix. Contract-Neutral: Tailwind CSS 4 migration for GHSA-vfj7-8cjw-p6xm: utility class renames and stylesheet compatibility, no subsystem contract change
7.6 KiB
Pulse Modern Design System 🎨
This document outlines the standard UI primitives, tokens, and components that conform to Pulse's single-source-of-truth semantic design spec.
Goal: All new pages, panels, toggles, buttons, and text should map exactly to these shared specifications instead of relying on manually typing raw Tailwind color configs (like bg-gray-800 or #1a1c23).
Core Principles
- Never hardcode hex values or use static
gray/white/slatelabels for structural layout colors. - Use the Semantic Tokens. These are declared as Tailwind
@themecolors inindex.cssand resolve through its CSS variables to flawlessly support light/dark transitions without needing a literaldark:prefix in the layout classes.
Enforcement
npm run lintis a hard gate for design-system regressions insrc/componentsandsrc/pages.npm run lint:themeis a hard gate for theme governance across the whole frontend (src/**+index.html).npm run lint:headersaudits header composition and page-level header usage policy.- Routed surfaces must use shared header primitives (
PageHeader,SectionHeader,SettingsPanel,OperationsPanel) instead of raw<h1>markup. - Visible form labels must either use
forwith a matching control ID or wrap a native labelable control; use a heading, paragraph, or span for non-control captions. - Shared form controls must keep a stable accessible name as their value changes; a placeholder is input guidance, not a label.
- Compact pointer targets must remain at least 24 by 24 CSS pixels, including icon and copy actions, per WCAG 2.2 Target Size (Minimum).
- Product motion must honor the operating-system
prefers-reduced-motionsetting. The global stylesheet completes animations and transitions immediately under that preference; do not override that safeguard or make motion the only indication of state. - Only explicit non-visual route wrappers are exempt from header primitive requirements.
- Theme ownership policy:
- Only
src/utils/theme.tsandindex.htmlmay read/write theme keys (pulseThemePreference,darkMode,pulse_dark_mode) or toggle the rootdarkclass. - Full-screen shells (
min-h-screen) must use semantic backgrounds (bg-base,bg-surface,bg-surface-alt) instead of hardcoded palettes.
- Only
- CI must fail on:
- orphaned utility prefixes (for example
dark:hover:orgroup-hover:with no class after them) dark:bg-white/dark:hover:bg-white- low-contrast class combinations like
bg-base+text-white - theme owner violations or non-semantic full-screen backgrounds reported by
lint:theme
- orphaned utility prefixes (for example
- If a UI requires exceptional styling, prefer adding a semantic token or shared component rather than bypassing the rule.
🏗 Tokens
Use standard Tailwind prefixes with these dynamic keys:
| Token | Utility Example | Represents | When to Use |
|---|---|---|---|
base |
bg-base |
Application background | The absolute bottom layer of the page, or inactive tracker backgrounds. |
surface |
bg-surface |
Elevated structural elements | Cards, panels, dialog boxes, standard button backgrounds, table headers. |
surface-hover |
hover:bg-surface-hover |
Interactive state | When a table row, button, or list item is hovered or active. |
surface-alt |
bg-surface-alt |
Slightly elevated surface | Selected states, inline badges, secondary panels, form group backgrounds. |
border-subtle |
border-border-subtle |
Faint divider | Very subtle dividers that don't need sharp contrast. |
border |
border-border |
Standard structure borders | Outline for panels, buttons, dialog boxes, and inputs. |
base-content |
text-base-content |
Primary readable text | The main text color instead of absolute black/white. |
muted |
text-muted |
De-emphasized text | Secondary tags, descriptions, disabled labels, or table column headers. |
Example of what NOT to do:
// ❌ BAD
<div class="bg-white dark:bg-[#1a1c23] border-gray-200 dark:border-gray-800 text-gray-900 border" />
Example of the spec:
// ✅ GOOD
<div class="bg-surface border-border text-base-content border" />
🧩 CSS Component Classes
Reusable component classes defined in index.css using @layer base. Use these instead of repeating the same token combinations:
| Class | What it provides | Use for |
|---|---|---|
.card |
bg-surface border border-border shadow-sm rounded-md |
Content panels, dialog boxes |
.form-input |
Full input styling (border, bg, text, focus ring, disabled state) | Text inputs, textareas |
.form-select |
Same as .form-input + dropdown arrow padding |
Select dropdowns |
.table-row |
border-b border-border hover:bg-surface-hover |
Table body rows |
// ❌ BAD — repeating tokens manually
<input class="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm text-base-content focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500" />
// ✅ GOOD — use the component class
<input class="form-input" />
🧩 Standard Components
Before building a native toggle or stylized button from scratch, heavily favor the standardized layout elements contained in /src/components/shared/.
1. Card (/src/components/shared/Card.tsx)
The absolute core container for content across Pulse. Automatically maps to bg-surface and standard borders/shadows/padding.
import { Card } from '@/components/shared/Card';
<Card hoverable border padding="md">
Main metric panel content!
</Card>
2. Button (/src/components/shared/Button.tsx)
A unified standard button built on semantic tokens instead of inline styling.
Variants: primary | secondary (default) | danger | ghost | outline
import { Button } from '@/components/shared/Button';
<Button variant="primary" size="md"> Save Settings </Button>
<Button variant="secondary" isLoading> Saving... </Button>
3. Toggle (/src/components/shared/Toggle.tsx)
Dynamic switch mapped to base, surface, and blue.
import { Toggle } from '@/components/shared/Toggle';
<Toggle checked={true} label="Enable Discovery" description="Scans network elements"/>
4. FilterButtonGroup (/src/components/shared/FilterButtonGroup.tsx)
Segmented radio-style navigation controls. Uses muted for inactive buttons, and maps the active card to a highlighted surface.
import { FilterButtonGroup } from '@/components/shared/FilterButtonGroup';
<FilterButtonGroup
value={currentView()}
onChange={setView}
options={[
{ value: 'pve', label: 'Virtual Environment' },
{ value: 'pbs', label: 'Backup Server' }
]}
/>
5. Table primitives (/src/components/shared/Table.tsx)
Wraps the raw HTML table structure with strict structural tags dynamically handling their hover states, borders, font weights, and tracking properties.
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/shared/Table';
<Table>
<TableHeader>
<TableRow>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Online</TableCell>
</TableRow>
</TableBody>
</Table>
6. Form utilities (/src/components/shared/Form.ts)
Pre-composed class strings for form elements. Import individual exports instead of composing tokens manually.
import { formControl, formSelect, formLabel, formField, formCheckbox } from '@/components/shared/Form';
<div class={formField}>
<label class={formLabel}>API Key</label>
<input class={formControl} type="text" />
</div>