feat(web): add a feed of recent publications to the home page - #6
Conversation
The home page ranked packages by downloads only, so nothing on it said what had gone up lately. It now opens with the nine most recent publications, served by a new `/webapi/recent`. A feed of publications rather than of packages: a package released twice is two entries, and each names the version that actually went up and links to that version's page. Ordering packages by `updatedAt` could say neither — the package list carries a package's highest stable version, so a fix released on an older line would sit next to a fresh timestamp under a version number that had not moved. Nothing new is written on publish: the feed is aggregated over the stored versions, ordered by the date each already carries. Capped at a hundred entries, since unlike a page of the package list its work grows with the whole repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces a new public endpoint and a new MongoDB aggregation spanning server, shared API models, and web UI, and the aggregation's ordering logic has no integration test coverage, so final human review is warranted.
Pull request overview
This PR adds a "Recently published" feed to the home page of the in_pub private Dart pub server. The home page previously showed only packages ranked by popularity; it now leads with a feed of the most recent publications (one entry per published version, newest first), rendered above the existing "Top Dart packages" block. The feed is served by a new GET /webapi/recent endpoint backed by a MongoDB $unwind/$sort/$limit aggregation over stored versions, so nothing new is written on publish. The two home lists are fetched concurrently and each degrades independently if the server fails to answer for it.
Changes:
- New
/webapi/recentendpoint plusMetaStore.queryRecentPublications({size})(Mongo aggregation), capped at 100 and defaulting to 10. - New shared API models (
RecentApi,RecentApiPublication) and internalUnpubRecentPublication, with generated JSON code. - Web UI: home page renders both lists (recent + top) via guarded concurrent fetches; each list is omitted if it can't be loaded.
File summaries
| File | Description |
|---|---|
| unpub/lib/src/app.dart | Adds getRecentPublications handler with size clamping and maps store results to RecentApi. |
| unpub/lib/src/app.g.dart | Registers the GET /webapi/recent route. |
| unpub/lib/src/meta_store.dart | Declares abstract queryRecentPublications({size}). |
| unpub/lib/src/mongo_store.dart | Implements the feed via $unwind/$sort/$limit aggregation. |
| unpub/lib/src/models.dart | Adds internal UnpubRecentPublication model. |
| unpub/lib/unpub_api/lib/models.dart | Adds shared RecentApi/RecentApiPublication API models. |
| unpub/lib/unpub_api/lib/models.g.dart | Generated JSON (de)serialization for the new models. |
| unpub_web/lib/app_service.dart | Adds fetchRecentPublications. |
| unpub_web/lib/src/home_component.dart | Fetches both lists concurrently, guards failures, adds publication URL/date helpers. |
| unpub_web/lib/src/home_component.html | Renders the recent feed above the top-packages list, each guarded by null checks. |
| unpub/test/recent_publications_test.dart | New endpoint tests (ordering, version naming, size passthrough/cap) via a fake store. |
| unpub/test/*.dart (7 fakes) | Add the new override to existing MetaStore test fakes. |
| unpub/CHANGELOG.md | Documents the feature under 3.6.0. |
Review details
Files not reviewed (2)
- unpub/lib/src/app.g.dart: Generated file
- unpub/lib/unpub_api/lib/models.g.dart: Generated file
- Files reviewed: 17/20 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| final rows = await db.collection(packageCollection).aggregateToStream([ | ||
| {r'$unwind': r'$versions'}, | ||
| { | ||
| r'$sort': {'versions.createdAt': -1} | ||
| }, | ||
| {r'$limit': size}, | ||
| { | ||
| r'$project': {'name': 1, 'versions': 1} | ||
| }, | ||
| ]).toList(); |
There was a problem hiding this comment.
Fair point — covered in 67dde1f, as a recent publications group in unpub/test/unpub_test.dart, against the real database alongside the other MongoStore cases.
One package holds 2.0.0 published on the 6th, 1.9.1 on the 8th (a fix on the older line, so the newest publication is not the newest version) and 1.9.0 back in August, with a second package in between; the feed is expected as 1.9.1, 1.0.9, 2.0.0, 1.9.0. Two more cases assert that an entry carries the version that was published together with its date, and that size cuts the feed at the newest end.
The rows are written straight into the collection rather than published through the API: the ordering only means anything when the publication dates are days apart and out of version order, which publishing here and now cannot produce.
Checked that the cases actually bite by flipping the sort direction to 1 — all three fail.
The endpoint's tests run against a fake store, so nothing exercised the `$unwind`/`$sort`/`$limit` pipeline itself — the piece that fails silently, a flipped sort direction or a wrong field path returning a plausible list in the wrong order. Written straight into the collection rather than published through the API: the ordering only means something when the publication dates are days apart and out of version order, with the newest publication sitting on an older release line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The only conflict was `unpub/lib/src/static/main.dart.js.dart`, the committed web bundle: both sides had rebuilt it, so the text cannot be merged. Resolved by rebuilding it from the merged sources, which is what that file is — the browser tab titles from master and this branch's publication feed are both in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
It spans a new public API endpoint, an unindexed full-collection MongoDB aggregation with operational/scaling implications, and web UI changes whose runtime behavior cannot be fully verified here, warranting human sign-off.
Review details
Files not reviewed (2)
- unpub/lib/src/app.g.dart: Generated file
- unpub/lib/unpub_api/lib/models.g.dart: Generated file
- Files reviewed: 18/21 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Что изменилось
Главная страница ранжировала пакеты только по загрузкам, поэтому по ней нельзя было понять, что publish'илось в последнее время. Теперь она открывается лентой из девяти последних публикаций, над прежним блоком Top packages.
Лента — это события, а не пакеты: пакет, опубликованный дважды, даёт две записи, каждая называет ту версию, что реально вышла, и ведёт на страницу именно этой версии. Сортировка пакетов по
updatedAtне могла сказать ни того, ни другого — список пакетов несёт highest stable версию, так что фикс, выпущенный в старую линию, показывался бы рядом со свежей датой под номером версии, который не менялся.Как устроено
GET /webapi/recent— отдельный эндпоинт, потому что отвечает другой сущностью ({publications: [{name, version, description, publishedAt}]}).MetaStore.queryRecentPublications({size}); в Mongo — агрегация$unwindпоversions→$sortпоversions.createdAt→$limit. Сортировать документы пакетов нельзя в принципе: версии лежат массивом внутри документа, и порядок документов может назвать только одну версию на пакет.sizeограничен сотней: в отличие от страницы списка пакетов, объём работы здесь растёт со всем репозиторием.Проверено
unpub:dart test— 556 тестов зелёные (нужен локальный MongoDB из.dev/docker-compose.yml),dart analyzeчисто.unpub_web:dart analyze— только предсуществующие info проdart:html.Новые тесты —
unpub/test/recent_publications_test.dart: одна запись на версию с сортировкой по дате, запись называет опубликованную версию, проброс и ограничениеsize.