smooth-app
전 세계 식품의 성분·영양·정보를 자유롭게 협업하고 공개하는 데이터베이스
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기medication app
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Check the medicine first. Then remind the user.
MediMind is a medication-management app built around a five-stage workflow:
Unlike an ordinary reminder app, MediMind verifies the medicine before it
schedules anything: it reads the label, checks the expiry date and flags missing
information, and only then suggests a reminder for the user to confirm.
MediMind is an informational medication-management and reminder tool. It
helps you read and organise what is printed on a medicine label. It does not
diagnose conditions, prescribe medication, or replace advice from a doctor or
pharmacist.README 전문 보기 ↗Check the medicine first. Then remind the user.
MediMind is a medication-management app built around a five-stage workflow:
SCAN → CHECK → CONFIRM → REMIND → TRACK
Unlike an ordinary reminder app, MediMind verifies the medicine before it schedules anything: it reads the label, checks the expiry date and flags missing information, and only then suggests a reminder for the user to confirm.
MediMind is an informational medication-management and reminder tool. It helps you read and organise what is printed on a medicine label. It does not diagnose conditions, prescribe medication, or replace advice from a doctor or pharmacist.
A complete record of everything built so far, the reasoning behind each decision, test coverage, bugs fixed, and what remains is kept in docs/PROJECT_SUMMARY.md. It is written to be lifted straight into the graduation report.
| Layer | Technology |
|---|---|
| Framework | React Native via Expo SDK 57 (RN 0.86, React 19.2) |
| Language | TypeScript, strict mode |
| Navigation | Expo Router (file-based, routes live in src/app/) |
| Local database | SQLite (expo-sqlite) — local-first, the phone is the source of truth |
| Cloud database + accounts | Supabase — PostgreSQL + Auth, with Row Level Security (supabase/schema.sql) |
| API server | Node on Railway (server/) — holds the AI key (Gemini free tier by default; Groq or Claude optional), answers assistant questions |
| Web hosting | Vercel (vercel.json) — static export of the app |
| Auth | Supabase Auth (email + password), session cached in expo-secure-store |
| Notifications | expo-notifications — locally scheduled, works offline |
| AI label reading | MedicationScannerService interface: mock ⇄ Claude vision (via the API server) |
| Voice alerts | expo-speech (on-device text-to-speech) |
| State | Zustand |
| Validation | Zod — also validates AI output so unreadable fields become null, never invented |
| Testing | Jest + jest-expo + @testing-library/react-native |
Scan this with any phone camera — it is a plain link to the live web app
(https://medimind-puce.vercel.app), no Expo Go needed:
modules/label-ocr/):
that is native code, so it needs the MediMind development build made in
the cloud by EAS (free Expo account + Apple Developer membership). See
docs/DEPLOYMENT.md §5. In Expo Go the scanner uses the MediMind server
instead.cd medimind
npm install
npm start
Then scan the QR code shown in the terminal with the iPhone's Camera app, which opens it in Expo Go. The app reloads automatically as files change.
Troubleshooting the connection on Windows
Camera says "No usable data found" — Expo Go is not installed. The QR
encodes an exp:// link, and without Expo Go nothing on the phone can open
it.
Expo Go opens but cannot reach the server — Windows Firewall is blocking
inbound connections to Metro. Either run npm run start:tunnel (no admin
rights needed), or fix it once in an Administrator PowerShell:
# Windows blocks Node inbound by default if the firewall prompt was dismissed
Get-NetFirewallRule -DisplayName "Node.js JavaScript Runtime" |
Where-Object { $_.Action -eq 'Block' } | Remove-NetFirewallRule
# Mark the home Wi-Fi as Private, then allow Metro's port on that profile
Set-NetConnectionProfile -InterfaceAlias "Wi-Fi 2" -NetworkCategory Private
New-NetFirewallRule -DisplayName "Expo Metro 8081" -Direction Inbound `
-Protocol TCP -LocalPort 8081 -Action Allow -Profile Private
A Block rule always beats an Allow rule in Windows Firewall, which is why
the existing block has to be removed rather than just overridden.
iOS via Expo Go is the primary target — it is where the app is developed and demonstrated. The web build is kept working as a convenience for fast UI work, but it cannot run the core features:
| iOS (Expo Go) | Web | |
|---|---|---|
| UI, navigation, auth, settings | ✅ | ✅ |
| SQLite database | ✅ | ⚠️ alpha; needs WASM + COOP/COEP headers |
| Camera label scanning | ✅ | ❌ |
| Local notifications | ✅ | ❌ |
| Voice alerts | ✅ | ⚠️ partial |
Native-only modules must therefore always have a web fallback or an explicit "not available here" state — see src/lib/storage.ts for the pattern.
| Command | What it does |
|---|---|
npm start | Start the Metro dev server (phone connects over the LAN) |
npm run start:tunnel | Same, but routed through ngrok — use when Windows Firewall or a public/campus Wi-Fi blocks the LAN connection |
npm run android | Start and open on a connected Android device/emulator |
npm test | Run the Jest test suite |
npm run test:watch | Run tests in watch mode |
npm run typecheck | TypeScript check with no emit |
npm run lint | Expo ESLint |
Before claiming a change works, run all three:
npm run typecheck
npm test
npx expo export --platform ios --platform web --output-dir .bundle-check # then delete it
Both platforms matter: ios is the demo target, and web catches assumptions
that a native module always exists.
Hosting on Vercel (web), Railway (API server) and Supabase (database + accounts) is a click-through of each dashboard — see docs/DEPLOYMENT.md.
Copy-Item .env.example .env.local
.env.local is git-ignored. Only publishable values belong in it — everything
prefixed EXPO_PUBLIC_ is compiled into the app bundle and readable by anyone
who downloads the app. The Anthropic API key is never stored here; it lives as
a secret on the Railway API server.
The app runs fully without any configuration: with no Supabase URL it stays local-only, and with no scan endpoint it uses the mock scanner (demo mode).
src/
├── app/ Expo Router — every file here is a screen
│ ├── _layout.tsx root: providers, launch sequence, auth gate
│ ├── onboarding.tsx 3 slides: scan → check → remind
│ ├── (auth)/ login, signup
│ └── (tabs)/ Home · Medicines · Schedule · History · Settings
├── components/
│ ├── ui/ design system: Screen, AppText, Button, Card,
│ │ Badge, TextField, TextLink, InlineMessage
│ └── auth/ AuthHeader
├── config/ constants, disclaimer text, typed env access
├── db/
│ ├── database.ts opens SQLite, runs migrations
│ ├── migrations.ts versioned schema, driven by PRAGMA user_version
│ ├── types.ts SqlDatabase — the interface that makes SQL testable
│ └── repositories/ MedicationRepository + SQLite and JSON implementations
├── domain/ entity types + Zod validation schemas
├── lib/ Result type, error catalogue, storage, dates, ids
├── services/
│ └── auth/ AuthService interface + LocalAuthService
├── stores/ Zustand stores (auth, settings, medications)
└── theme/ colour / type / spacing tokens + ThemeProvider
__tests__/ Jest suites, mirroring src/
Feature services, all present:
src/services/
├── scanner/ MedicationScannerService: MockScanner (Demo Mode) + ProxyScanner (server /scan)
├── notifications/ scheduling, permissions, Taken / Skip / Snooze actions, arrivals
└── voice/ VoiceService over expo-speech
src/domain/safety.ts the safety engine (pure function, runs on save and load)
src/domain/scan.ts scan result schema, confidence rules, duplicates, demo readings
Medicines live in SQLite on the device. The schema is versioned with
SQLite's own PRAGMA user_version, so an existing install upgrades in place
instead of losing data — see src/db/migrations.ts.
Two details worth knowing:
safety_status,
source and archived all carry CHECK constraints, so no code path can
store an invalid value.user_id, including reads by primary key. A
caller cannot fetch a medicine without proving whose it is, which is the
same guarantee Row Level Security gives in Postgres.MedicationRepository has two implementations, chosen in
src/db/repositories/index.ts: SQLite on the
phone, and a JSON store in the browser (where expo-sqlite is alpha). The same
test suite runs against both, so the fallback cannot drift.
AuthService is an interface with swappable implementations, selected in
src/services/auth/index.ts:
LocalAuthService (active) — accounts stored on the device, encrypted by
the OS keystore. Works with no internet, no backend and no API keys, so the
demo can never be broken by an outage. Passwords are stored as a salted
SHA-256 digest; see the file header for an honest note on that limitation.SupabaseAuthService (Milestone 2b) — real cloud auth with server-side
bcrypt hashing, enabled automatically once EXPO_PUBLIC_SUPABASE_URL and
EXPO_PUBLIC_SUPABASE_ANON_KEY are set.No screen imports either implementation directly.
There is no admin account and no preset login. Every account is created through Sign up and lives only on the device that created it.
For demonstrations, the login screen offers a "Use demo account" button while Demo Mode is on:
Email: [email protected]
Password: medimind123
Pressing it creates that account through the ordinary sign-up path the first time and simply signs in thereafter. The credentials are displayed on screen, the button disappears when Demo Mode is switched off, and nothing is pre-seeded — so it is a convenience, not a backdoor. Defined in src/config/demo.ts.
The palette in src/theme/colors.ts is derived from the
MediMind logo, with the source colours declared up front as BRAND:
| Token | Colour | From |
|---|---|---|
navy | #17255A | capsule outline + wordmark |
nodeBlue | #2E7FD4 | the neural-network nodes |
pillRed | #D32F27 | lower half of the capsule |
paleBlue | #DDEDF9 | the logo's background panel |
The light theme uses the logo's own arrangement: pale blue as the app background, white cards on top. That inversion is what makes the app read as MediMind rather than as a white app with a logo in the corner.
Navy is the primary action colour rather than the brighter node blue, because
white-on-navy reaches ~14:1 contrast while white-on-#2E7FD4 is only ~3.6:1 —
below the 4.5:1 WCAG minimum for body text.
Settings offers two independent choices:
Palettes live in src/theme/palettes.ts, each a full
ColorTokens set for light and dark. Adding one means adding two literals plus
a registry entry; the Settings picker is generated from the registry, so no
screen changes. "High contrast" composes on top of whichever palette is
selected, replacing it with maximum-contrast black and white.
Colour choices are checked, not eyeballed:
npm run check:contrast
scripts/check-contrast.js reads every palette straight out of the theme source and verifies 15 foreground/background pairs per palette against WCAG AA — 4.5:1 for text, 3:1 for input outlines and dividers. It exits non-zero on failure. Run it after changing any colour or adding a palette.
All icon assets are generated from assets/images/logo-lockup.png (the
master artwork) by scripts/generate-icons.ps1:
powershell -ExecutionPolicy Bypass -File .\scripts\generate-icons.ps1
The app icon itself comes from the square artwork
assets/images/app-icon-source.png.jpg via
scripts/generate-app-icon.ps1, which also
writes the favicon and the web home-screen icons in public/.
It crops the capsule mark out of the banner, colour-keys the pale-blue panel to
transparency with a graded alpha ramp, and renders the app icon, Android
adaptive icon (foreground/background/monochrome), splash image, favicon, and
the in-app logo-mark.png. Re-run it after changing the logo.
The mark is rendered in-app only through
<Logo/>, which has mark, lockup and
stacked variants. The wordmark is drawn as text, not as part of the
image, so it follows the theme — the logo's black lettering would be invisible
in dark mode.
src/app/ renders UI and
calls a store or a service. Nothing else.MedicationSafetyService.
It is a pure function of its inputs, so it is fully unit-testable.MedicationScannerService interface, not on any implementation.null, and the user confirms or edits
every value before anything is stored.| Milestone | Scope | Status |
|---|---|---|
| M1 | Project setup, design system, navigation | ✅ Complete |
| M2 | Splash, onboarding, auth, home dashboard, accessibility settings | ✅ Complete |
| M3 | SQLite schema + migrations, repositories, medication CRUD, structured manual entry (chips, date picker), health conditions, family profiles (one account, many people) | ✅ Complete |
| M4 | Camera, scanner abstraction + mock + server vision reader (free Vercel function or Railway) + on-device Apple Vision (dev build), label text panel, auto-add with confirmation, safety engine, demo scenarios | ✅ Complete |
| M5 | Suggested reminders, local notifications, dose tracking, missed doses | ✅ Complete |
| M6 | Voice alerts, offline sync, settings, accessibility, error handling, tests | ◐ Language (English / Arabic, RTL), voice alerts + snooze, privacy page done |
A chat screen in the bottom tab bar (Ask), also reachable from a medicine's Ask about this medicine button, that answers questions about the user's saved medicines: when they expire, what dosage or instructions were recorded, what to do about a missed dose.
Two implementations behind one interface, chosen in src/services/assistant/index.ts:
OfflineAssistant (whenever no proxy is configured, and the fallback
when the AI cannot be reached) — deterministic rules that read the user's
own records back in plain language. Needs no network. Every reply it can
give is unit-tested.ProxyAssistant — sends the question to the MediMind API server in
server/ (deployed on Railway), which
holds the Anthropic API key server-side and calls Claude with a system prompt
that forbids diagnosis, dosing advice and interaction claims. The app never
sees the key.Three safety layers apply regardless of implementation:
screenQuestion in
src/domain/assistant.ts) that intercepts questions
about changing doses, mixing medicines, pregnancy, children or emergencies
and answers them with a fixed conservative message — they never reach a
model.The assistant is told only name, dosage, frequency, instructions and
expirationDate for each medicine — never notes, photos, ids or account data —
and the conversation is not persisted.