feat(FN-1018): isolate header brand spacing with .header-brand container

- Wrap logo + title in a .header-brand div for independent flex layout
- Replace hard-coded gap/margin with gap utilities for consistent spacing
- Add .header-brand CSS class with align-items: center and gap
- Add unit test verifying brand container structure
- Document .header-brand lockup pattern in README
This commit is contained in:
gsxdsm
2026-04-05 22:29:03 -07:00
parent 6ff6e69e60
commit 31c8ea68b0
4 changed files with 39 additions and 15 deletions

View File

@@ -361,6 +361,8 @@ The Fusion logo and all task-creation action buttons (including the "+ New Task"
All color themes automatically provide values for these tokens. Adding a new color theme requires only setting `--todo` (and optionally `--logo-accent` if the logo should differ from the todo column color).
**Header brand lockup:** The logo SVG and "Fusion" wordmark are wrapped in a dedicated `.header-brand` flex container with `gap: var(--space-xs)` (4 px). This isolates logo-to-wordmark spacing from the wider `.header-left` gap (`var(--space-sm)`, 8 px) so the brand mark can be tuned independently of the spacing between the brand group and adjacent header controls (project selector, back button, etc.).
## Performance Characteristics
The dashboard includes several runtime safeguards to stay responsive during long sessions and on larger boards:

View File

@@ -248,21 +248,23 @@ export function Header({
return (
<header className="header">
<div className="header-left">
<svg
className="header-logo"
width={24}
height={24}
viewBox="0 0 128 128"
fill="none"
aria-label="Fusion logo"
role="img"
>
<circle cx="44" cy="44" r="20" fill="currentColor" />
<circle cx="84" cy="44" r="20" fill="currentColor" />
<circle cx="44" cy="84" r="20" fill="currentColor" />
<circle cx="84" cy="84" r="20" fill="currentColor" />
</svg>
<h1 className="logo">Fusion</h1>
<div className="header-brand">
<svg
className="header-logo"
width={24}
height={24}
viewBox="0 0 128 128"
fill="none"
aria-label="Fusion logo"
role="img"
>
<circle cx="44" cy="44" r="20" fill="currentColor" />
<circle cx="84" cy="44" r="20" fill="currentColor" />
<circle cx="44" cy="84" r="20" fill="currentColor" />
<circle cx="84" cy="84" r="20" fill="currentColor" />
</svg>
<h1 className="logo">Fusion</h1>
</div>
{/* Project Selector - shown when 2+ projects on desktop only */}
{!isCompact && projects.length > 1 && (

View File

@@ -62,6 +62,21 @@ describe("Header", () => {
expect(logo.compareDocumentPosition(h1) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});
it("renders logo and wordmark inside a .header-brand container", () => {
const { container } = render(<Header />);
const brand = container.querySelector(".header-brand");
expect(brand).not.toBeNull();
// Brand container should contain the logo SVG
const logo = brand!.querySelector("[aria-label='Fusion logo']");
expect(logo).not.toBeNull();
// Brand container should contain the heading
const h1 = brand!.querySelector("h1.logo");
expect(h1).not.toBeNull();
expect(h1!.textContent).toBe("Fusion");
// Logo should appear before the heading within the brand container
expect(logo!.compareDocumentPosition(h1!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});
it("renders the settings button", () => {
const onOpen = vi.fn();
render(<Header onOpenSettings={onOpen} />);

View File

@@ -163,6 +163,11 @@ body {
align-items: center;
gap: var(--space-sm);
}
.header-brand {
display: flex;
align-items: center;
gap: var(--space-xs);
}
.header-logo {
width: 24px;
height: 24px;