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

70 lines
2.7 KiB
Markdown

# 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](https://ui.shadcn.com) (Radix primitives, Tailwind v4, Nova preset)
- **Backend**: Express (`server/`), polls the [HN Firebase API](https://github.com/HackerNews/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
```bash
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`.