feat(design): a channel's and a Plex playlist's one action list (C1) — the row's "…", Delete + undo, an action toast's 8 s floor (E28 S3e2)

- Channels: the row's full-history, hide and unsubscribe buttons become its "…" at the row end
  (useChannelActions: open, pin, YouTube, copy name / link, full history or the admin reset,
  hide with an undo toast, unsubscribe behind its danger confirm); the card layout docks the "…"
  beside the name (ChannelLayoutGrid moreKey). The admin reset's confirm now names the reset.
- Plex playlist page: a title, a show group and a season each open the page's one row menu (a
  title's is its usePlexCardActions list plus the removal); no X at rest; removals take an undo
  toast whose Undo puts every row back at its own index in one request (bulk add `positions`);
  j/k walk the visible rows, Delete removes the walked one; rename and delete in the header's "…".
- notify: a toast with an action lives at least 8 s (the owner's call), whatever the toast setting.
This commit is contained in:
2026-09-26 05:56:07 +02:00
parent b0e51c6f89
commit 1b946113c6
27 changed files with 1354 additions and 293 deletions
@@ -85,3 +85,20 @@ describe("ChannelLayoutGrid · keyboard", () => {
expect(opened).toEqual(["UCb"]);
});
});
describe('ChannelLayoutGrid · the card\'s "…"', () => {
it("docks it in the heading row beside the name, not in the meta line", () => {
const columns: Column<Ch>[] = [
{ key: "channel", header: "Channel", cardPrimary: true, render: (c) => <b>{c.title}</b> },
{ key: "stored", header: "Stored", render: () => <i>12</i> },
{ key: "actions", header: "", render: (c) => <u data-testid={`more-${c.id}`}>…</u> },
];
const { getByTestId, getByText } = render(
<ChannelLayoutGrid rows={rows} columns={columns} rowKey={(c) => c.id} moreKey="actions" />,
);
const heading = getByText("Alpha").closest("div")!.parentElement!;
expect(heading.contains(getByTestId("more-UCa"))).toBe(true);
// Exactly once: a slotted column is not also painted in the meta line.
expect(document.querySelectorAll('[data-testid="more-UCa"]')).toHaveLength(1);
});
});