Community social features: public profiles, follows, stars, timeline, avatars - #795
Conversation
…ar caching, timeline limits, hidden packages, unfollow oracle, backfill updated_at
|
Important Review skippedToo many files! This PR contains 145 files, which is 45 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (145)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…iles-follows-timeline-1919 # Conflicts: # packages/worker/client/routes/account.tsx # packages/worker/src/app/handlers/account-profile.node.test.ts # packages/worker/src/app/handlers/account-profile.ts
|
🔎 Preview deployed: https://kody-pr-795.kody-a99.workers.dev Worker: Mocks:
|
…ble-id profile lookup
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7b588d9. Configure here.
|
|
||
| const items = chunkResults.flat() | ||
| items.sort(compareActivityItems) | ||
| return items.slice(0, input.limit) |
There was a problem hiding this comment.
Timeline merge drops global events
Medium Severity
listCommunityActivityForActors splits followees into SQL chunks and applies the final timeline limit separately on each chunk’s stored, fork, and star queries before merging. Recent activity from one chunk can be omitted from the merged feed even when it should rank above older items returned from other chunks, so /timeline and community_timeline can show an incomplete or wrongly ordered feed for users who follow many accounts.
Reviewed by Cursor Bugbot for commit 7b588d9. Configure here.
| }) | ||
| } catch (error) { | ||
| if (error instanceof CommunityActionError) { | ||
| return jsonResponse({ ok: false, error: error.message }, 400) |
There was a problem hiding this comment.
Profile save not atomic with rename
Medium Severity
A single profile POST can rename the username and then update community profile fields. Username scope work runs first and commits; if updateCommunityProfile fails afterward, the handler returns an error but leaves the new username in place without applying the requested display name, bio, or visibility changes.
Reviewed by Cursor Bugbot for commit 7b588d9. Configure here.


Summary
Adds the community social layer:
/@username— display name, bio, avatar, join date, follower/following counts, and a searchable list of the user's public packages. A saved package is public when itspackage.jsonlacks"private": true(projected tosaved_packages.is_private; new saves still injectprivate: trueby default). Community-published packages carry a Community badge with a Fork link; public-but-unpublished packages show metadata only (name, kody id, description, tags, updated date) — never README or source, and no forking.user_follows), and/timelineshows followees' public activity chronologically. Publish/update events are stored incommunity_activity_events(backfilled from existing listings'published_at); fork and star events are derived at read time, so a fork disappears from timelines the moment the forked copy goes private, and unstarring removes the star entry. Ratings never appear in timelines.community_stars), distinct from the existing 1–5 ratings. Star counts on listing cards/detail/OG image, public stargazer lists (public profiles only), and an/account/starspage with unstar.COMMUNITY_ASSETSR2 bucket, served immutable at/profiles/:username/avatar/:cacheKey, shown on profiles, timelines, stargazer lists, and profile OG images./profiles/:username/og.png(avatar or initial placeholder, bio, follower/package/listing stats) plus star count added to the community listing OG stats row; profile pages emit full OG/Twitter meta tags.community_profile_get,community_profile_update,community_follow,community_unfollow,community_timeline,community_star,community_unstar,community_starred_list; additivestar_count,stargazers,owner_username,owner_profile_url, and avatar URL fields on existing outputs (owner_anonymousuntouched).Merged with
main's username-change package-scope work (#792 era): profile-field saves compose with the username rename flow, and an unchanged username is now a no-op so accounts holding grandfathered/reserved usernames can still edit display name, bio, and visibility. Migrations renumbered to0068/0069aftermaintook0065–0067.Decision that deserves explicit sign-off
Existing
saved_packagesrows are migrated withis_private = 1(safe default). Privacy is recomputed frompackage.jsonon every package re-save, and the guardedPOST /__maintenance/backfill-package-privacyendpoint recomputes it for all existing packages. Because pre-existing packages never had aprivatefield, running the backfill makes the metadata (name, kody id, description, tags) of most existing packages visible on their owners' public profiles. That matches the agreed rule ("noprivate: true⇒ public") but is retroactive — run the backfill deliberately.Hardening applied after independent review
Export redacts other users' stable ids; public display names never fall back to email local parts; private-profile avatars serve
Cache-Control: private, no-store; hidden packages are excluded from public profiles andsearch_textis not publicly searchable;community_unfollowno longer distinguishes unknown vs private profiles; follow cap aligned with the 2000-followee timeline bound and timeline chunks query concurrently; the privacy backfill no longer bumpsupdated_at.Testing
npm run validatefully green post-merge (format, lint, typecheck, 990+ unit tests, 18 Playwright E2E including a newcommunity-socialspec and profile OG coverage, MCP E2E, primitives check). One local-only e2e hiccup was stale.wrangler/state/e2efrom the migration renumbering; fresh state (as in CI) passes.OG image samples:
System recap — adds a new primitive (high risk)
Mode: recap · Base:
main@9aede048· Head:56ecdb7dClassification: adds — new
community-socialprimitive (profiles, follows, stars, activity timeline); extendscommunity-listings,saved-packages,d1-app-db,community-assets-r2,account-export, andmcp-serveroutputs; composesapp-uiwiring.Primitives touched
community-social0068,0069,social-repo/social-service)community-listingssaved-packagesis_privateprojection frompackage.json, privacy backfill endpointd1-app-dbusers.display_name/bio/profile_visibility/avatar_key, 3 new tablescommunity-assets-r2user-avatars/<user>/<hash>objectsaccount-exportmcp-servercommunity_*capabilities, additive output fieldsapp-ui/@username,/timeline,/account/stars, account profile/avatar settingsSystem map
Profile, timeline, and star traffic flows from new app routes through the community-social service into D1, with avatars in R2 and the same service backing the new MCP capabilities.
Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).
Invariants
community-listing-isolation: preserved — profile package lists expose projected metadata only; snapshots/README stay listing-gated.community-social-privacyadded to the map: private profiles/packages never serialize cross-user; public payload mappers rebuild objects field-by-field so stable user ids, emails, and internal package ids never leave the server.