smooth-app
전 세계 식품의 성분·영양·정보를 자유롭게 협업하고 공개하는 데이터베이스
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A workout tracker for people who train at more than one gym. It logs strength and hypertrophy
sessions, easy runs, recovery and symptoms, and keeps machine history per gym and per
machine, so stack numbers from different equipment are never mixed.
Everybody signs up for their own account. Gyms, machines, programmes and history belong to one
person and nobody else can see them. What a friend may see is a short, separate list — a
finished workout's numbers, a run's distance and pace, the best of each lift — and only once
you have let them follow you (see Friends).README 전문 보기 ↗A workout tracker for people who train at more than one gym. It logs strength and hypertrophy sessions, easy runs, recovery and symptoms, and keeps machine history per gym and per machine, so stack numbers from different equipment are never mixed.
Everybody signs up for their own account. Gyms, machines, programmes and history belong to one person and nobody else can see them. What a friend may see is a short, separate list — a finished workout's numbers, a run's distance and pace, the best of each lift — and only once you have let them follow you (see Friends).
Progressive overload, one set at a time.
Weights and heights are stored once, in kilograms and centimetres, and read back in whichever units the account chose — pounds and feet, or kilograms and centimetres. Body weight is a running record rather than a single field: finishing a session with a weight, or changing it on the profile, writes that day's reading, the profile always shows the newest one, and Progress → Body weight draws the trend.
The only thing shipped with the app is shared reference data: the equipment catalogue (92 kinds of machine), the exercise library (376 movements, every muscle group covered), warm-up protocols and the programme templates. It is one library, readable by everybody and owned by nobody, so a new account has all of it on day one and adds only what its own gyms have. Nothing is seeded per person.
Each exercise says how it is counted — reps, seconds held, or metres covered — so a farmer's carry asks for a distance and a plank for a time. Rep-based sets record reps in reserve (RIR); timed sets and carries record perceived effort (RPE), including in completed workout history.
A finished workout can be mended for a week after its day: open it (from Progress → History, the calendar or Today), tap Edit, and every exercise opens to change, add or delete its sets, with Add exercise for one nobody logged. Each change saves as it is made, and History, Progress, records and what friends see follow it. After the week it is read only (ADR 0049).
SETUP.md: the one-time steps to create the Supabase and Vercel projects.docs/implementation-plan.md: structure, phases and decisions.docs/decisions/: architecture decision records.docs/planning/: the original requirements and training context.docs/planning/FRIENDS_COMPARE_LEADERBOARD_PLAN.md
and ADR 0026: following, comparison
and leaderboards among friends, and the contract for what a follower can see.The fifth tab is Profile: who you are, then everything that used to be Settings. Its Friends page is the one door to other people (ADR 0026):
/u/username): name, handle, follower counts, and — if you may see it —
their workouts, working sets and volume for a period, the shape of their muscle split, and
their best lifts; or their runs, distance, time and best pace. Your own page shows exactly
what a follower would see.Both sports have their own switch on those screens: Lifting first, Running beside it.
Screenshots of each screen live in docs/friends/.
Only movements from the shared library whose load means the same everywhere are compared or ranked — a machine's stack numbers are its own — and estimated 1RM covers barbell and dumbbell lifts. Everything is stored in kilograms and read back in the viewer's unit.
Privacy (Profile → Privacy) has four switches — approve requests, share training, share
body weight for relative strength (off by default), be findable by email — and lists, in plain
words, what a follower can and can never see. Nothing about other people is readable except
through three shared_* tables that the owner writes and an accepted follower may read; every
other table keeps its owner-only policy.
Requires Node.js 20.9 or newer (Node 22 recommended) and npm.
For a separate local database, local authentication and six test accounts with 56 months of history, follow the local audit setup. The comprehensive audit report records the fixes, screen coverage and verification commands.
npm install
cp .env.example .env.local # fill in the values described in SETUP.md
npm run db:setup # apply migrations and seed the shared library
npm run dev # http://localhost:3000
The app shell renders without any environment variables, but signing up and every data screen
need the Supabase values from SETUP.md.
| Command | What it does |
|---|---|
npm run dev | Start the development server |
npm run build | Production build |
npm run start | Serve the production build |
npm run lint | ESLint (Next.js core-web-vitals + TypeScript rules) |
npm run format | Prettier, writes changes (format:check only checks) |
npm run typecheck | Generate Next.js route types, then tsc --noEmit |
npm test | Vitest: domain rules, seed integrity, migrations + RLS on PGlite |
npm run check | lint + format check + typecheck + tests |
npm run db:generate | Generate a SQL migration from the Drizzle schema |
npm run db:migrate | Apply migrations to DIRECT_DATABASE_URL |
npm run db:seed | Seed the shared library (no gyms, machines or programmes) |
npm run db:setup | db:migrate followed by db:seed |
npm run db:deploy | What a production deploy runs: migrate, then seed the library |
npm run db:deploy:after-build | What a production deploy runs after the build: carry Today's run skips onto runs |
npm run db:backfill:shared-stats | Rewrite friends' shared stats from history (idempotent; db:deploy ran it once) |
npm run db:studio | Drizzle Studio against the configured database |
Every real screen is behind sign-in and a database. npm run dev also serves /preview (the
day's cards, including a day that both lifts and runs), /preview/logging (the set grid in each
of its three measures) and /preview/food (the Food tab, with
?state=first|empty|over|evening|noweight; a meal's page with ?meal=breakfast, adding
&state=new for an account with no foods yet; and ?page=targets, ?page=my-foods and
?page=meal, each taking &state=first or &state=new for its first use) against made-up
data, so the navigation, Today, logging and food can be seen on a phone before anything ships.
These routes exist in development only; a production build does not have them.
src/
app/
layout.tsx, manifest.ts metadata, viewport, PWA manifest
(auth)/ sign in, sign up, forgot and reset password
auth/confirm/ where every emailed link lands
(onboarding)/welcome/ the four first-run steps
(app)/ the five tabs behind the shared shell
today/ training/ food/ progress/ profile/
progress/history/ History: every entry, behind Overview's latest ten
profile/friends/ friends, leaderboard, compare; profile/privacy/
u/[username]/ a person's page and the head-to-head comparisons
gyms/[gymId]/..., exercises/..., workouts/[sessionId]/...
(preview)/ development-only screens: the shell without an account
proxy.ts refreshes the Supabase session; keeps the app private
db/
schema/ Drizzle tables, enums and RLS policies (source of truth)
migrations/ generated SQL + the hand-written auth bridge
seed/ shared reference data, programme templates, seed CLI
test/ PGlite database and the populated-account fixture
client.ts, with-user.ts node-postgres client; RLS-enforcing transaction wrapper
domain/ pure rules: blueprints, equipment resolution, pace, calendar
server/ auth helpers, server actions, repositories, validation
components/ shell, ui primitives
lib/ env access, app identity, helpers
docs/ plan, ADRs, planning documents
Every user-owned table has a user_id column and a Row Level Security policy on auth.uid().
Application code reads and writes inside withUser(db, userId, fn), a transaction that sets the
Supabase JWT claims and switches to the authenticated role, so the policies apply to Drizzle
queries exactly as they do to Supabase's own API. Migrations and the seed CLI use the direct
connection instead.
The three shared_* tables are the one exception to "owner-only", by design: their read policy
is can_view_training(user_id) (or can_view_body_weight), a security-definer function that
says whether the signed-in user is an accepted follower of the owner while the owner shares.
Every query against them still names the user ids it wants; the policy is the guarantee, not
the filter.
A programme is described by a blueprint (src/domain/program-blueprint.ts): a validated,
versioned document that names exercises, equipment and warm-ups by their shared slug.
createProgramFromBlueprint turns one into a user's own rows.
The built-in templates are blueprints. So is anything that later generates or revises a plan — an import, a coach, or a model given the exercise library — which is why that path already supports continuing a programme's lineage as a new version rather than editing history.
Deploy to Vercel (see SETUP.md), then open the URL on the phone. Profile → Install offers
Chrome's install prompt on Android; on iPhone use Safari's Share → Add to Home Screen. The
app launches standalone with safe-area padding.
Calorie and macro tracking, entered by hand. The day has seven meals, in the order they are eaten: breakfast, morning snack, lunch, afternoon snack, evening snack, dinner and late-night snack. My foods is a screen of its own: a food is its kcal and, if known, carbohydrate, fat and protein for a portion in a real unit (100 g, 250 ml, 1 scoop), and a meal is a set of foods at amounts. Both are made there without logging anything, and added to any meal of the day from its page; a new food can also be made from a meal's page when a search finds nothing. Logging asks only how much, and the figures follow: 200 g of oats saved per 100 g is twice everything.
Targets are a screen of their own too: the day's kcal, protein per kilogram of body weight and fat as a share, with carbohydrate the rest, starting from a split the profile's training goal chooses (55 / 25 / 20 by default). The Food tab shows the day against a goal band and each macronutrient against its target; tapping one lists what today's foods gave it. Carbohydrate and fat turn red past their targets, and protein green once reached.
Any day before today opens from the strip of days under the Food header, or from the month calendar behind it, and is filled in or corrected exactly as today is: food eaten after midnight can still go on the day it belongs to. A dot under each day says whether it met the goal (green), went past it (amber) or was only logged. Every signed-in account has it as the Food tab, where History used to be; History is now a section of Progress. See ADRs 0032, 0033, 0034, 0035, 0036 and 0037.
Progress → Food draws calories and protein a day over any span, against today's target, each average taken over the days something was logged; a bar opens its day (ADR 0042).
Saving needs a connection. Retrying a save after a lost reply cannot log a food twice. Production deployments apply database migrations before building the app.
Optionally, a Claude Code routine on the owner's Claude subscription plans everyone's next
training day overnight: exercises and machines for their gym, sets, reps, RIR, loads and a
warm-up, with a one-line reason per exercise, and the run when the day runs. Today shows the
plan, starting the session prefills it, the run screen opens with the coach's numbers already
in it, and a re-plan at another gym is one tap away. When the programme itself is the problem,
the coach proposes a change and the athlete approves it under Profile → Programme, which writes
the next version rather than editing what was logged. Each athlete switches it on under
Profile → AI coach; the owner
sets it up once, as described in docs/coach-automation.md.
A cloud ChatGPT task can back up the complete coach workflow after ten minutes without Claude progress, using the same policy and service validation. See ChatGPT coach backup for owner-only OAuth setup and activation checks.
See Coach API for token setup and endpoint details. Unsaved workout set rows are retained on the device for manual retry and removed after a confirmed save. Other forms require a connection. The offline screen explains how to reconnect; private pages are not cached.