fix(keys): epic-end review round 3 — the part-way clip count shows, busy buttons keep focus, cards say their names

Targeted round over 3cd0c525's fix surfaces: 14 raised, 14 fixed.

- VideoEditor: the "only X of Y clips" toast went silent exactly on the job-cap/quota refusal it was
  written for (req had already reported the error); it now always shows. The cut list holds still while
  Create queues, and a retry continues the "(i/N)" series.
- ChannelPage: Subscribe/Unsubscribe use Button `loading`, "Load more" and ChannelDiscovery's Subscribe
  block the click with aria-disabled instead of native disabled; busy Block/Hide no longer light up on
  hover.
- listNav: an item can name itself (`navItemAttrs(i, name)`) — Plex posters, seasons, episodes, message
  rows and download rows pass their title; focus on a toast's Undo keeps the removed card's slot.
- AnchoredPopover: the hand-back only for focus-managed surfaces (a hover card no longer pulls focus
  back); the dead panel-ref check is gone.
- AccountPanel: no confirmation mail offered to the demo account; send_failed copy no longer blames
  the message for an unreachable server. Pager comment points at scrollParent.
This commit is contained in:
2026-09-13 20:47:13 +02:00
parent 3cd0c525e2
commit c6b552e75f
18 changed files with 312 additions and 98 deletions
+50
View File
@@ -511,4 +511,54 @@ describe("useListNav · leaving the list, and naming the item (deep-review E11 r
expect(a0.hasAttribute("aria-label")).toBe(false);
expect(screen.getByTestId("a1").getAttribute("aria-label")).toBe("title");
});
// A Plex poster's open element is the whole card — badges, title, year, rating and summary — and a
// season card's holds only the hover caption "Open show": named from that text, every season was
// "Open show" (deep-review E11 round 3). The item's own name wins.
it("names a card by the name its item gives, over the text of what it opens", () => {
function Posters() {
const ref = useRef<HTMLDivElement>(null);
useListNav(ref, { enabled: true });
return (
<div ref={ref}>
<div {...navItemAttrs(0, "Season 1")} data-testid="p0">
<div role="button" tabIndex={0} data-nav-open="">
Open show
</div>
</div>
</div>
);
}
render(<Posters />);
press("j");
expect(screen.getByTestId("p0").getAttribute("aria-label")).toBe("Season 1");
});
// Hide a card, then Undo in the toast that put up: focus on the toast's button is not the reader
// leaving the list, and `j` goes on from the card put back (deep-review E11 round 3).
it("keeps the slot when focus went to the Undo of a toast", () => {
withLayout(
(_, n) => ({ left: 0, top: n * 100 }),
() => {
const page = (ids: number[]) => (
<>
<div data-toast-region="">
<button data-testid="undo">undo</button>
</div>
<Swappable ids={ids} />
</>
);
const { rerender } = render(page([0, 1, 2, 3]));
press("j");
press("j");
press("j");
rerender(page([0, 1, 3]));
act(() => screen.getByTestId("undo").focus());
rerender(page([0, 1, 2, 3]));
act(() => screen.getByTestId("undo").blur());
press("j");
expect(focused()).toBe("r2");
},
);
});
});