import clsx from "clsx"; import { Bookmark, Check, ListPlus, MoreHorizontal, Play } from "lucide-react"; import { Reference, Sample } from "./labKit"; import { LAYER } from "./labTokens"; import { iconButtonClass } from "../components/ui/buttonStyles"; import { SURFACE } from "../components/ui/surfaces"; // Batch C, part 1 — action placement: the classification table (object × action → where it // lives), DECIDED (owner, 2026-09-24: accepted as proposed). It is the source each object's // ONE action list is built 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. type Where = "rest" | "hover" | "menu"; type Row = { action: string; /** mouse: visible at rest / on hover / only in the menu (the menu always lists everything) */ mouse: Where; /** touch card / row: visible, or reached through "…" */ touch: "visible" | "…"; /** what changes against today's actions, empty when nothing does */ change?: string; danger?: boolean; }; type Group = { object: string; rows: Row[] }; const TABLE: Group[] = [ { object: "Video card (feed grid and list rows, Home shelves)", rows: [ { action: "Play / continue", mouse: "rest", touch: "visible", change: "the thumbnail plays; the title is a link-styled element that ignores a click ending a text selection", }, { action: "Open channel", mouse: "rest", touch: "visible" }, { action: "Watched (toggle)", mouse: "hover", touch: "…", change: "the watched / progress STATE stays visible at rest", }, { action: "Save (watch later)", mouse: "hover", touch: "…" }, { action: "Add to playlist", mouse: "hover", touch: "…", change: "today hover only, not in the menu", }, { action: "Download", mouse: "menu", touch: "…", change: "today hover only, not in the menu", }, { action: "Details (ⓘ)", mouse: "menu", touch: "…", change: "today hover only, gone on touch", }, { action: "Hide", mouse: "menu", touch: "…", change: "leaves the hover bar (undo via the toast)", }, { action: "Reset state", mouse: "menu", touch: "…", change: "leaves the hover bar" }, { action: "Pin to shelf", mouse: "menu", touch: "…" }, { action: "Copy title / Copy link", mouse: "menu", touch: "…", change: "new (text stays selectable)", }, ], }, { object: "Playlist item row", rows: [ { action: "Play", mouse: "rest", touch: "visible" }, { action: "Reorder (drag grip)", mouse: "rest", touch: "visible", change: "the grip gets touch-action: none", }, { action: "Move top / up / down / bottom", mouse: "menu", touch: "…" }, { action: "Move to… / to a recent playlist", mouse: "menu", touch: "…" }, { action: "Pin", mouse: "menu", touch: "…" }, { action: "Copy title / Copy link", mouse: "menu", touch: "…", change: "new" }, { action: "Remove from playlist", mouse: "menu", touch: "…", danger: true, change: "the X column goes; Delete key + undo toast", }, ], }, { object: "Playlist (the rail row and the page header)", rows: [ { action: "Open", mouse: "rest", touch: "visible" }, { action: "Play all", mouse: "rest", touch: "visible", change: "header" }, { action: "Rename", mouse: "menu", touch: "…", change: "the header pencil becomes a menu item", }, { action: "Pin", mouse: "menu", touch: "…" }, { action: "Open on YouTube / Push to YouTube", mouse: "menu", touch: "…", change: "from the header into the menu", }, { action: "Revert to YouTube", mouse: "menu", touch: "…", danger: true }, { action: "Delete", mouse: "menu", touch: "…", danger: true, change: "the header trash goes into the menu", }, ], }, { object: "Channel row (Channels manager)", rows: [ { action: "Open the channel", mouse: "rest", touch: "visible" }, { action: "Priority (stepper)", mouse: "rest", touch: "visible", change: "it is a value, so it stays", }, { action: "Tags", mouse: "rest", touch: "visible", change: "a value, stays" }, { action: "Hidden from feed (state)", mouse: "rest", touch: "visible", change: "a badge; the toggle moves to the menu", }, { action: "Hide / unhide, Backfill", mouse: "menu", touch: "…", change: "from row buttons into the menu", }, { action: "Pin, Copy name / link", mouse: "menu", touch: "…" }, { action: "Reset backfill (admin), Unsubscribe", mouse: "menu", touch: "…", danger: true, change: "from row buttons into the menu", }, ], }, { object: "Plex card (movie, show, season, episode)", rows: [ { action: "Open / play", mouse: "rest", touch: "visible" }, { action: "Watched (state)", mouse: "rest", touch: "visible", change: "the badge stays; the toggle is hover + menu", }, { action: "Watched (toggle)", mouse: "hover", touch: "…", change: "today hover only, not in the menu", }, { action: "Add to playlist", mouse: "hover", touch: "…", change: "today hover only on seasons", }, { action: "Details", mouse: "menu", touch: "…", change: "today hover only" }, { action: "Pin", mouse: "menu", touch: "…" }, ], }, { object: "Download row", rows: [ { action: "Play (library, shared)", mouse: "rest", touch: "visible" }, { action: "Pause / resume / retry (queue)", mouse: "rest", touch: "visible", change: "the queue's primary action", }, { action: "Save file", mouse: "rest", touch: "visible" }, { action: "Error details (state)", mouse: "rest", touch: "visible" }, { action: "Edit clip, Edit details, Share, Manage shares, Redownload, Copy source URL", mouse: "menu", touch: "…", change: "six row buttons become one '…'; a menu is new here", }, { action: "Cancel, Delete, Remove shared", mouse: "menu", touch: "…", danger: true, change: "from row buttons into the menu", }, ], }, { object: "Notification row", rows: [ { action: "Its link (open Downloads / Scheduler, review access…)", mouse: "rest", touch: "visible", }, { action: "Unread (state), Mark read", mouse: "rest", touch: "visible" }, { action: "Revert / find (activity rows)", mouse: "menu", touch: "…", change: "a menu is new here", }, { action: "Dismiss", mouse: "hover", touch: "…" }, { action: "Header: Mark all read", mouse: "rest", touch: "visible" }, { action: "Header: Clear everything", mouse: "menu", touch: "…", danger: true, change: "into the header's '…'", }, ], }, { object: "Scheduler job row and page", rows: [ { action: "Run now", mouse: "rest", touch: "visible" }, { action: "Interval (value, click to edit)", mouse: "rest", touch: "visible" }, { action: "Page: Run all, Pause / resume", mouse: "rest", touch: "visible" }, { action: "Page: Purge discovery", mouse: "menu", touch: "…", danger: true, change: "into the page's '…'", }, ], }, ]; const BACKGROUND: [string, string][] = [ ["Feed", "Sort · View (grid / list) · Watch state · Shorts on / off"], ["Plex browse", "Sort · View"], ["Channels", "Sort · Sync subscriptions · Backfill everything"], ["Playlists", "New playlist · Sync with YouTube"], ["Downloads", "Queue / Library / Shared (the tabs)"], ["Notifications", "Mark all read"], ]; const MOUSE: Record = { rest: "● at rest", hover: "◐ on hover", menu: "☰ menu", }; function Table() { return (
{TABLE.map((g) => ( {g.rows.map((r) => ( ))} ))}
Action Mouse Touch Change against today
{g.object}
{r.action} {r.danger && " ⚠"} {MOUSE[r.mouse]} {r.touch === "visible" ? "● visible" : "… menu"} {r.change}
); } function Card({ touch }: { touch: boolean }) { // The IconButton `media` face (a dark button on a picture); the touch rendering shows the 44 box // that a coarse pointer gives it (A3). const act = clsx(iconButtonClass({ variant: "media", size: "sm" }), touch && "size-11"); return (
12:04 left {!touch && ( )}
Building a home lab on a budget: Proxmox, ZFS and a cheap NAS
Techno Tim · 2 days ago
{touch && ( )}
); } export function C1() { return (
{BACKGROUND.map(([page, items]) => ( ))}
{page} {items}
); }