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:
@@ -51,17 +51,25 @@ def compute_tag_suggestions(db: Session, user: User) -> dict[str, list[dict]]:
|
||||
capped at ``MAX_PER_CHANNEL``. Channels with no suggestion are omitted (empty dict when the
|
||||
user has no tag with a usable profile). Deterministic for a fixed DB state.
|
||||
"""
|
||||
# Candidate universe: the channels this user manages. A user can only tag a subscribed channel
|
||||
# (attach_tag requires a subscription), so example channels are a subset of this too — which
|
||||
# lets us bound the auto-tag scan to just these channels.
|
||||
subscribed: set[str] = set(
|
||||
db.scalars(select(Subscription.channel_id).where(Subscription.user_id == user.id)).all()
|
||||
)
|
||||
# Candidate universe: the channels this user is subscribed to. `subscribed` (incl. hidden) is the
|
||||
# SIGNAL set — a hidden channel is still a valid example / topic-feature source, since hiding only
|
||||
# affects the feed, not the tagging pattern. `hidden` is excluded as a suggestion TARGET below (the
|
||||
# Channel Manager shows ghost chips only on non-hidden rows, so suggesting for hidden channels would
|
||||
# inflate the header count past what's shown and let bulk-accept tag channels the user can't see).
|
||||
subs = db.execute(
|
||||
select(Subscription.channel_id, Subscription.hidden).where(Subscription.user_id == user.id)
|
||||
).all()
|
||||
subscribed: set[str] = {cid for cid, _ in subs}
|
||||
hidden: set[str] = {cid for cid, h in subs if h}
|
||||
if not subscribed:
|
||||
return {}
|
||||
|
||||
# The user's manual tag links: tag_id -> example channels, and each channel's user-tag set (so
|
||||
# a channel with ANY user tag is excluded from "untagged").
|
||||
# a channel with ANY user tag is excluded from "untagged"). Intersect with `subscribed`: unsubscribe
|
||||
# deletes the Subscription but NOT the source='user' ChannelTag, so a tagged-then-unsubscribed
|
||||
# channel would otherwise stay an "example" — inflating a profile's CHAR_SUPPORT denominator while
|
||||
# contributing zero features (features are scanned over `subscribed` only), dropping characteristic
|
||||
# topics. Keeping examples ⊆ subscribed makes the support fraction honest.
|
||||
examples: dict[int, set[str]] = {}
|
||||
user_tagged: set[str] = set()
|
||||
for tag_id, cid in db.execute(
|
||||
@@ -69,6 +77,8 @@ def compute_tag_suggestions(db: Session, user: User) -> dict[str, list[dict]]:
|
||||
ChannelTag.user_id == user.id, ChannelTag.source == "user"
|
||||
)
|
||||
).all():
|
||||
if cid not in subscribed:
|
||||
continue
|
||||
examples.setdefault(tag_id, set()).add(cid)
|
||||
user_tagged.add(cid)
|
||||
|
||||
@@ -104,7 +114,7 @@ def compute_tag_suggestions(db: Session, user: User) -> dict[str, list[dict]]:
|
||||
|
||||
suggestions: dict[str, list[dict]] = {}
|
||||
for cid in subscribed:
|
||||
if cid in user_tagged: # already has a user tag → not "untagged"
|
||||
if cid in hidden or cid in user_tagged: # hidden → no chips shown; tagged → not "untagged"
|
||||
continue
|
||||
cfeat = features.get(cid)
|
||||
if not cfeat:
|
||||
|
||||
Reference in New Issue
Block a user