cada1ef6d7
## Problem The menu's GitHub star and Discord member counts almost always render the hard-coded snapshot (49.6K / 6.6K, frozen June 2026), not live numbers. The render-time fetches run unauthenticated from Cloudflare Workers, whose egress IPs are shared across tenants; GitHub's unauthenticated quota is 60 req/hr per IP, so the call is effectively always rate-limited. Live prod today shows the frozen 49.6K GitHub count next to a live Discord count, confirming only GitHub is affected. ## Change - GitHub fetch sends `Authorization: Bearer $GITHUB_STATS_TOKEN` when the env var is set, moving it onto its own 5,000 req/hr quota. Discord keeps the public invite endpoint, which works fine from Cloudflare's IPs. - The hard-coded fallback is deleted rather than refreshed. `CommunityStats` fields are now `number | null`, resolved live -> last-good -> null, and the menu renders an icon-only chip when a count is genuinely unavailable. A fake number can never ship. - Last-good values persist in the worker's existing OpenNext R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`, key `community-stats/latest.json` outside the `incremental-cache/` prefix), so a third-party outage shows the numbers from the previous refresh. No new infrastructure. - Revalidation tightens from 1h to 15min (~4 GitHub calls/hour, shared cache entry across pages). - `MenuSocial`/`MenuDrawer` import `formatCompactCount` from its module directly: the community barrel now re-exports server-only code, and a client value-import would pull `getCloudflareContext` into the client bundle. ## Rollout - twentyhq/twenty-infra#795 passes the built-in Actions token at build time so prerendered pages ship with a real star count from the first request after deploy. - One manual step: set the `GITHUB_STATS_TOKEN` secret (fine-grained PAT, public read-only, no permissions) on the twenty-website-dev and twenty-website-prod workers. Until it exists, behavior degrades to today's minus the fake numbers. ## Tests 5 unit tests cover the resolution ladder: live wins, cache fills a failed fetch, null on cold-cache failure, both-fail serves cache without overwriting, nothing written when nothing succeeded. Verified against the dev server: menu renders live 53.3K / 6.9K.