Halo is a realtime social network built with the Heliostack skills.
SocialTypeScript · CSSGitHub @heliostack-devOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
A realtime social network built by AI agents with the Heliostack skills — Next.js 16, React 19.3, Postgres, zero UI/ORM/auth libraries.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A realtime social network, built by AI agents with the Heliostack skills.
Live demo → heliostack-halo.vercel.app · browse signed-out, or sign in as demo / halo-demo
Halo tests one claim: an agent that follows a small set of well-made skills can build a product-grade app on the platform alone. Timelines, threads, reposts and quotes, follows, grouped notifications, direct messages, search, trends and seven themes — in ~5,400 lines of TypeScript with five runtime dependencies.Read the full README ↗A realtime social network, built by AI agents with the Heliostack skills.
Live demo → heliostack-halo.vercel.app · browse signed-out, or sign in as demo / halo-demo
Halo tests one claim: an agent that follows a small set of well-made skills can build a product-grade app on the platform alone. Timelines, threads, reposts and quotes, follows, grouped notifications, direct messages, search, trends and seven themes — in ~5,400 lines of TypeScript with five runtime dependencies.
cacheComponents: true + partialPrefetching: true. Every page is partially prerendered (◐ in the build output): the shell — sidebar, header, tabs, trends — is static HTML from the CDN, and per-user parts stream in behind <Suspense>.
| Data | Treatment | Example |
|---|---|---|
| Same for everyone, minutes-stale OK | 'use cache' + cacheLife('minutes') + cacheTag(...) | trends, profile headers, hashtag counts |
| Personalised or must be fresh | uncached, <Suspense> as deep as possible | feeds (viewer's liked state), unread badges, composer |
| URL-dependent | params / searchParams promises passed down, awaited inside a boundary | /login?next=, /explore?q= |
Layouts never await request data, so navigating between pages is instant. Cache tags are built in one place (src/server/cache-tags.ts) so writers and readers can't drift.
src/features/*/server/repo.ts) are pure (db, …args) functions with no Next.js imports, which is what makes them testable against a real database.hydrate() turns them into full cards — author, counts, the viewer's like/repost/bookmark state, reply target, reposter — in a single statement ordered by unnest(...) with ordinality, plus one batched query for quoted posts. No N+1.repost_of_id), so the home timeline is one id-cursor query. A post reposted by several people you follow appears once (distinct on (coalesce(repost_of_id, id))).(1 + likes + 2·reposts + replies + 2·quotes) / (hours + 2)^1.4 with an offset cursor; the client dedupes across pages.db/migrations/0001_init.sql) — likes, reposts, replies and followers can never drift from the rows.tsvector with the simple config, so Korean and code work) plus prefix search on handles and names.Every Server Action follows the same steps — createPostAction is the reference:
requireViewer() → zod parse → repo (authorisation inside the SQL) → updateTag / refresh() → publish realtime event → ActionState
Likes, reposts, bookmarks and follows are optimistic: useOptimistic layered over locally confirmed state, so the UI flips instantly and rolls back on failure (post-actions.tsx). Every mutation is idempotent (on conflict do nothing), so double clicks are harmless.
flowchart LR
A["Server Action"] -->|"pg_notify · ids only"| DB[("Postgres")]
DB -->|"LISTEN · one per instance"| S["/api/events · SSE"]
S -->|"routed per viewer"| B["EventSource"]
B --> U["badge +1 · Show N posts · chat refresh"]
src/server/realtime)./api/events filters per viewer (posts from people you follow, your notifications, your conversations), sends a heartbeat every 20 s and closes itself before the serverless time limit; EventSource reconnects. Signed-out visitors get 204, which tells EventSource to stop.NOTIFY can't fire.scrypt from node:crypto for passwords; 256-bit random session tokens in an httpOnly cookie, stored in the database only as HMAC(SESSION_SECRET, token); sliding 30-day expiry (src/server/auth). proxy.ts does optimistic redirects from cookie presence only — real checks happen next to the data. The timeline, profiles, posts and search are public; there's no landing page.
design-system.json by the ds-init theme engine: OKLCH colours with light-dark(), 7 WCAG-checked themes, a 12/14/16 px type scale.src/ui built on the platform: <dialog> modals, Popover API menus positioned with CSS anchor positioning, @starting-style animations, and content-visibility: auto instead of a virtualization library./compose/post is intercepted by @modal/(.)compose/post and opens as a dialog over the current page; a direct visit still works.design/check-tokens.mjs) fails any raw colour or spacing value, and any CSS file that doesn't declare the cascade-layer order on line 1.node:test runs the TypeScript sources directly. useTestDb() boots a fresh in-memory Postgres (PGlite, WASM) per test file behind the wire protocol, so tests use the same driver and migrations as production — no mocks, no Docker. 26 tests cover threads, idempotent toggles, pagination edges, repost dedupe, authorisation, notification grouping and text parsing.
| Feature | Notes | Code |
|---|---|---|
| Timelines | ranked For you, chronological Following, infinite scroll via a Server Function, live "Show N new posts" | posts/ |
| Threads | ancestors via a recursive CTE, focused post, replies ranked by likes | status/[postId] |
| Profiles | cached header per handle, relationship streamed separately, posts / replies / likes tabs | profiles/ |
| Notifications | likes, reposts and follows grouped per post ("Sam and 10 others…"), read state synced across tabs | notifications/group.ts |
| Messages | 1:1 DMs, membership checked in SQL, optimistic send, IME-safe Enter to send | messages/ |
| Explore | full-text posts, people search, trends, hashtag pages | explore/ |
| Settings | theme + light / dark / system switcher (applied pre-paint, no flash), profile editing | settings/ |
Requires Node 24+ and pnpm. No Docker — local Postgres runs as WASM.
pnpm install
cp .env.example .env.local # set SESSION_SECRET (command in the file)
pnpm dev:db # Postgres (PGlite) on :5432 — keep it running
pnpm db:migrate && pnpm db:seed # 40 users, ~660 posts, likes, follows, DMs
pnpm dev # http://localhost:3000
pnpm verify # typecheck (typed routes) → ds:check → test → build
runtime next · react · react-dom · postgres · zod
dev typescript · @types/* · @electric-sql/pglite · @electric-sql/pglite-socket
Vercel + Neon (Vercel Marketplace). The vercel-build script runs migrations over the direct connection before next build.
| Variable | Purpose |
|---|---|
DATABASE_URL | pooled connection for queries (set by the Neon integration) |
DATABASE_URL_UNPOOLED | direct connection — migrations and cross-instance realtime (LISTEN) |
SESSION_SECRET | 32+ random bytes; rotating it signs everyone out |
Full walkthrough: the deploy-vercel skill.