feat(ui): E31 S4a — rail rows are links, the channel page opens on unwatched, users and audit tidied

- Nav rail page rows are real links (/?page=<id>): a middle-click or
  Ctrl-click opens the page in a new tab; a plain click stays in-app.
- A fresh channel page shows the unwatched videos; a picked watch state
  is a remembered preference (channelShow).
- Users: the role, suspend and delete move into the row's one menu
  (the "…", right-click, Shift+F10); locked ones keep their reason.
- Audit log: back-to-back successful runs of one job fold into one row
  that opens to the earlier ones; a failed run reads in the danger colour.
- DESIGN.md decision log: the E31 picks.
This commit is contained in:
2026-10-01 22:51:38 +02:00
parent ab146211fa
commit 87d71e3762
23 changed files with 542 additions and 98 deletions
+14 -3
View File
@@ -4,6 +4,7 @@ import type { LucideIcon } from "lucide-react";
import { Pressable } from "./Pressable";
import { SURFACE } from "./surfaces";
import Tooltip from "./Tooltip";
import { isPlainLeftClick } from "../../lib/nav";
// B7 (docs/DESIGN.md): the app's primary navigation rail. Slim — 3.75rem wide whether or not the
// labels are on (they add height, never width) — with "you are here" as a soft accent tint plus a 3px
@@ -76,6 +77,7 @@ export function NavRailItem({
showLabel,
active = false,
badge,
href,
onClick,
testId,
}: {
@@ -88,15 +90,24 @@ export function NavRailItem({
active?: boolean;
/** An unread / in-progress count on the icon's corner (0 hides it). */
badge?: number;
/** The page's own address. A page row is a real link (the owner, E31): a middle-click, a
* Ctrl/Cmd-click or the browser's "Open in new tab" opens the page in a tab of its own. */
href: string;
/** The in-app navigation, run only for a plain left click. */
onClick: () => void;
testId?: string;
}) {
const shown = !!badge && badge > 0;
return (
<Tooltip hint={label} side="right" always openDelay={350}>
<Pressable
<a
data-testid={testId}
onClick={onClick}
href={href}
onClick={(e) => {
if (!isPlainLeftClick(e)) return; // the browser opens its tab or window
e.preventDefault();
onClick();
}}
// With the label visible the row's name starts with that text (WCAG 2.5.3: the spoken name
// contains the visible one); the full name stays in the tooltip. Icon-only, the full name is the
// name. The exact count rides in the name either way, so "Messages (3)" is heard and not only
@@ -131,7 +142,7 @@ export function NavRailItem({
{shortLabel}
</span>
)}
</Pressable>
</a>
</Tooltip>
);
}