Skip to content

feat: trending hashtags and bookmarks - #2076

Merged
karlitschek merged 1 commit into
masterfrom
feat/trends-and-bookmarks
Sep 8, 2026
Merged

feat: trending hashtags and bookmarks#2076
karlitschek merged 1 commit into
masterfrom
feat/trends-and-bookmarks

Conversation

@karlitschek

Copy link
Copy Markdown
Member

Stacks on #2075 (→ #2074#2073). Merge in that order.

Both features were already half-built and completely unreachable from the app.

Trending hashtags

The cron job has been counting hashtag use per window — 1 h, 12 h, 1 d, 3 d, 10 d — into social_hashtag.trend for as long as the table has existed (HashtagService::manageHashtags()), and nothing ever read it.

  • GET /api/v1/trends/tags serves those counts as Mastodon Tag entities (limit, capped at 20; period, defaulting to 1d).
  • The sidebar lists the top five with how often each was used; picking one opens its timeline.

Two deliberate choices worth reviewing:

  • The ordering happens in PHP. The counts live in a JSON column, which no supported database can be asked to sort on portably. The table holds one row per hashtag the instance has ever seen, which is small enough that this is cheaper than a schema migration for it — but it is the thing to revisit first if an instance ever has a very large tag vocabulary.
  • accounts is always 0. Mastodon reports distinct accounts per tag; this app counts uses. Reporting 0 and documenting it beats inventing a number that clients would display as fact.

Bookmarks

Exactly the same story as blocked accounts two PRs ago: the bookmark action worked, /api/v1/bookmarks returned the list, and the word "bookmark" appeared nowhere in src/.

  • A bookmark/remove-bookmark entry in every post's menu, with the filled or outline icon.
  • A Bookmarks timeline in the sidebar.
  • Removing a bookmark while reading that list takes the post off it, the way unliking does on the Liked timeline.
  • A refusal from the server puts the flag back, so the UI never claims a bookmark that isn't stored.

Also

The README still said "Creating own polls is not supported" — three PRs after own polls shipped. Corrected.

Tests

5 service specs (window ordering, the window deciding the order, unused tags excluded, limit honoured, unknown window falling back, quiet instance), 3 controller specs (entity shape, limit cap, readable without a viewer), 4 sidebar specs (listing with counts, opening a tag, quiet instance, unreadable counts staying silent), 4 store specs (bookmark/unbookmark optimism and rollback, plus removal from the bookmarks list). 1847 PHP, 691 vitest, lint clean.

Verified on devel

posted 4 statuses with #nextcloud ×2, #fediverse ×2, #selfhosting
occ social:cache:refresh          → 3 hashtags updated
GET /trends/tags?limit=5          → fediverse(2), nextcloud(2), selfhosting(1)
GET /trends/tags?period=1h&limit=2 → the same two, limit respected
POST /statuses/{id}/bookmark      → bookmarked: true;  GET /bookmarks → [that post]
POST /statuses/{id}/unbookmark    → bookmarked: false; GET /bookmarks → []

🤖 Generated with Claude Code

Both were already half-built and completely unreachable.

The cron job has been counting hashtag use per window — 1h, 12h, 1d,
3d, 10d — into social_hashtag.trend for as long as the table has
existed, and nothing ever read it. GET /api/v1/trends/tags now serves
those counts as Mastodon Tag entities, and the sidebar lists the top
five with how often each was used; picking one opens its timeline.
The ordering happens in PHP because the counts live in a JSON column
that no supported database can be asked to sort on portably, and the
table holds one row per hashtag the instance has ever seen — cheaper
than a schema for it. `accounts` in the history is always 0: this
instance counts uses, not distinct accounts, and saying so beats
inventing a number.

Bookmarks were the same story as blocked accounts: the action worked,
/api/v1/bookmarks returned the list, and the word 'bookmark' did not
appear anywhere in src/. There is now a bookmark entry in every post's
menu and a Bookmarks timeline in the sidebar. Removing a bookmark while
reading that list takes the post off it, and a refusal from the server
puts the flag back rather than leaving the UI lying.

Also corrects the README, which still said creating polls was not
supported three PRs after it shipped.

Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Frank Karlitschek <frank.karlitschek@nextcloud.com>
@karlitschek
karlitschek merged commit 61ac133 into master Sep 8, 2026
45 checks passed
@karlitschek
karlitschek deleted the feat/trends-and-bookmarks branch September 8, 2026 15:13
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.

1 participant