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
+20 -6
View File
@@ -66,22 +66,36 @@ def test_a_system_tag_is_nobodys_to_edit(db, make_user):
# --- tags ------------------------------------------------------------------------------------
def test_a_tag_counts_apart_the_links_on_channels_the_user_still_follows(db, make_user):
"""An unsubscribe keeps the channel's tag links; the Channels page's filter lists only followed
channels, so its count must not hold the others."""
def _music_on_four(db, make_user):
"""A tag on four channels: two followed, one followed but hidden, one no longer followed."""
user, other = make_user(), make_user()
tag = service.create_tag(db, user, "Music", "other", None)
for cid in ("UCa", "UCb", "UCgone"):
for cid in ("UCa", "UCb", "UChid", "UCgone"):
db.add(Channel(id=cid, title=cid))
db.flush()
for cid in ("UCa", "UCb"):
db.add(Subscription(user_id=user.id, channel_id=cid))
db.add(Subscription(user_id=user.id, channel_id="UChid", hidden=True))
db.add(Subscription(user_id=other.id, channel_id="UCgone"))
for cid in ("UCa", "UCb", "UCgone"):
for cid in ("UCa", "UCb", "UChid", "UCgone"):
db.add(ChannelTag(channel_id=cid, tag_id=tag.id, user_id=user.id, source="user"))
db.commit()
return user, tag
def test_a_tag_counts_apart_the_links_on_channels_the_user_still_follows(db, make_user):
"""An unsubscribe and a hide keep the channel's tag links; the Channels page's filter lists only
the followed channels not hidden, so its count must not hold the others."""
user, tag = _music_on_four(db, make_user)
[(_, count, subscribed)] = [r for r in queries.tags_with_counts(db, user) if r[0].id == tag.id]
assert (count, subscribed) == (3, 2)
assert (count, subscribed) == (4, 2)
def test_the_tag_list_sends_each_count_under_its_own_name(db, make_user, as_user):
"""Both counts are ints, so a swap would type-check: the answer itself pins which is which."""
user, tag = _music_on_four(db, make_user)
[row] = [t for t in as_user(user).get("/api/tags").json() if t["id"] == tag.id]
assert (row["channel_count"], row["subscribed_count"]) == (4, 2)
def test_a_tag_is_made_once_per_name_and_an_unknown_category_is_other(db, make_user):