header cluster and breadcrumb colour

Breadcrumb links rested at duck-forest and hovered to duck-teal; they now sit at the
hover colour, so the hover and its transition go with it. One shared component, so the
full browser and the dashboard widget both pick it up. The current folder stays
duck-dark semibold, keeping clickable and current distinct.

Header: the theme control leads the right cluster and names the theme it is wearing
rather than showing a palette glyph — with 227 of them, which one is on is worth reading.
It takes JobsIndicator's pill styling so the cluster stays one shape, and keeps its
colour on --header-fg rather than hardcoding white.

The jobs indicators and the rescan button are Super Admin only now. Cosmetic, and the
comment says so: /jobs stays reachable by URL and rescan is gated server-side, so
nothing here is load-bearing. Uses the same user?.role check UserMenu already makes in
this folder rather than adding a second convention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 03:24:08 +00:00
co-authored by Claude Opus 5
parent cbcd8d65df
commit f9dfd05676
3 changed files with 19 additions and 14 deletions
@@ -11,6 +11,7 @@ import { JobsIndicator } from './JobsIndicator';
import { ThemeSwitcher } from './ThemeSwitcher';
import { RescanButton } from '../Rescan/RescanButton';
import { usePageTitle } from '@/state/usePageTitle';
import { useAuth } from 'hooks/useAuth';
import { useThemeAsset } from '@/components/ui/ThemeProvider';
// import { BugReportButton } from '../BugReport/BugReportButton';
@@ -46,6 +47,7 @@ type HeaderProps = {
export function Header({ dockItems, hidden }: HeaderProps) {
const [open, setOpen] = useState(false);
const isTouch = useIsTouch();
const { user } = useAuth();
// The mark comes from the theme — a white-labelled build replaces it without touching this file.
const logo = useThemeAsset('--logo-src');
@@ -100,9 +102,15 @@ export function Header({ dockItems, hidden }: HeaderProps) {
*/}
{/* Bug Report — hidden */}
{/* <BugReportButton /> */}
<JobsIndicator />
<RescanButton />
<ThemeSwitcher />
{/* Owner only, and tidiness rather than access control — /jobs stays reachable and the rescan
endpoint is gated server-side, because a hidden button is not a permission. */}
{user?.role === 'Super Admin' && (
<>
<JobsIndicator />
<RescanButton />
</>
)}
<UserMenu />
</div>
</div>
@@ -1,5 +1,5 @@
import { createPortal } from 'react-dom';
import { Palette, Check } from 'lucide-react';
import { Check } from 'lucide-react';
import * as Dropdown from '@/components/ui/dropdown-menu';
import type { Theme } from '@/components/ui/ThemeProvider';
import { useTheme, useThemeKeys } from '@/components/ui/ThemeProvider';
@@ -45,11 +45,14 @@ export const ThemeSwitcher = () => {
<DropdownMenuTrigger asChild>
<button
title={`Theme: ${theme.name} · ctrl+shift+←/→ to cycle · ctrl+shift+space for the list`}
aria-label="Theme"
className="flex h-8 w-8 items-center justify-center rounded-full transition-transform hover:scale-110 cursor-pointer"
aria-label={`Theme: ${theme.name}`}
// Named rather than a palette glyph: with 227 themes the one you are wearing is worth
// reading at a glance, and an icon cannot say which that is. Same pill as JobsIndicator so
// the cluster stays one shape.
className="flex h-8 cursor-pointer items-center rounded-full bg-black/25 px-2.5 text-xs font-semibold transition-colors hover:bg-black/35"
style={{ color: 'var(--header-fg)' }}
>
<Palette className="h-4 w-4" />
{theme.name}
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="z-50 max-h-[70vh] w-56 overflow-y-auto">
@@ -33,10 +33,7 @@ export const Breadcrumb = ({ path, onNavigate, basePath = '/', hrefForPath }: Br
return (
<div className="shrink-0 border-b border-duck-dark/10 px-4 py-2">
<nav className="flex items-center gap-1 text-sm flex-wrap">
<Crumb
to={basePath}
className="flex items-center gap-1 text-duck-forest hover:text-duck-teal transition-colors cursor-pointer font-medium"
>
<Crumb to={basePath} className="flex items-center gap-1 text-duck-teal cursor-pointer font-medium">
<Home className="h-4 w-4" />
<span>{basePath === '/' ? 'home' : basePath.split('/').pop()}</span>
</Crumb>
@@ -52,10 +49,7 @@ export const Breadcrumb = ({ path, onNavigate, basePath = '/', hrefForPath }: Br
{isLast ? (
<span className="text-duck-dark font-semibold">{segment}</span>
) : (
<Crumb
to={segmentPath}
className="text-duck-forest hover:text-duck-teal transition-colors cursor-pointer font-medium"
>
<Crumb to={segmentPath} className="text-duck-teal cursor-pointer font-medium">
{segment}
</Crumb>
)}