Area
Product / UX
Context
ClawHub browse surfaces currently lean on all-time popularity or recency, which means the same established publishers, skills, and plugins can dominate discovery indefinitely.
A proposed improvement is to add period tabs: Weekly, Monthly, and All time.
The immediate UI request is narrow:
/publishers: add the period tabs to the right of the Popular publishers heading.
/skills: add the same period tabs above the listing, beside the existing List/Grid controls, without creating a separate Popular skills section.
/plugins: do the same beside List/Grid, also without adding a separate popular section.
A local code investigation found that the UI placement is straightforward, but the data contract is not yet ready for correct period-based popularity across all three surfaces.
Current implementation facts:
/publishers calls api.publishers.listPublicPage from src/routes/publishers/index.tsx and highlights publishers.slice(0, 3) as Popular publishers.
- Publisher ranking is currently all-time:
comparePublisherListItems ranks by downloads, stars, published count, then name.
- Publisher schema stores all-time denormalized totals:
publishedSkills, publishedPackages, totalDownloads, totalInstalls, totalStars.
/skills uses api.skills.listPublicPageV4 for all-time-ish browse sorts such as downloads, stars, installs, updated, newest, and name.
- Skills already have a weekly trending foundation through
skillDailyStats, skillLeaderboards, and listPublicTrendingPage, but that endpoint is not the paginated general browse contract needed for the /skills listing tabs.
/plugins uses fetchPluginCatalog -> /api/v1/plugins, and backend listPluginsV1Handler merges code-plugin and bundle-plugin results by updatedAt.
packageSearchDigest does not currently include stats fields, and package browse indexes are mostly updated/filter indexes.
packageStatEvents records download/install events but is indexed only by processedAt for processing; package processing increments all-time package stats and does not write daily package stats today.
The core decision: should ClawHub expose period popularity only where it is already correct, or should we first establish a shared temporal popularity foundation across skills, plugins, and publishers?
Goals
- Make discovery more current: users should be able to see what is popular this week or month, not only what won all-time.
- Keep
/publishers, /skills, and /plugins semantically aligned: Weekly and Monthly should mean the same kind of period popularity across surfaces.
- Avoid expensive Convex scans, especially for monthly windows and publisher aggregation.
- Keep all-time browse using existing denormalized totals and indexes where appropriate.
- Preserve stable pagination when leaderboards rebuild while a user is paging.
- Avoid misleading publisher rankings that count skills but silently ignore plugins.
- Keep the first UI implementation scoped and testable once the backend contract is ready.
Non-goals
- This RFC does not require changing the visual design of cards or browse pages beyond period tabs.
- This RFC does not propose a separate
Popular skills or Popular plugins section.
- This RFC does not define final scoring weights for every metric beyond recommending that the first version stay simple and explainable.
- This RFC does not require replacing existing all-time sorting indexes with leaderboard snapshots.
- This RFC does not decide analytics dashboards or private admin reporting.
Proposal
Treat this as an RFC before implementing the visible tabs.
Recommended direction:
- Do not ship
/publishers Weekly / Monthly tabs backed only by skillDailyStats unless the UI explicitly labels the result as skill-only activity. Calling that Popular publishers would be misleading for publishers whose primary activity is plugins.
- Keep
All time backed by current denormalized totals and indexes. Do not snapshot all-time into temporal leaderboard tables unless there is a separate reason later.
- Add daily package stats before exposing plugin or publisher period popularity:
packageDailyStats: { packageId, day, downloads, installs, updatedAt }
- indexes similar to
skillDailyStats, at minimum by_package_day and by_day
- update
processPackageStatEventsInternal to write daily rows in addition to all-time totals
- add a cursor-based backfill path from existing
packageStatEvents if the event history is retained and usable
- Add publisher-level daily aggregation so publisher period ranking does not have to scan all owned skills/packages across every requested window:
publisherDailyStats: { publisherId, day, skillDownloads, packageDownloads, downloads, installs, updatedAt }
- use ownership at event/day attribution time, not current ownership at query time, or explicitly choose and document a different rule
- Use separate leaderboard tables per entity family, but share TypeScript helpers/row shape where useful:
- extend or version
skillLeaderboards for weekly/monthly paginated browse
- add
packageLeaderboards or plugin-specific leaderboard storage
- add
publisherLeaderboards
- Use snapshot-pinned pagination for temporal leaderboards:
- first page resolves a
snapshotId or snapshotAt
- subsequent cursors include the snapshot identity and rank/cursor position
- old snapshots can be garbage-collected after a short retention window
- Define period semantics simply for V1:
Weekly = trailing 7 completed days
Monthly = trailing 30 completed days
- avoid calendar-week/calendar-month semantics for the first implementation unless there is a strong product reason
- UI behavior:
/publishers: tabs sit beside Popular publishers; top highlight cards and list are derived from the same selected period result.
/skills and /plugins: tabs sit beside List/Grid controls above the existing listing.
- switching period resets pagination/cursor.
- empty or low-activity windows should show a clear empty state rather than silently falling back to all-time.
Suggested implementation sequence:
- Foundation PR: add
packageDailyStats, update package stat processing, and add package daily backfill.
- Foundation PR: add
publisherDailyStats or an equivalent bounded daily rollup derived from skill/package daily stats.
- Leaderboard PR: add weekly/monthly leaderboard generation with snapshot-pinned pagination for skills, plugins, and publishers.
- UI PR for
/publishers: add tabs beside Popular publishers, backed by publisher period leaderboards for weekly/monthly and current all-time path for all-time.
- UI PR for
/skills and /plugins: add tabs beside List/Grid using the same period contract.
Examples
Good behavior:
- A new publisher whose plugin is heavily downloaded this week can appear in
Weekly publisher results even if they have low all-time totals.
- A skill with strong activity over the last 7 completed days appears in
Weekly skills, while All time remains ordered by existing downloads/stars/installs sort behavior.
- A user switches from
Weekly to Monthly; the page resets to the first page and does not reuse an old cursor.
- A leaderboard rebuild happens between page 1 and page 2; the user's cursor remains pinned to the original snapshot, so there are no duplicates or skipped items.
Bad behavior to avoid:
/publishers Weekly counts only skills while presenting the result as overall publisher popularity.
Weekly silently falls back to all-time when there is not enough activity.
- Monthly leaderboards query raw stat event tables directly on every browse request.
- A single mixed generic leaderboard table grows to include the union of all fields needed by skills, plugins, and publishers, increasing read size and coupling unrelated rebuilds.
Edge cases:
- Ownership transfer during a week: the RFC should decide whether daily stats remain attributed to the owner at event time or move retroactively with current ownership. Event-time attribution is likely less surprising for a time-window leaderboard.
- Low activity: decide whether to show low-count leaders, require a minimum score threshold, or show an empty state.
- Plugins with both code-plugin and bundle-plugin families: define whether plugin popularity ranks package entities directly and ensure there is no double-counting through mixed catalog behavior.
User impact
Users get fresher discovery surfaces and can find currently active publishers, skills, and plugins instead of only all-time winners.
Publishers get a fairer path to visibility if recent work is popular, but only if the metric counts both skills and plugins honestly.
Maintainers get a clearer implementation boundary: UI tabs should not ship until the backing data contract is correct enough and tested.
API/CLI consumers are not directly affected unless public API sort/period parameters are later exposed; if they are, those parameters should follow the same period semantics.
Open questions
- Should
Weekly / Monthly rank by downloads only, installs only, or a simple weighted score? For V1, downloads may be the most explainable baseline, but publisher all-time ranking currently also considers stars and published count as tie-breakers.
- Should publisher period popularity include stars, or only activity events such as downloads/installs?
- Should low-activity weekly/monthly windows have a minimum threshold before showing results?
- Should ownership attribution be event-time, day-rollup-time, or current-owner-at-query-time?
- Do we want separate
pluginLeaderboards for code/bundle plugin families, or one package/plugin leaderboard filtered by family?
- Is backfilling
packageDailyStats from existing packageStatEvents worth doing before launch, or is a forward-only warm-up acceptable?
- Should
/skills replace the existing ad hoc listPublicTrendingPage behavior with the new paginated period contract once available?
Validation plan
Backend tests:
- Daily package stat processing writes both all-time totals and
packageDailyStats rows.
- Package daily backfill is idempotent and cursor-based.
- Publisher daily aggregation includes both skill and package activity.
- Weekly and monthly leaderboards read daily stats day-by-day, not through unbounded full scans.
- Snapshot-pinned pagination does not duplicate or skip items after a rebuild.
- All-time paths continue using existing denormalized totals/indexes.
- Ownership-transfer behavior is explicitly tested according to the chosen attribution rule.
Frontend tests:
/publishers period tab changes the query/search state and resets pagination.
/publishers highlight cards derive from the selected period result.
/skills and /plugins period tabs live beside List/Grid and reset cursor/page state.
- Empty/low-activity weekly/monthly states do not silently display all-time results.
Manual checks:
- Verify
/publishers, /skills, and /plugins on desktop and mobile.
- Verify list/grid controls still work with each period.
- Verify query/filter interactions preserve the selected period only where semantically valid.
Area
Product / UX
Context
ClawHub browse surfaces currently lean on all-time popularity or recency, which means the same established publishers, skills, and plugins can dominate discovery indefinitely.
A proposed improvement is to add period tabs:
Weekly,Monthly, andAll time.The immediate UI request is narrow:
/publishers: add the period tabs to the right of thePopular publishersheading./skills: add the same period tabs above the listing, beside the existing List/Grid controls, without creating a separatePopular skillssection./plugins: do the same beside List/Grid, also without adding a separate popular section.A local code investigation found that the UI placement is straightforward, but the data contract is not yet ready for correct period-based popularity across all three surfaces.
Current implementation facts:
/publisherscallsapi.publishers.listPublicPagefromsrc/routes/publishers/index.tsxand highlightspublishers.slice(0, 3)asPopular publishers.comparePublisherListItemsranks by downloads, stars, published count, then name.publishedSkills,publishedPackages,totalDownloads,totalInstalls,totalStars./skillsusesapi.skills.listPublicPageV4for all-time-ish browse sorts such as downloads, stars, installs, updated, newest, and name.skillDailyStats,skillLeaderboards, andlistPublicTrendingPage, but that endpoint is not the paginated general browse contract needed for the/skillslisting tabs./pluginsusesfetchPluginCatalog->/api/v1/plugins, and backendlistPluginsV1Handlermerges code-plugin and bundle-plugin results byupdatedAt.packageSearchDigestdoes not currently include stats fields, and package browse indexes are mostly updated/filter indexes.packageStatEventsrecords download/install events but is indexed only byprocessedAtfor processing; package processing increments all-time package stats and does not write daily package stats today.The core decision: should ClawHub expose period popularity only where it is already correct, or should we first establish a shared temporal popularity foundation across skills, plugins, and publishers?
Goals
/publishers,/skills, and/pluginssemantically aligned:WeeklyandMonthlyshould mean the same kind of period popularity across surfaces.Non-goals
Popular skillsorPopular pluginssection.Proposal
Treat this as an RFC before implementing the visible tabs.
Recommended direction:
/publishersWeekly/Monthlytabs backed only byskillDailyStatsunless the UI explicitly labels the result as skill-only activity. Calling thatPopular publisherswould be misleading for publishers whose primary activity is plugins.All timebacked by current denormalized totals and indexes. Do not snapshot all-time into temporal leaderboard tables unless there is a separate reason later.packageDailyStats:{ packageId, day, downloads, installs, updatedAt }skillDailyStats, at minimumby_package_dayandby_dayprocessPackageStatEventsInternalto write daily rows in addition to all-time totalspackageStatEventsif the event history is retained and usablepublisherDailyStats:{ publisherId, day, skillDownloads, packageDownloads, downloads, installs, updatedAt }skillLeaderboardsfor weekly/monthly paginated browsepackageLeaderboardsor plugin-specific leaderboard storagepublisherLeaderboardssnapshotIdorsnapshotAtWeekly= trailing 7 completed daysMonthly= trailing 30 completed days/publishers: tabs sit besidePopular publishers; top highlight cards and list are derived from the same selected period result./skillsand/plugins: tabs sit beside List/Grid controls above the existing listing.Suggested implementation sequence:
packageDailyStats, update package stat processing, and add package daily backfill.publisherDailyStatsor an equivalent bounded daily rollup derived from skill/package daily stats./publishers: add tabs besidePopular publishers, backed by publisher period leaderboards for weekly/monthly and current all-time path for all-time./skillsand/plugins: add tabs beside List/Grid using the same period contract.Examples
Good behavior:
Weeklypublisher results even if they have low all-time totals.Weeklyskills, whileAll timeremains ordered by existing downloads/stars/installs sort behavior.WeeklytoMonthly; the page resets to the first page and does not reuse an old cursor.Bad behavior to avoid:
/publishersWeeklycounts only skills while presenting the result as overall publisher popularity.Weeklysilently falls back to all-time when there is not enough activity.Edge cases:
User impact
Users get fresher discovery surfaces and can find currently active publishers, skills, and plugins instead of only all-time winners.
Publishers get a fairer path to visibility if recent work is popular, but only if the metric counts both skills and plugins honestly.
Maintainers get a clearer implementation boundary: UI tabs should not ship until the backing data contract is correct enough and tested.
API/CLI consumers are not directly affected unless public API sort/period parameters are later exposed; if they are, those parameters should follow the same period semantics.
Open questions
Weekly/Monthlyrank by downloads only, installs only, or a simple weighted score? For V1, downloads may be the most explainable baseline, but publisher all-time ranking currently also considers stars and published count as tie-breakers.pluginLeaderboardsfor code/bundle plugin families, or one package/plugin leaderboard filtered by family?packageDailyStatsfrom existingpackageStatEventsworth doing before launch, or is a forward-only warm-up acceptable?/skillsreplace the existing ad hoclistPublicTrendingPagebehavior with the new paginated period contract once available?Validation plan
Backend tests:
packageDailyStatsrows.Frontend tests:
/publishersperiod tab changes the query/search state and resets pagination./publishershighlight cards derive from the selected period result./skillsand/pluginsperiod tabs live beside List/Grid and reset cursor/page state.Manual checks:
/publishers,/skills, and/pluginson desktop and mobile.