README.md
Rama main · réplica de lectura del repositorio original.
# Barback
Home-bar pantry app: inventory shelf, "what can I make tonight," shopping-list
gap analysis, recipe index. The product thesis is **templates, not recipes**.
Barback is a client. The backend is an unmodified, version-pinned
[Bar Assistant](https://github.com/karlomikus/bar-assistant) instance — see
[ADR-001](docs/adr/001-bar-assistant-as-upstream-appliance.md). The visual
identity is **The Back Bar** design system —
[docs/design/the-back-bar.md](docs/design/the-back-bar.md), tokens in
[docs/design/tokens.css](docs/design/tokens.css).
## Architecture
Decisions live in [docs/adr/](docs/adr/):
| ADR | Decision |
|---|---|
| [001](docs/adr/001-bar-assistant-as-upstream-appliance.md) | Bar Assistant as pinned upstream appliance; never forked |
| [002](docs/adr/002-families-and-ratio-data-ownership.md) | Family taxonomy as API tags; ratio templates as typed client data |
| [003](docs/adr/003-client-stack-vite-preact-spa.md) | Vite + Preact + TypeScript strict SPA, TanStack Query |
| [004](docs/adr/004-search-direct-meilisearch-scoped-key.md) | Search direct to Meilisearch with scoped keys |
| [005](docs/adr/005-deployment-single-compose-single-origin.md) | One compose stack; single-origin reverse proxy as end state |
| [006](docs/adr/006-design-system-consumed-as-vendored-delivery.md) | Design system vendored in-repo and imported via `@ds`; changes land as numbered revisions |
| [007](docs/adr/007-installable-client-offline-posture.md) | Installable PWA; shell-cache worker + list snapshot; API never cached; sync deferred |
| [008](docs/adr/008-purchasing-intelligence-standing-orders.md) | Staples are standing orders (auto-queue); app suggestions require a tap; aspiration outranks server rank |
| [009](docs/adr/009-remote-access-cloudflare-tunnel-access.md) | Public at barback.pezza.dev via Cloudflare Tunnel + Access; gated on migration to an always-on host |
| [010](docs/adr/010-agent-governance-index-and-records.md) | `CLAUDE.md` indexes the ADRs; records and the build check change only through a maintainer-merged PR |
| [011](docs/adr/011-test-gate-vitest-playwright.md) | Vitest in `pnpm build`; Playwright against fixtures as `pnpm verify` before every PR; all local, devDependencies only |
## Running the stack
```bash
cp deploy/.env.example deploy/.env # non-secret overrides only; MEILI_MASTER_KEY comes from 1Password
pnpm install && pnpm build:deploy # the web container serves dist/ (ADR-005)
ALLOW_REGISTRATION=true op run --env-file=deploy/.env.tpl -- docker compose -f deploy/docker-compose.yml up -d
./scripts/seed.sh # creates your user while registration is open
op run --env-file=deploy/.env.tpl -- docker compose -f deploy/docker-compose.yml up -d # re-up: registration closes
python3 scripts/tag_families.py # family:* tags + assignments (idempotent)
```
`MEILI_MASTER_KEY` lives in 1Password (vault Personal, item **Barback Meilisearch**,
field **master-key**) and reaches compose through `op run`, so it is never written to
disk. A value still set in `deploy/.env` keeps working as a fallback; when both are
present, the `op run` value wins.
Family curation lives in [scripts/family-assignments.json](scripts/family-assignments.json)
(normalized slugs → family tag); edit it and re-run the script. Par-level
staples (bottles the bar should never run out of) are curated in
[src/data/staples.ts](src/data/staples.ts) by canonical ingredient slug —
the file is a standing order: a staple that leaves the shelf is queued to
the shopping list automatically, with a toast and a "staple" note on the
row. The First pours roster (onboarding classics; each pick favorites the
drink and queues its missing bottles) lives in
[src/data/first-pours.ts](src/data/first-pours.ts). A minimal shell-cache
service worker plus a stamped localStorage snapshot keep the shopping list
readable away from the LAN (the store aisle); the API itself is never
cached. For the
uncurated backlog, `python3 scripts/propose_families.py` drafts structural
proposals with reasons to [scripts/family-proposals.json](scripts/family-proposals.json)
— review, move keepers into the assignments file, rerun the tag script.
- App (single-origin front door): http://localhost:8080 — proxies `/bar/` → API,
`/search/` → Meilisearch; set `BARBACK_ORIGIN` in `deploy/.env` for LAN access
- API direct: http://localhost:8000 (OpenAPI docs at `/docs`)
- Salt Rim (upstream admin/reference UI): http://localhost:3000
- Local dev credentials seeded by the script: `admin@example.com` / `password`
## Vintage imports
Public-domain books live as transcribed batches in [scripts/vintage/](scripts/vintage/)
(Thomas 1862, Winter 1884 — period measures modernized, substitutions noted
per-ingredient). Imports are idempotent (duplicates skipped by name) and each
maintains a per-book collection:
```bash
python3 scripts/import_vintage.py scripts/vintage/thomas-1862.json
```
## Client development
```bash
pnpm install
pnpm dev
```
Tests (ADR-011) run locally; there is no CI. Run both before opening a PR:
```bash
pnpm build # check.mjs, tsc, vitest, vite build
pnpm exec playwright install chromium # once per machine
pnpm verify # Playwright journeys against fixtures
```
`pnpm test` runs Vitest in watch mode. `pnpm verify` builds the client in the
single-origin deploy shape and answers every `/bar/` and `/search/` request from
the fake in `e2e/support/fake-bar.ts`, which models Bar Assistant v5.15.3 from
`test/fixtures/`. It needs no Docker, no `op run` and no secrets.
## A note before exposing this to the internet
The compose stack is tuned for a home LAN: the `:8080` front door is the only
port the network sees (API, Meilisearch and Salt Rim bind to loopback),
registration is closed outside first boot, and login is the only write gate —
but nothing terminates TLS. That is the right posture behind your router and
still the wrong one on a public host. If you must expose it, change the seeded
password first, and put real TLS and an auth proxy in front.
## Built on
Barback is a client; the heavy lifting is upstream open source, consumed as
pinned Docker images (ADR-001) and gratefully acknowledged:
- [Bar Assistant](https://github.com/karlomikus/bar-assistant) (MIT, Karlo
Mikuš) — inventory, matching and recipe engine
- [Salt Rim](https://github.com/karlomikus/vue-salt-rim) (MIT, Karlo Mikuš) —
upstream web client, kept alongside as the admin/reference UI
- [Meilisearch](https://github.com/meilisearch/meilisearch) (MIT) — search
- [Preact](https://preactjs.com) + [preact-iso](https://github.com/preactjs/preact-iso),
[TanStack Query](https://tanstack.com/query), [Vite](https://vite.dev),
[TypeScript](https://www.typescriptlang.org) — the client stack
- Libre Caslon Display & Text (SIL OFL) — the system's two voices
- Jerry Thomas' *How to Mix Drinks* (1862) and George Winter's *How to Mix
Drinks — Bar Keepers' Handbook* (1884) — public-domain sources for the
vintage imports
Barback itself is [MIT licensed](LICENSE).