Skip to content

feat: show who wrote the wire in the left rail - #74

Merged
dimslaev merged 1 commit into
masterfrom
feat/rail-who-wrote
Oct 10, 2026
Merged

dimslaev merged 1 commit into
masterfrom
feat/rail-who-wrote

Conversation

@dimslaev

Copy link
Copy Markdown
Owner

Session 2 of 4 (rails). Adds the first left-rail panel: how the feed's current window splits by origin, and how its articles were found.

Changes

  • Backend: origin_counts(session, since) in catalog/facets.py. One COUNT per Origin, built with origin_condition, zeros included. Also unlabelled and found_via grouped counts.
  • Route GET /api/v1/articles/origins?since=<iso>, thin adapter, 422 on a bad since.
  • Models OriginCount, FoundViaCount, OriginCounts in catalog/models.py.
  • Regenerated types.gen.ts, sdk.gen.ts, schemas.gen.ts.
  • Frontend: components/Rails/panels/WhoWrote.tsx, registered first in LEFT. Only change to panels.tsx is the import and the one LEFT entry. RailPanelDef is untouched.
    • Range line at the top of the body follows the Published filter. PUBLISHED_DAYS and cutoffIso are now exported from ArticlesList.tsx, and ORIGIN_OPTIONS from FeedFilters.tsx.
    • Rows are sorted by count. Bars are foreground/45 on a faint track. The active From row and its label are paper. A click sets origin, and clicking the active row clears it.
    • Footer: "Of N: x from publishers' own feeds, y found on Hacker News, z via newsletters." Every non-null found_via that isn't Hacker News counts as a newsletter.
    • At 40% opacity and inert while search is active. Query key ["articles", "origins", dateRange], staleTime 5 min.
  • docs/product.md: one line under the feed section.

Where I departed from the plan

  • marked_for_deletion_at is not excluded. The plan says to exclude it "same as the feed", but the feed (_filters in articles.py) doesn't exclude those rows. The plan's main rule is that the numbers always match what the From filter shows, so I kept that rule and left the marked rows in.
  • unlabelled counts posts only. These are posts by an unknown publisher. A repo, paper or model from an unknown publisher already counts under its own origin. With this, total = sum(origins) + unlabelled.
  • Panel title is "Who wrote the wire", not "Who wrote this week". The window can be 3 days or a month, so "this week" would often be wrong. The title matches this PR's title.
  • Playwright at 1440. The plan asks that [ hides and shows the left rail, with the state surviving a reload. The merged shell (feat: add collapsible side rails to the feed #72) doesn't work that way: on a wide screen the rails are always open, [ does nothing, and only a panel's folded state is saved. The test checks what the shell actually does: [ leaves the rail visible, and folding the panel survives a reload in both directions.
  • "The feed shows only writers" is checked from the feed's API response, not from row text. Every returned article must be a post from an individual publisher, and the row count must match. Row text can't tell a writer from other publishers.

Verification

  • Backend: Postgres via compose (pgvector image from Docker Hub, tagged as the compose image, because the ghcr pull is blocked in this sandbox), then prestart.sh and pytest. Result: 537 passed, 1 failed. The failure is test_read_endpoints_are_public[/search?q=agents]: the test downloads an embedding model and the sandbox proxy blocks it (403). It's unrelated to this change.
  • New backend tests in catalog/tests/test_routes.py:
    • every origin is listed
    • a company's repo counts as repo
    • a community post counts as media
    • an unknown publisher's post counts as unlabelled and in the total
    • since bounds the counts
    • found_via grouping, including null
    • 422 on a bad since
    • the endpoint is public
  • Lint and types: ruff check, ruff format, mypy, ty and biome all clean.
  • Build: bun run build passes.
  • Playwright: run against a live backend and mailcatcher, using the preinstalled Chromium. Pinned 1.61.1 wants a newer browser build, so I pointed it at Chromium with a temporary local config that isn't committed.
    • tests/rails.spec.ts: all 4 pass (1440, 1000, 390, Writers click).
    • Full suite: 28 passed, 1 failed. The failure is likes.spec.ts "shows Liked / My profile / Password / Danger zone tabs". It fails the same way on master, since sign-in has no passwords and the profile has no Password tab.
  • I checked a screenshot of the panel at 1440 by eye against the picker design.
  • Merged latest master before opening: already up to date.

🤖 Generated with Claude Code

https://claude.ai/code/session_0183b3dLpUezpQ8TCgDQnysM


Generated by Claude Code

@dimslaev
dimslaev merged commit 2df6294 into master Oct 10, 2026
3 of 5 checks passed
@dimslaev
dimslaev deleted the feat/rail-who-wrote branch October 10, 2026 08:35
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