#2216·clawhub

RFC: Period-based popularity for publishers, skills, and plugins

Author: vyctorbrzezowskiCreated May 14, 2026Updated Sep 17, 2026
LabelsP3clawsweeper:no-new-fix-prclawsweeper:needs-maintainer-reviewclawsweeper:needs-product-decisionissue-rating: 🌊 off-meta tidepool

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:

  1. 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.
  2. 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.
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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:

  1. Foundation PR: add packageDailyStats, update package stat processing, and add package daily backfill.
  2. Foundation PR: add publisherDailyStats or an equivalent bounded daily rollup derived from skill/package daily stats.
  3. Leaderboard PR: add weekly/monthly leaderboard generation with snapshot-pinned pagination for skills, plugins, and publishers.
  4. 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.
  5. 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.