fix: tag counts, chip focus, upload naming, share links and unsized clips hold after round 2

- A tag counts the followed channels the Subscriptions tab shows: hidden ones are left out, and a
  subscribe, unsubscribe, hide or tag toggle refreshes the count. The Tag manager's row and its
  hover list count the same channels as the chip; /api/tags sends both counts under their names.
- A chip's count keeps the chip's colour after a dot (a dimmed count fell under 4.5:1). A pick in
  the open "+N" fold closes it; a chip that leaves the list hands the focus to the one in its place.
  The fold is a dialog of toggle buttons, not an empty menu.
- Upload: a folder name left by a press is made once the press is over, if the dialog is still
  open, instead of guessing a dismissal from Modal's markup. While the folder is made, its place
  holds the focus and a live region says so.
- Share: an expired link has no Copy and a warn badge; the link made on open goes into the cached
  list, so a reopen makes no second one; the New link form survives the link made on open.
- Watch: a repeated Enter sends one unlock while one is on its way. See all buttons are named with
  their rail.
- An unsized clip of a rotated file gets the upright size its crop cut.
This commit is contained in:
2026-11-06 02:20:52 +01:00
parent 2baad2ebae
commit 4d1cf66d96
30 changed files with 526 additions and 128 deletions
+21
View File
@@ -178,6 +178,27 @@ def test_a_clip_ffprobe_cannot_size_gets_the_size_its_crop_cut(
assert (asset.width, asset.height) == (160, 120)
def test_a_rotated_clip_ffprobe_cannot_size_gets_the_upright_size_its_crop_cut(
db, make_user, tmp_path, monkeypatch
):
"""The frame was drawn on the upright picture, which is the stored one turned: so is its cut."""
monkeypatch.setattr(settings, "files_root", str(tmp_path))
src = tmp_path / "in.mp4"
_source(src, "320x240", "x=0:y=0:w=160:h=240", sar=1, rotate=90)
def probe_fails():
monkeypatch.setattr(pipeline.probe, "probe_media", lambda _p: (None,) * 5)
asset = _cut_asset(
db, make_user, src,
{"crop": {"x": 0, "y": 160, "w": 240, "h": 160, "frame_w": 240, "frame_h": 320}},
then=probe_fails,
) # fmt: skip
# What ffmpeg cut (the rotated test above): 240 x 160, not the stored 320 x 120.
assert (asset.width, asset.height) == (240, 160)
@pytest.mark.parametrize("key", ["frame_w", "x"])
def test_an_infinite_number_in_a_crop_is_dropped_not_a_crash(key):
crop = {"x": 4, "y": 6, "w": 100, "h": 50, "frame_w": 640, "frame_h": 240}