Files
privatecloud builder 95bef8bf7b
CI / build (push) Successful in 2m0s
Build hn-reader: keyboard-driven Hacker News reader
Vite/React + shadcn/ui frontend with Superhuman-style keyboard nav
(j/k/o/v/e/s/u, feed switching, localStorage caching for instant
loads) backed by an Express API that polls the HN Firebase API into
Postgres and serves cached feeds/comments. Instrumented with
OpenTelemetry (traces/metrics/logs via OTLP/HTTP).
2026-07-25 12:16:08 +00:00

2.7 KiB

HN Reader

A lightweight, keyboard-driven Hacker News reader. Navigate like Superhuman/Linear: j/k to move, o/Enter to read, e to archive, s to star, numbers to switch feeds. Feed data is cached in localStorage for instant loads, then revalidated in the background.

Press ? in the app for the full shortcut list.

Stack

  • Frontend: Vite + React + TypeScript, shadcn/ui (Radix primitives, Tailwind v4, Nova preset)
  • Backend: Express (server/), polls the HN Firebase API every 5 minutes and caches stories in Postgres
  • Database: Postgres 16 (Docker), schema in scripts/init.sql
  • Observability: OpenTelemetry traces/metrics/logs shipped via OTLP/HTTP

Architecture

src/            React SPA (feeds, keyboard nav, comments dialog)
server/         Express API + HN sync job + OTel bootstrap
scripts/        DB schema, backup script
docker-compose.yml   Postgres container for this app
.gitea/workflows/    CI: lint, typecheck, build

The API caches HN's top/new/best/ask/show/job feeds (100 items each) in Postgres so the frontend never talks to Hacker News directly. Per-story read/starred/archived state lives in story_state and is returned inline with each feed response.

Local development

cp .env.example .env        # fill in POSTGRES_PASSWORD
docker compose up -d        # start Postgres
npm install
npm run dev:server          # API on :3012 (loaded from .env)
npm run dev                 # Vite dev server on :3012, proxies /api

Production

Runs as the app-54d6877c systemd unit: builds are not automated on deploy, so after pulling changes run npm run build then restart the service (sudo systemctl restart app-54d6877c). The compiled SPA is served as static files by the same Express process that serves /api, on port 3012.

Nightly Postgres backups run via the app-54d6877c-backup.timer systemd timer (scripts/backup.sh), keeping 7 days of pg_dump snapshots in ~/backups.

Design system

Built with shadcn/ui (components.json, Radix base, Nova preset). Components live under src/components/ui and are added with npx shadcn@latest add <component> — nothing here is hand-rolled, so swapping in a custom registry later (e.g. a privatecloud registry) is a matter of updating components.json's registries field and re-running add against it.

Observability

The API sends traces, metrics, and logs to an OTLP/HTTP collector at OTEL_EXPORTER_OTLP_ENDPOINT (defaults to http://localhost:4318), instrumenting HTTP, Express, and Postgres calls automatically via @opentelemetry/auto-instrumentations-node. View it in Grafana at localhost:3400.