fix(e8): deep-review hardening + cut 0.73.0

Fixes from several multi-agent deep-review passes over the E8 feed-filtering &
channel-manager epic (S1-S4):
- feed_years buckets by UTC year to match the UTC-pinned year-filter bounds
- tag suggestions: examples ∩ subscribed (drop tagged-then-unsubscribed channels
  from the profile denominator); exclude hidden channels as suggestion targets
- channel manager: restore ghost chips + surface an error toast on a failed tag accept
- year filter: debounce the search term so the per-year facet doesn't refetch per keystroke
- channel tags: aria-label the "+N" overflow pills
- extract useTagSuggestions hook; add backend + hook tests (profile boundaries, cap,
  language exclusion, unsubscribed/hidden handling, UTC bucketing, bulk-accept)

Bump VERSION to 0.73.0 + release notes.
This commit is contained in:
2026-08-26 05:25:47 +02:00
parent 17f8885fb1
commit 7104767ff9
13 changed files with 519 additions and 70 deletions
+57
View File
@@ -253,6 +253,63 @@ def test_feed_years_counts_ignore_the_date_narrowing_but_honour_other_filters(
]
def test_feed_years_buckets_by_utc_year_under_a_non_utc_session(db, make_user):
"""feed_years must bucket by UTC year to stay in lockstep with the year filter's UTC-pinned
published_after/before bounds. published_at is timestamptz; a bare extract('year', …) would use the
DB session timezone, so a video just before midnight UTC on New Year's Eve would bucket into the
wrong (local) year on a non-UTC session and disagree with the feed the year chip drills into. Called
directly on this session (the HTTP client uses a separate connection SET TIME ZONE wouldn't reach)."""
from sqlalchemy import text
from app.feedquery import feed_years
user = make_user(email="tz-years@example.com")
db.add(Channel(id="chTZ", title="chTZ", subscriber_count=1))
db.add(Subscription(user_id=user.id, channel_id="chTZ", priority=0, hidden=False))
# 2023-12-31 23:30 UTC — a positive-offset session tz local-shifts this into 2024.
db.add(
Video(
id="tzv0",
channel_id="chTZ",
published_at=datetime(2023, 12, 31, 23, 30, tzinfo=timezone.utc),
title="NYE clip",
is_short=False,
live_status="none",
via_search=False,
via_explore=False,
)
)
db.commit()
params: dict = {
"tags": [],
"tag_modes": {"language": "or", "topic": "or", "other": "or"},
"untagged": False,
"channel_ids": [],
"q": None,
"min_duration": None,
"max_duration": None,
"max_age_days": None,
"published_after": None,
"published_before": None,
"show_normal": True,
"include_shorts": False,
"include_live": False,
"show": "all",
"scope": "my",
"library_source": "organic",
}
db.execute(
text("SET TIME ZONE 'Asia/Tokyo'")
) # +9: pushes the video's local wall-clock into 2024
try:
years = feed_years(db, user, **params)
finally:
db.execute(text("SET TIME ZONE 'UTC'")) # SET (not SET LOCAL) survives rollback — reset it
# UTC bucketing keeps the boundary video in 2023; a bare session-tz extract would report 2024.
assert years == [{"year": 2023, "count": 1}]
@pytest.mark.parametrize("case", _SUMMARY_CASES)
def test_feed_summary_matches_count_plus_facets(db, as_user, make_user, case):
# /feed/summary must return byte-identical numbers to the old /feed/count + /facets it replaces,