smooth-app
전 세계 식품의 성분·영양·정보를 자유롭게 협업하고 공개하는 데이터베이스
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Each meal weaves one band across the day. Every food group on the plate is a dyed stripe in a fixed order: veg and fruit in green, protein in red, starch in ochre, dairy in blue. A group that is not there leaves bare cotton. You read balance at a glance, from which stripes a band carries, never from a score, a ring or a countdown. Tap a band and the numbers are there, plainly: grams per food, energy, protein, fibre, sugars, salt and the rest, set against the EU reference intakes.
The week is seven towels hung side by side. The coach reads them with you and asks a question. Dismissing it costs nothing.
| Real numbers, no pressure | Grams and nutrients per plate and per day, as plain values and neutral bars against EU reference intakes, or a daily guide tuned by your optional profile. No streaks, badges, points, countdowns, "calories remaining" or red "over budget" states. Numbers can be hidden in one switch if they feel stressful. |
| Yours, not ours | No account. Your history lives in an encrypted database in your browser, opened by a passphrase only you know. The server serves the app and holds none of your data. |
| Proved, not promised | Every trust claim below names the test that proves it, and a release gate fails the build if a proof goes missing. |
| AI is optional | The no-AI app is complete: type it, describe, paste a recipe, the coach's questions and the week review all run on the device, offline. Add your own key, or use the AI your server's operator offers, when you want photo recognition or a coach that writes. |
| Never fails the log | Offline, no AI, an unknown barcode: the meal still lands, with the closest matches and a quantity you can adjust. |
| One command to run it | bin/ai-nutrition-coach up, or pull the published image from ghcr.io. Hardened container, strict CSP, no outbound need. |
The full tour, screen by screen: docs/features.md.
Run it (Docker on your machine):
git clone https://github.com/mmornati/ai-nutrition-coach.git && cd ai-nutrition-coach
bin/ai-nutrition-coach up # builds and serves http://localhost:8080 (loopback only)
Open http://localhost:8080/ and choose a passphrase. That passphrase creates your encrypted store; it cannot be
recovered, so keep it somewhere safe. Log your first plate with the Log button.
Use it on your phone: the browser store needs HTTPS (or localhost), so serve it with a domain:
AI_NUTRITION_COACH_DOMAIN=plate.example.com bin/ai-nutrition-coach up --tls # automatic HTTPS (Caddy)
AI_NUTRITION_COACH_DOMAIN=plate.example.com bin/ai-nutrition-coach up --traefik # behind your own Traefik
Then open the address on the phone and add it to the home screen before you start logging. Prefer the published
image? AI_NUTRITION_COACH_IMAGE_TAG=ghcr.io/mmornati/ai-nutrition-coach:edge, then docker compose pull && docker compose up -d.
Step by step, including install on iPhone and Android, the first passphrase and the first plate:
docs/getting-started.md. Every deployment option: docs/self-host.md
and docs/pwa.md.
| Getting started | Run it, install it on a phone, first passphrase, first plate, build from source |
| Feature tour | Every screen and what it does |
| How it is secured | The short version of the data, network and AI posture, with links to the detail |
| FAQ | Short answers to the usual questions |
| Your data | The encrypted store, backups, exports, erase, shared devices |
| AI tiers | No AI, your own key, this server's AI; exactly what is sent |
| Telemetry posture | What is on by default (nothing) and every user-enabled path |
| Self-host · PWA | Deploying, HTTPS, Traefik, Authentik, the published image, updates |
| No-Dark-Patterns Standard · Accessibility · |
Every promise below names the test that proves it (Proof).
pnpm check:release-posture (part of pnpm verify) fails if a row has no proof, if a named
file does not exist, is not a *.test.* / *.spec.* file, or contains no real, un-skipped
test. A shell script may stand beside the tests (the container egress test
scripts/smoke_self_host.sh, which the self-host job of the ci workflow runs) only if it exists and a
workflow invokes it. Unit tests (apps/web/src, packages/core/test) and the gate self-tests
(tools/gates, tools/pwa) run in pnpm verify; the Playwright specs (apps/web/e2e,
Chromium and WebKit) run in CI via pnpm e2e, and tools/webkit_smoke/smoke.spec.mjs is the
smoke of the production image (scripts/webkit_smoke.sh, the image job of the ci workflow).
| Promise | Where it lives | Proof |
|---|---|---|
Local encrypted store (sqlite3mc, key derived from your passphrase): the stored file is not plaintext SQLite, a wrong passphrase cannot open it, the key is dropped on lock, erase removes every byte. The browser adapters (OPFS worker, IndexedDB header copy, localStorage) are proved in Chromium and WebKit by the e2e spec, not on real devices: Safari / Firefox are untested | AD-8, ADR-0002 — docs/your-data.md | apps/web/src/storage/sqlite-engine.test.ts, apps/web/src/storage/store-controller.test.ts, apps/web/e2e/storage.spec.ts |
| Exporter: CSV/JSON offline, no account | AD-9 | apps/web/src/export/csv-exporter.test.ts, apps/web/src/export/json-exporter.test.ts, apps/web/src/export/web-export-destination.test.ts |
| Sync is a seam only: in-memory outbox, no transport yet; the UI says "on this device", never "synced" | AD-6 | apps/web/src/sync/sync.test.ts, packages/core/test/sync.test.ts, apps/web/src/components/day/StatusChip.test.tsx |
| Photos sent to your AI endpoint are metadata-stripped, consent is per host (and per photo by default), the key is bound to its host | N1 — docs/ai-tiers.md | apps/web/src/capture/photo-sanitizer.test.ts, apps/web/src/ai/consent.test.ts, apps/web/src/ai/ai-settings.test.ts, apps/web/src/ai/byok-ai-rung.test.ts |
| One-command self-host: hardened static container, strict CSP, no outbound need | FR-11 — docs/self-host.md | tools/pwa/infra.test.mjs, apps/web/e2e/csp.spec.ts, scripts/smoke_self_host.sh |
| Installable, offline-capable web app: our own service worker precaches a checksummed manifest, never intercepts cross-origin or non-GET requests, never caches at run time, and waits for you to tap before a new version takes over. The worker is executed by and, in real browsers, by ; the production-image run is the opt-in |
What is not proven yet (real-device Safari and Chrome, Firefox) is listed under
Known accepted gaps below and in
_bmad-output/review/security-and-quality-review-2026-10-01.md.
Stated, not hidden (details in docs/migration/parity-checklist.md and
docs/your-data.md):
The app speaks English and French (Français). It follows your device's language until you choose:
Settings → Language offers Match my device, English and Français. The choice is stored in
the encrypted local store with the rest of your settings, plus a plain localStorage hint (the
language code only) so the passphrase and unlock screens can show it before the store is open.
"Erase all local data" removes both. Adding a language is described in
CONTRIBUTING.md.
Contributions are welcome. CONTRIBUTING.md has the build, test and smoke commands, the gate
frequency, how to make and prove a trust claim, and where things live in the repository. The release posture is
enforced by docs/release-checklist.md. To report a vulnerability, see
SECURITY.md.
pnpm install && pnpm dev # the React PWA in a Vite dev server (Node 24, pnpm 11.14.0 via corepack)
pnpm verify # the one gate: format, lint, types, tests, posture gates, build, bundle scans
MIT, see LICENSE. AI Nutrition Coach was formerly called OpenPlate.
| The product rules the gates enforce |
| Encrypted sync plan | The plan for a relay and cross-device sync (not built yet) |
sw.behaviour.test.mjse2e/pwa.spec.tsscripts/webkit_smoke.shADR-0003 — docs/pwa.md |
tools/pwa/precache.test.mjs, tools/pwa/sw.behaviour.test.mjs, apps/web/src/pwa/app-update.test.ts, apps/web/src/components/shell/UpdatePrompt.test.tsx, apps/web/e2e/pwa.spec.ts |
The AI key and settings live inside the encrypted store, never in localStorage; the last-export date and the quiet export suggestion are encrypted too | ADR-0003 — docs/ai-tiers.md, docs/your-data.md | apps/web/src/storage/store-controller.test.ts, apps/web/src/ai/ai-settings.test.ts, apps/web/src/screens/settings/YourDataScreen.test.tsx |
| Per-plate delete & privacy page: remove one plate (and its coach note) after a calm confirm, reachable by screen reader too; a one-time "About this app" notice and a one-time Open Food Facts notice, both kept in the encrypted store; an in-app page saying what is stored and sent; photos ask before every upload by default | PV1 / PV5 / PV7 — docs/your-data.md | packages/core/test/delete-plate.test.ts, apps/web/src/screens/plate/PlateScreen.test.tsx, apps/web/src/screens/settings/PrivacyScreen.test.tsx, apps/web/src/screens/about/AboutNotice.test.tsx, apps/web/src/catalog/privacy-prefs.test.ts, apps/web/src/ai/consent.test.ts |
What leaves the device is pinned exactly: the AI request has a fixed key set, the Open Food Facts request is GET /api/v2/product/<barcode>?fields= with no Referer / Cookie, no log call can carry content, the browser blocks every other origin (CSP connect-src), and the self-host container logs no request (no User-Agent, address truncated) unless asked | PV2 / PV3 / PV9 — docs/telemetry-posture.md | apps/web/src/ai/openai-compatible-provider.test.ts, apps/web/src/catalog/off-barcode-source.test.ts, tools/gates/log-hygiene.test.mjs, apps/web/e2e/csp.spec.ts, tools/pwa/infra.test.mjs |
Zero telemetry by default; only user-enabled network paths (an import-based gate over the TypeScript AST, run by pnpm check:telemetry) | FR-12 — docs/telemetry-posture.md | tools/gates/telemetry.test.mjs |
Adults-only: no biometric API, no under-13 / teen / parent profile machinery (pnpm check:adults-only) | FR-13 / FR-14 — docs/adults-only.md | tools/gates/adults-only.test.mjs, packages/core/test/managed-oversight-seam.test.ts |
| No-Dark-Patterns Standard (no streaks, badges, countdowns, spinners, red) | FR-15 — docs/no-dark-patterns-standard.md | apps/web/src/no-dark-patterns.test.ts, tools/gates/release-posture.test.mjs |
| Every screen meets the accessibility floor (axe: names, roles, contrast, target size) in jsdom and in a real browser | NFR-7 — docs/accessibility.md | apps/web/src/a11y-floor-gate.test.ts, apps/web/src/screens/today/TodayScreen.test.tsx, apps/web/e2e/gallery.spec.ts |