feat(design): batch C recorded, the player row open as C5 (E28 S2d)

C1-C4 as proposed: the action classification, overlay-drawer side panels,
touch-first players, dialog focus by purpose with a pinned footer. The
players were then played live at tablet size, which found the Plex
control row running off-screen below 1024; how it fits is open as C5.
CLAUDE.md now points at DESIGN.md.
This commit is contained in:
2026-09-24 03:27:26 +02:00
parent 7055548260
commit f4bc60d659
5 changed files with 318 additions and 234 deletions
+26 -39
View File
@@ -1,10 +1,11 @@
import clsx from "clsx";
import { Bookmark, Check, ListPlus, MoreHorizontal, Play } from "lucide-react";
import { Decision, Sample, Variant } from "./labKit";
import { Reference, Sample } from "./labKit";
import { H, LAYER } from "./labTokens";
// E28 S2 · Batch C, part 1 — action placement: the classification table (object × action → where it
// lives). The owner delegated this to the UX lead, so it is reviewed rather than picked. It applies
// lives), DECIDED (owner, 2026-09-24: accepted as proposed). It is the source S3 builds each object's
// ONE action list from. It applies
// the decided guard rails (DESIGN.md "Action placement"): ONE action list per object feeds every
// trigger; the primary action and visible state stay on the object; rare, secondary and destructive
// actions go to the menu; nothing is gesture-only, because the "…" button opens the same menu.
@@ -370,47 +371,33 @@ function Card({ touch }: { touch: boolean }) {
export function C1() {
return (
<Decision
<Reference
id="c1"
code="C1"
title="Action placement: the classification"
question="Where each object's actions live. You delegated this one, so it is for review, not a pick: say where you disagree. The menu (right-click, long-press, Shift+F10 or the '…' button) always lists ALL of an object's actions from one list; the table says what is ALSO visible."
now="No object gets its buttons and its menu from one list: the feed card's menu re-declares watched / save / hide / reset and leaves out add-to-playlist and download (hover only). Move up / down and pin live only in menus. Downloads, Notifications and the Scheduler have no menu at all; Channels and Plex menus offer only Pin."
rule="The menu (right-click, long-press, Shift+F10 or the '…' button) lists ALL of an object's actions from one list; the table says what is ALSO visible. Mouse: the primary action and the state at rest, the everyday toggles on hover. Touch: the state and a 44px '…'. Destructive row buttons move into the menu."
>
<Variant
letter="A"
name="The card, mouse (hovered) and touch"
recommended
why="Mouse: play, the watched / progress state and the channel at rest; the three everyday toggles plus '…' on hover. Touch: nothing to hover, so the card keeps its state and a 44px '…' next to the title; everything else is in the menu. The title is plain, selectable text."
>
<div className="flex flex-wrap gap-4">
<Sample label="Mouse, hovered">
<Card touch={false} />
</Sample>
<Sample label="Touch">
<Card touch />
</Sample>
</div>
</Variant>
<Variant
letter="B"
name="The background menu (desktop only)"
why="A right-click on empty content area opens the page's menu. It only mirrors toolbar actions that already exist; Shift+right-click still gives the browser's own menu. Not on touch: a long-press on the background would fight scrolling and text selection."
>
<table className="text-xs">
<tbody>
{BACKGROUND.map(([page, items]) => (
<tr key={page} className="align-top [&>td]:py-1 [&>td]:pr-3">
<td className="whitespace-nowrap font-medium">{page}</td>
<td className="text-muted">{items}</td>
</tr>
))}
</tbody>
</table>
</Variant>
<div className="col-span-full">
<Table />
<div className="flex flex-wrap gap-4">
<Sample label="Card · mouse, hovered">
<Card touch={false} />
</Sample>
<Sample label="Card · touch">
<Card touch />
</Sample>
<Sample label="The background menu (desktop only; it mirrors the toolbar, Shift+right-click = the browser's)">
<table className="text-xs">
<tbody>
{BACKGROUND.map(([page, items]) => (
<tr key={page} className="align-top [&>td]:py-1 [&>td]:pr-3">
<td className="whitespace-nowrap font-medium">{page}</td>
<td className="text-muted">{items}</td>
</tr>
))}
</tbody>
</table>
</Sample>
</div>
</Decision>
<Table />
</Reference>
);
}