Repository navigation
Conversation
…ng badges, view counters and live sign-out
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 6 of the hub plan: live on everything, and the fun layer. Plan:
.claude/.Plans/as2026-11-07-graphite-hub-phase-6.md.What changed
app/topics.tsis the catalogue of topics the server publishes, the set that patches in place (presence,reading,editing,views,ping,map) andROUTE_TOPICS, the full subscription of every route module; every route's handle istopicsFor('<module>'), withreading:<id>andediting:<id>keyed from the match.server/live-audit.test.tsreadsapp/routes.ts, checks every component route uses the map, fires every topic through the realcreateClientwith each route's union and asserts a frame reaches exactly the routes that depend on it, that no route exceeds the 12-topic cap, and that patched topics never revalidate while the rest do. The cap stays at 12:statsleaves the home page andconsoleleaves home and status (everyconsoleevent is private, so it only ever triggered a revalidation)./liveconnection is a tab;server/presence.tskeeps time-scored Redis sorted sets (presence:tabs,presence:online,presence:reading:<key>,map:countries, all with TTLs), counts inside a 90-second window (10 minutes for the map), publishes only on change, sweeps every 30 seconds and forgets the previous process's tabs on start. Patched topics carry their detail to everyone; the client overlays it on loader data, so counts tick without a revalidation.onAwarenessUpdate,onDisconnect) and is published onediting:<post>when the set of handles changes.PFADDs it into a two-day HyperLogLog. Only a new register writes theviewsrow, bumps the in-memory total and publishesviews. The shown figure is the sum of daily uniques./status: a dot-matrix world rasterised once from Natural Earth's public-domain 110m countries (app/components/kit/map-data.ts, 96 columns, 174 centroids), lit fromCF-IPCountrywhen the peer is in Cloudflare's ranges (or in aDEV_LOGINrun), fading ten minutes after the last tab, named withIntl.DisplayNames./privacygains entries for views, reading now and the map.signOutEveryonepublishessessions; the live module pushes that frame to every member stream regardless of topics and ends the stream, the tab revalidates with no viewer and reconnects as a visitor, Hocuspocus closes every connection; a member removed by the sync gets the same by login. The admin action redirects the owner to/.n here) turns amber and reads "reconnecting" while the stream is down; on reopen the client revalidates once, since only recorded events replay.site.motionandprefers-reduced-motion.bouncedinserver/subscribers.tsreturns its promise andserver/sends.tsawaits it, so a failed publish reaches the job's error capture instead of an empty catch.Migration
server/migrations/008-views.sql, additive:Redis keys added, each with a TTL:
presence:*(2 min),map:countries(10 min),views:salt:<day>andviews:<kind>:<key>:<day>(2 days),limit:views:<hash>(1 min).viewsrows are kept for ever (plan).Checks
npm run typecheck,npx eslint .,npx prettier --check .,npm run build,npm run size(largest pageroutes/editor147.7 KB, every page within 150 KB). Specs run:server/live-audit.test.ts,server/live.test.ts,server/presence.test.ts,server/views.test.ts,server/auth.test.ts,server/sends.test.ts,server/subscribers.test.ts(57 passing).Exit proofs, measured locally
Against Redis on 6390 and the dev server on 3100, with Playwright tabs open; latency is from the write request leaving the sender to the DOM mutation on the open tab, same machine clock:
releasewebhook to/internal/github(GITHUB_WEBHOOK_SECRET)/internal/deploys(DEPLOY_REPORT_SECRET)home.sectionsset from the admin tab; home tabA script fetched a published post 1,000 times and reported a view each time (1,000 × 204): the
viewsrow rose by exactly one for that visitor (the HyperLogLog counted 2: the script and the browser tab). The map lit Egypt, Germany and Japan from streams opened withCF-IPCountry; the pulse read "reconnecting" while the server was down and "live" again on restart; "Amr is editing" appeared when the editor opened and left when it closed; two tabs on a post read "2 reading now".Dependencies
None added.