Explore: show only the top 5 most active owners, plus instance owner/repo totals (currently 50 name-sorted sections, no totals) #295

Closed
opened 2026-09-10 16:28:23 +00:00 by crueber · 3 comments
Owner

What's requested

The /explore page should show only the top 5 most active owners (organizations/users) — not the current cap of 50 name-sorted sections.

Current state (code evidence)

  • MAX_OWNERS = 50 (web/src/lib/owners.js:8) caps the owner sections on /explore; Owners.jsx:110 orders them with newestFirst(owners()) — reverse-lexicographic name order, not activity. "Showing newest N of M owners" is a name-sort claim, not an activity claim.
  • Repo rows inside each section are activity-ordered (#247: repos/detailed?sort=activity&order=desc), but the sections themselves aren't, and there is no per-owner activity value anywhere yet — #283 (owner-level last_commit_time rollup + sort=activity on GET /api/v1/owners) is still open.
  • Cost note: each rendered owner section triggers a per-owner detailed fetch; at 50 owners that's 50 catalog reads per cold page load. Cutting to 5 also cuts that to 5 — a real performance win, not just cosmetic.

Proposed behavior

  • /explore renders the 5 owners with the most recent commit activity (owner whose repo committed most recently first), each section capped at MAX_REPOS_PER_OWNER as today.
  • "Active" = has at least one repo with a last_commit_time; owners with no commit activity are not shown on the top-5 page at all (they're the opposite of active). An instance with fewer than 5 active owners shows fewer than 5 — no filler.
  • The "showing newest N of M owners" line dies with the cap (or becomes "top 5 of M owners by activity" only if an overflow affordance is wanted — decision for the implementer; the ask implies the page is just top 5).

Implementation shape (depends on #283 — sequence this after it)

  • With #283 landed (owner rollup in the catalog): the page requests GET /api/v1/owners?sort=activity&order=desc and slices the first 5 — MAX_OWNERS becomes 5, newestFirst is finally retired from this page (its own comment anticipates the replacement), and "active" filtering is just "skip rows with null last_commit_time".
  • Without #283 the client can't know which owners are active without fetching all owners' details — that's the N-fetch antipattern the page explicitly avoids. So this should be sequenced after #283, reusing its rollup rather than inventing a client-side ranking.

Also in this change: instance totals

Show the total number of owners and the total number of repositories in the system on /explore (a small stats line on the intro card is the natural spot — it already carries the "walhub is a git host…" copy).

  • Counts must be true totals, not the page's caps: all owners (including ones not in the top-5 view) and all repos across them.
  • Source: extend the existing owners/catalog listing with count fields (the #283 catalog aggregate already walks every repo — owner_count and repo_count are free there), or a tiny GET /api/v1/stats if the author prefers a dedicated surface. Either way, through all three route twins.
  • The counts must reflect deleted-repo ghosting correctly — count manifest-backed repos only, the same rule liveRepos uses (cmd/walhub/serve.go).
  • Headless test: counts render from the payload (no client-side recomputation from the top-5 slice).

Acceptance criteria

  • /explore shows at most 5 owner sections, ordered by most recent commit activity across each owner's repos.
  • Owners with no commit activity never appear in the top 5; fewer than 5 active owners renders fewer than 5 sections.
  • Per-owner repo rows keep the #247 ordering and MAX_REPOS_PER_OWNER cap (unchanged).
  • Implemented on the #283 owner-activity data (no client-side all-owners fetch storm); newestFirst retired from this page.
  • Cold-load catalog reads for the page drop from ~50 to ~5 (verified by fetch counting in a headless test).
  • Instance totals (owner count, repo count) are displayed on the page and reflect true totals (uncapped, ghost-free), served through all three route twins.
  • Empty instance still shows the existing "no repositories yet" empty state.
## What's requested The `/explore` page should show only the **top 5 most active owners** (organizations/users) — not the current cap of 50 name-sorted sections. ## Current state (code evidence) - `MAX_OWNERS = 50` (`web/src/lib/owners.js:8`) caps the owner sections on `/explore`; `Owners.jsx:110` orders them with `newestFirst(owners())` — reverse-lexicographic **name** order, not activity. "Showing newest N of M owners" is a name-sort claim, not an activity claim. - Repo *rows* inside each section are activity-ordered (#247: `repos/detailed?sort=activity&order=desc`), but the sections themselves aren't, and there is no per-owner activity value anywhere yet — #283 (owner-level `last_commit_time` rollup + `sort=activity` on `GET /api/v1/owners`) is still open. - Cost note: each rendered owner section triggers a per-owner detailed fetch; at 50 owners that's 50 catalog reads per cold page load. Cutting to 5 also cuts that to 5 — a real performance win, not just cosmetic. ## Proposed behavior - `/explore` renders the **5 owners with the most recent commit activity** (owner whose repo committed most recently first), each section capped at `MAX_REPOS_PER_OWNER` as today. - "Active" = has at least one repo with a `last_commit_time`; owners with no commit activity are not shown on the top-5 page at all (they're the opposite of active). An instance with fewer than 5 active owners shows fewer than 5 — no filler. - The "showing newest N of M owners" line dies with the cap (or becomes "top 5 of M owners by activity" only if an overflow affordance is wanted — decision for the implementer; the ask implies the page is just top 5). ## Implementation shape (depends on #283 — sequence this after it) - **With #283 landed** (owner rollup in the catalog): the page requests `GET /api/v1/owners?sort=activity&order=desc` and slices the first 5 — `MAX_OWNERS` becomes 5, `newestFirst` is finally retired from this page (its own comment anticipates the replacement), and "active" filtering is just "skip rows with null `last_commit_time`". - **Without #283** the client can't know which owners are active without fetching all owners' details — that's the N-fetch antipattern the page explicitly avoids. So this should be sequenced **after** #283, reusing its rollup rather than inventing a client-side ranking. ## Also in this change: instance totals Show the **total number of owners** and the **total number of repositories** in the system on `/explore` (a small stats line on the intro card is the natural spot — it already carries the "walhub is a git host…" copy). - Counts must be **true totals**, not the page's caps: all owners (including ones not in the top-5 view) and all repos across them. - Source: extend the existing owners/catalog listing with count fields (the #283 catalog aggregate already walks every repo — `owner_count` and `repo_count` are free there), or a tiny `GET /api/v1/stats` if the author prefers a dedicated surface. Either way, through all three route twins. - The counts must reflect deleted-repo ghosting correctly — count manifest-backed repos only, the same rule `liveRepos` uses (`cmd/walhub/serve.go`). - Headless test: counts render from the payload (no client-side recomputation from the top-5 slice). ## Acceptance criteria - [ ] `/explore` shows at most 5 owner sections, ordered by most recent commit activity across each owner's repos. - [ ] Owners with no commit activity never appear in the top 5; fewer than 5 active owners renders fewer than 5 sections. - [ ] Per-owner repo rows keep the #247 ordering and `MAX_REPOS_PER_OWNER` cap (unchanged). - [ ] Implemented on the #283 owner-activity data (no client-side all-owners fetch storm); `newestFirst` retired from this page. - [ ] Cold-load catalog reads for the page drop from ~50 to ~5 (verified by fetch counting in a headless test). - [ ] Instance totals (owner count, repo count) are displayed on the page and reflect true totals (uncapped, ghost-free), served through all three route twins. - [ ] Empty instance still shows the existing "no repositories yet" empty state.
crueber changed title from Explore: show only the top 5 most active owners (currently 50 name-sorted sections) to Explore: show only the top 5 most active owners, plus instance owner/repo totals (currently 50 name-sorted sections, no totals) 2026-09-10 16:29:43 +00:00
Author
Owner

Fix is up: PR #306 (branch fix/issue-295) - /explore renders the top-5 most-active owners over the #283 owners/detailed rails (activeOwnerNames filter before the 50->5 slice; cold load ~51 to 6 GETs), plus the instance owner total on the intro card from the payload's uncapped row count. No backend change, no new deps; node --test 586/586, vite build green, browser proof open (loopback guard).

One acceptance item needs a backend rail and is deliberately NOT in this PR: the instance REPO total. No endpoint serves one (owners/detailed rows carry no repo counts, no stats endpoint), and deriving it client-side would be either a capped sum (dishonest) or an N-fetch over every owner's listing (the antipattern this page avoids). Propose a follow-up: repo_count on the owners/detailed rows (ghost-filtered like liveRepos) or a tiny stats surface through all three route twins - happy to implement that rail as its own issue/PR.

Fix is up: PR #306 (branch fix/issue-295) - /explore renders the top-5 most-active owners over the #283 owners/detailed rails (activeOwnerNames filter before the 50->5 slice; cold load ~51 to 6 GETs), plus the instance owner total on the intro card from the payload's uncapped row count. No backend change, no new deps; node --test 586/586, vite build green, browser proof open (loopback guard). One acceptance item needs a backend rail and is deliberately NOT in this PR: the instance REPO total. No endpoint serves one (owners/detailed rows carry no repo counts, no stats endpoint), and deriving it client-side would be either a capped sum (dishonest) or an N-fetch over every owner's listing (the antipattern this page avoids). Propose a follow-up: repo_count on the owners/detailed rows (ghost-filtered like liveRepos) or a tiny stats surface through all three route twins - happy to implement that rail as its own issue/PR.
Author
Owner

Review of PR #306 (branch fix/issue-295 @860ceb6, verified in scratch worktree /tmp/pr306, since removed). No browser per instructions — headless tests + reasoning only; no docker, no system packages, main worktree untouched (still clean on main).

VERDICT: ready to merge. The repo-total gap is now tracked as #307 (filed); see the call at the end.

CHECKS (all pass, file:line on the PR branch):

  • Top-5 most-active: Owners.jsx:114-115 fetches owners.listDetailed({sort:activity,order:desc}) (the #283 server rank, correct past any cap); Owners.jsx:175 runs activeOwnerNames (owners.js:163 — drops rows whose last_commit_time is not a string) BEFORE pageSlice(ordered, MAX_OWNERS=5) at Owners.jsx:180. Filter-before-slice confirmed; MAX_OWNERS 50->5 at owners.js:14.
  • Owner total honest: intro card uses the payload's owners.length (Owners.jsx:153-163, 'Home to N owners'), never the page slice. ownersDetailed is uncapped by construction (internal/api/owners_activity.go:82-117 — membership is the Owners() registry, one row per owner, no pagination), so the count is a true owner total.
  • REPO total gap verified, not freelanced: OwnerActivityRow carries only {name, last_commit_sha, last_commit_time} (owners_activity.go:35-44); no stats endpoint exists. Client-side derivation would be a capped sum (dishonest) or an N-fetch over every owner (the antipattern). Deferral is correct; tracked as #307 (repo_count on owners/detailed rows or a stats surface, ghost-filtered like liveRepos, triple twins + SDK + docs).
  • Overflow/empty honest: Owners.jsx:195-199 gates the line on extra>0 ('showing top 5 of A active owners', active count only — <=5 shows no line); Owners.jsx:184-190 keeps 'no repositories yet' for empty instances and adds 'no commit activity yet' for owners-but-no-commits. No filler sections.
  • MAX_OWNERS blast radius: sole consumer is Owners.jsx (+ tests); the core.js:256 mention is a comment and stays accurate. Nothing else breaks.
  • Re-rank fallback coherent: Owners.jsx:177-179 gates orderOwnersByActivity on hasKnownActivity (server order kept on first paint) in the same total order the server uses, refining shown sections only — no fight with server order.
  • Laws 1/7/8/12: no new deps (4-file diff, no package.json); loading fallback + independent per-owner sections (no silent spinner); page+lib change only, no core/registry/seam changes; 12_web_ui.md Data/Order/Caps updated with a FIXED(#295) entry appended (not rewritten) that declares the repo-total gap.
  • Dark/light N/A (no style changes; existing muted/divide classes reused). Doc entries accurate.

TESTS (scratch worktree, web/node_modules symlinked read-only from the main checkout — main files untouched):

  • node --test web/test/unit/*.test.js: 586/586 pass (~271s wall; the suite is slow, not hung — earlier 180s tool cap just cut it off).
  • owners.test.js: 23/23 (activeOwnerNames order/filter/edges, filter-then-top-5-then-uncapped-total composition, fewer-than-5, 1+5=6 cold-fetch budget).
  • vite build: green (1.97s).

NO CODE PUSHED: nothing defective found. One cosmetic nit left alone to avoid churn: an empty instance reads 'Home to 0 owners — showing the most active below.' directly above 'no repositories yet' — slightly awkward, fully honest.

MERGE RECOMMENDATION: ready to merge. #295's top-5 + owner-total items are met; its repo-total item moves to #307. If #295 should stay open until #307 lands, retarget the PR's 'Fixes #295' keyword before merging.

Review of PR #306 (branch fix/issue-295 @860ceb6, verified in scratch worktree /tmp/pr306, since removed). No browser per instructions — headless tests + reasoning only; no docker, no system packages, main worktree untouched (still clean on main). VERDICT: ready to merge. The repo-total gap is now tracked as #307 (filed); see the call at the end. CHECKS (all pass, file:line on the PR branch): - Top-5 most-active: Owners.jsx:114-115 fetches owners.listDetailed({sort:activity,order:desc}) (the #283 server rank, correct past any cap); Owners.jsx:175 runs activeOwnerNames (owners.js:163 — drops rows whose last_commit_time is not a string) BEFORE pageSlice(ordered, MAX_OWNERS=5) at Owners.jsx:180. Filter-before-slice confirmed; MAX_OWNERS 50->5 at owners.js:14. - Owner total honest: intro card uses the payload's owners.length (Owners.jsx:153-163, 'Home to N owners'), never the page slice. ownersDetailed is uncapped by construction (internal/api/owners_activity.go:82-117 — membership is the Owners() registry, one row per owner, no pagination), so the count is a true owner total. - REPO total gap verified, not freelanced: OwnerActivityRow carries only {name, last_commit_sha, last_commit_time} (owners_activity.go:35-44); no stats endpoint exists. Client-side derivation would be a capped sum (dishonest) or an N-fetch over every owner (the antipattern). Deferral is correct; tracked as #307 (repo_count on owners/detailed rows or a stats surface, ghost-filtered like liveRepos, triple twins + SDK + docs). - Overflow/empty honest: Owners.jsx:195-199 gates the line on extra>0 ('showing top 5 of A active owners', active count only — <=5 shows no line); Owners.jsx:184-190 keeps 'no repositories yet' for empty instances and adds 'no commit activity yet' for owners-but-no-commits. No filler sections. - MAX_OWNERS blast radius: sole consumer is Owners.jsx (+ tests); the core.js:256 mention is a comment and stays accurate. Nothing else breaks. - Re-rank fallback coherent: Owners.jsx:177-179 gates orderOwnersByActivity on hasKnownActivity (server order kept on first paint) in the same total order the server uses, refining shown sections only — no fight with server order. - Laws 1/7/8/12: no new deps (4-file diff, no package.json); loading fallback + independent per-owner sections (no silent spinner); page+lib change only, no core/registry/seam changes; 12_web_ui.md Data/Order/Caps updated with a FIXED(#295) entry appended (not rewritten) that declares the repo-total gap. - Dark/light N/A (no style changes; existing muted/divide classes reused). Doc entries accurate. TESTS (scratch worktree, web/node_modules symlinked read-only from the main checkout — main files untouched): - node --test web/test/unit/*.test.js: 586/586 pass (~271s wall; the suite is slow, not hung — earlier 180s tool cap just cut it off). - owners.test.js: 23/23 (activeOwnerNames order/filter/edges, filter-then-top-5-then-uncapped-total composition, fewer-than-5, 1+5=6 cold-fetch budget). - vite build: green (1.97s). NO CODE PUSHED: nothing defective found. One cosmetic nit left alone to avoid churn: an empty instance reads 'Home to 0 owners — showing the most active below.' directly above 'no repositories yet' — slightly awkward, fully honest. MERGE RECOMMENDATION: ready to merge. #295's top-5 + owner-total items are met; its repo-total item moves to #307. If #295 should stay open until #307 lands, retarget the PR's 'Fixes #295' keyword before merging.
Author
Owner

Fixed by PR #306 (review clean; top-5 + owner total verified; repo totals continue in #307), merged. Closing.

Fixed by PR #306 (review clean; top-5 + owner total verified; repo totals continue in #307), merged. Closing.
crueber added this to the v1 milestone 2026-09-10 22:27:06 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#295
No description provided.