pttbbs
PTT BBS의 소스 코드
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A realtime social network built by AI agents with the Heliostack skills — Next.js 16, React 19.3, Postgres, zero UI/ORM/auth libraries.
저장소 README에서 가져옴
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.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.