Skip to content

feat: show who writes the wire in a voices panel - #78

Merged
dimslaev merged 3 commits into
masterfrom
feat/rail-voices
Oct 13, 2026
Merged

dimslaev merged 3 commits into
masterfrom
feat/rail-voices

Conversation

@dimslaev

Copy link
Copy Markdown
Owner

What

  • Backend: voices(session, kind, since, limit=8) in catalog/facets.py. It returns publishers of one kind (individual / lab / company) ordered by count, then best score. Each row has its scores oldest first (last 15) and its best article. Writers count posts only. Labs and Companies count every kind.
  • Route GET /api/v1/articles/voices?kind=&since=&limit= is a thin adapter. It returns 422 on a bad kind (a new VoiceKind enum) or a bad since. Response models Voice, VoiceArticle are in catalog/models.py. The client is regenerated.
  • Frontend: new Rails/panels/Voices.tsx, registered in LEFT right after Who wrote (the last spot). It has Writers / Labs / Companies tabs built from FilterOption, a fixed 30-day window, and rows of name + ScoreBars + count, with the best title and its score below in dim.
  • Clicking a row sets publisher in Filters, which is passed to readArticles. A "Publisher: Name ×" chip in the From row clears it, and so does clicking the active row. A pick sends voices_pick ({publisher, kind}), the same pattern as who_wrote_pick.
  • docs/product.md: one line in the rails paragraph.

Where this differs from the plan, and why

  • Deleted articles: the feed has no deleted filter. Rows are hard-deleted, and marked_for_deletion_at is only a review flag the feed still shows. So Voices matches the feed and adds no filter.
  • Tab memory: RailsProvider stores only open per panel, so the tab lives in component state.
  • Picking a row also sets Published to Month, because the counts cover 30 days and the feed's default window is a week. Without this, a row reading "5" could open a feed with 1 article or none.
  • Picking from Writers also sets From = Writers. That filter means posts by individuals, so the feed shows exactly what the row counted and leaves out the writer's repos and models. Labs and Companies clear From, since their counts include every kind.
  • Display name in filter state: Filters also holds publisherName, so the chip can show the name and not the slug.
  • Search: while a search is active the panel is greyed and inert, like Who wrote, because a search ignores the publisher filter.

Verified

  • Backend: pytest on a fresh local DB (pgvector pg17 in docker, prestart.sh): 592 passed. The 1 failure is test_read_endpoints_are_public[/search?q=agents], because the sandbox proxy blocks the Hugging Face model download. That is environmental and unrelated.
    • New tests: kind filter; Writers excludes repo/model articles; Labs count every kind; since bounds counts; scores oldest first and capped at 15; best article is the highest score; order is count, then best score; 422 on a bad kind and on a bad since.
  • Backend lint and types: ruff check, ruff format --check, mypy, ty all clean, plus the banned-module-names check.
  • Frontend: bun run lint (biome), tsc -p tsconfig.build.json, bun run build all clean.
  • Playwright, new tests/voices.spec.ts (mocked voices endpoint and publisher feed): tabs switch the list (including the empty Companies message); clicking a name narrows the feed and shows the chip; the chip clears it; clicking the active row clears it; a failed fetch hides the panel. Ran against a local backend, Vite and mailcatcher. All pass, and rails.spec, reader-picks.spec and articles.spec pass with them.
  • Full Playwright suite: 6 failures in stories.spec, topic-map.spec and likes.spec. The same 6 fail on master in this environment with this change stashed, so they are not from this PR.
  • I also checked the panel and chip by screenshot at 1440px.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E5MK5gpkC5MCcqo4fKqxZ5


Generated by Claude Code

@dimslaev
dimslaev merged commit cfc811c into master Oct 13, 2026
3 of 5 checks passed
@dimslaev
dimslaev deleted the feat/rail-voices branch October 13, 2026 19:04
@dimslaev
dimslaev deployed to production October 13, 2026 19:04 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
production — cfc811c6 Deployed Oct 13, 2026 by dimslaev via deploy #211
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.

2 participants