CI / build (push) Successful in 2m0s
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).
70 lines
2.7 KiB
Markdown
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`.
|