Skip to content

fix(topics): label a topic by its most-used spelling, not min() - #162

Merged
ralyodio merged 1 commit into
mainfrom
worktree-topics-spam
Aug 29, 2026
Merged

fix(topics): label a topic by its most-used spelling, not min()#162
ralyodio merged 1 commit into
mainfrom
worktree-topics-spam

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

/topics was full of labels like ! news, ["en"], _posts, /life and @work.

None of these are extraction bugs. topicSlug had already normalised every one of them away — the grouping, the URLs and the counts were correct the whole time. Only the display name was wrong.

Cause

Five queries picked that name independently with min(keyword): the rollup (refreshTopics), the topic page (topicBySlug), both alert queries, and the followed-topics list.

min() is a lexicographic minimum, and in ASCII !(0x21) "(0x22) *(0x2A) .(0x2E) /(0x2F) [(0x5B) _(0x5F) all sort before lowercase letters. So whenever one publisher wrote a malformed <category> tag, their spelling won the label for everyone.

Measured on prod:

slug shown used by real spelling used by
news ! news 1 feed news 10,310
en ["en"] 1 feed en 13,130
ai "ai" 1 feed ai 11,051
post _posts 1 feed post 11,774
life /life 4 feeds life 8,985

12,318 of 152,814 topics carry such a label, concentrated at the top of the directory by feed count.

Fix

The rule now lives once, in packages/db/src/topicLabel.js, and ranks by count(distinct feed_id) — the directory's own usage decides. That needs no stoplist and cannot be captured by a single feed. Tie-breaks are shortest-first (so ai beats ai, and ai: at equal counts) then lexicographic for determinism.

The old comment on topicBySlug is why this stayed invisible: it reasoned that "any spelling will do … the rows under one slug differ only in ways the slug already erased". The slug strips punctuation and the keyword keeps it, so the surviving differences are exactly the ugly ones.

Effect

topicBySlug, the alert queries and the followed-topics list correct themselves on deploy. /topics follows within the hour, on the poller's existing hourly refreshTopics. No migration.

Verified

  • New regression test in packages/db/test/topic-label.test.js (fails on min(), passes here)
  • pnpm -r test: 1,305 tests across 10 packages, 0 failures
  • Fix re-run read-only against prod: the 25 worst labels all resolve to the clean spelling, and legitimately non-ASCII ones (e.g. Cyrillic на) are left alone

Not addressed

Topics that are junk in themselves rather than mislabelled — #39 and rsquo from undecoded HTML entities, and non-English grammar words (de, la, que) from an English-only stoplist. Those are extraction-side and need a re-crawl rather than a rollup, so they belong in their own change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TGaU2kAfCZSNxH6pvW5ZZQ

/topics was full of labels like `! news`, `["en"]`, `_posts` and `/life`.
None of them were extraction bugs: `topicSlug` had normalised every one of
them away correctly, and the grouping, URLs and counts were right the whole
time. Only the display name was wrong.

Five queries picked that name independently with `min(keyword)` — the
rollup, the topic page, both alert queries and the followed-topics list.
`min()` is a lexicographic minimum, and in ASCII `!` `"` `*` `.` `/` `[`
and `_` all sort before lowercase letters, so a single publisher's
malformed <category> tag won the label for everybody. Measured on prod:

  slug `news` showed as `! news`   — 1 feed, against 10,310 spelling it `news`
  slug `en`   showed as `["en"]`   — 1 feed, against 13,130
  slug `post` showed as `_posts`   — 1 feed, against 11,774
  slug `life` showed as `/life`    — 4 feeds, against 8,985

12,318 of 152,814 topics carry such a label, concentrated at the top of the
directory by feed count.

The rule now lives once, in topicLabel.js, and ranks by
count(distinct feed_id): the directory's own usage decides, which needs no
stoplist and cannot be captured by one feed. Tie-breaks are shortest-first
(so `ai` beats `ai,` and `ai:`) then lexicographic for determinism.

The old comment on topicBySlug is why this stayed invisible — it reasoned
that "any spelling will do … the rows under one slug differ only in ways
the slug already erased". The slug strips punctuation and the keyword keeps
it, so the surviving differences are exactly the ugly ones.

Not addressed here: topics that are junk in themselves rather than
mislabelled — `#39` and `rsquo` from undecoded HTML entities, and non-English
grammar words from an English-only stoplist. Those are extraction-side and
need a re-crawl, not a rollup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGaU2kAfCZSNxH6pvW5ZZQ
@ralyodio
ralyodio marked this pull request as ready for review August 29, 2026 15:32
@ralyodio
ralyodio merged commit 98e8fe4 into main Aug 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant