OpenTrailPaper
Open-source bike computer firmware for the LilyGO T5S3 4.7" E-Paper PRO, with offline maps, GPX routes, FIT recording and Bluetooth sensors.
One registry, roll call or QR check-in, an automatic hour count and promotion criteria for every belt, for Brazilian Jiu-Jitsu schools.
Sports & fitnessTypeScript · PLpgSQLGitHub @yintong-zhouOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
BJJ Belt Progress Tracker
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Faixa BJJ keeps one registry for everybody who trains at the gym — students and instructors alike — counts the hours they put on the mat, and tells the head coach who is getting close to their next stripe or belt. It does that one thing and stops there.
It is not a gym management system. No payments, no online enrollment, no competition management. It is meant to sit alongside whatever software the gym already uses for the business side, and cover the part those tools handle badly: technical progression, with as little extra work for the instructor as possible. One deployment hosts many gyms, each isolated from the others.
Generic gym software treats belt rank as a text field. Promotions end up decided by feel, or tracked in a notebook that lives in one person's bag. Faixa BJJ gives the instructor an objective baseline — accumulated hours, time at the current belt, attendance over the last weeks — and then gets out of the way.
[!IMPORTANT] Eligibility is always a suggestion. The app flags who has met the thresholds; the promotion itself is recorded by the head coach, never automatically, and a member can never change their own belt. A member never sees remaining hours, their next grade or a verdict.
/members/criteria) drive a count of eligible members, an
eligibility filter and a marker on each row — one computation, seen three ways./check-in; members
scan it with the phone camera or with the in-app scanner on their dashboard./members/requests./logs. Ids only, no names or emails.| Layer | Choice |
|---|---|
| Frontend | Next.js 16 (App Router), React 19, Tailwind CSS v4 |
| Database | Supabase (Postgres + Auth) |
| Permission | Postgres Row Level Security, driven by the active role and gym |
| Bot check | Cloudflare Turnstile, verified by Supabase Auth |
| Tests | Vitest (pure functions in frontend/utils/), SQL replay in Docker |
| Hosting | Vercel |
There is no API layer on purpose. The browser talks to Supabase directly, so the user's JWT reaches Postgres and RLS is what actually enforces the rules — the UI only hides what the database would refuse anyway.
Requirements: Node.js 20+, a Supabase project, and Docker if you want to replay the migrations locally.
git clone <repo-url>
cd faixabjj/frontend
npm install
cp .env.example .env.local # fill in the values below
npm run dev # http://localhost:3000
| Variable | Required | What it is |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | yes | Project URL |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | yes | Publishable key (the newer key, not the legacy anon one) |
SUPABASE_SECRET_KEY | yes | Service-role key, server-only — account management and the app log need it |
NEXT_PUBLIC_SITE_URL | yes | Absolute deployment URL, no trailing slash — canonical links, sitemap, QR |
NEXT_PUBLIC_TURNSTILE_SITEKEY | yes | Turnstile sitekey; its secret lives in Supabase Auth → Attack Protection |
TURNSTILE_SECRET_KEY | for /join | Server-only; the invite registration verifies its challenge itself |
[!WARNING]
SUPABASE_SECRET_KEYbypasses Row Level Security. It carries noNEXT_PUBLIC_prefix on purpose and must never reach the browser.
Apply the SQL in supabase/migrations/ in filename order, either with the
Supabase CLI or by pasting each file into the project's SQL Editor. Every
migration is safe to run twice. Some must be applied before or after deploying
the matching app version — docs/claude/database.md says which.
To check the whole history locally, in a throwaway Postgres:
bash supabase/tests/replay.sh --isolation # from the repo root, Docker running
It applies every migration twice and runs the tenant-isolation checks. Never
point anything in supabase/tests/ at a real project.
After the migrations nobody has a privilege. Grants are made by hand, on purpose:
supabase/scripts/bootstrap-platform-admin.sql in the SQL Editor (adjust the
email inside)./gyms — timezone, go-live date
and location included — and add its manager./members.[!NOTE] Set each gym's go-live date to the day it really starts recording attendance. Hours before it are estimated, hours from it on are counted; a date in the future counts the same weeks twice.
All of these run from frontend/:
npm run dev # dev server (Turbopack)
npm run build # production build
npm run start # serve the production build
npm run lint # ESLint
npm test # Vitest, single run
npm run test:watch
Roles come from an active assigned_role row and decide what a person can
reach inside their gym:
| Role | Members list | Courses / Attendance | Edit registry | Manage accounts | Promote |
|---|---|---|---|---|---|
student | — | Attendance only, check-in for self | — | — | — |
assistant | — | Attendance only, check-in for self | — | — | — |
instructor | read-only | full | — | — | — |
head_coach | full | full | yes | yes | yes |
admin | full | full | yes | yes | — |
admin means "runs the portal without teaching": a portal-only admin appears in
no people list and shows no belt. The platform superadmin sits outside every
gym: it reaches only the platform dashboard, /gyms, /logs and its own
account.
Each privilege is one SQL predicate, called by the frontend rather than reimplemented in it, and enforced three times over: the nav hides the link, the page checks the predicate and answers 404, and RLS refuses the query. Hiding a nav link is never the access control.
faixabjj/
├── frontend/ # Next.js app (App Router) — run every command from here
│ ├── app/ # routes: /members, /courses, /attendance, /check-in, /dashboard,
│ │ # /account, /gym, /gyms, /logs, /join, public pages
│ ├── components/ # shell, belt graphics, hand-rolled icons, no UI dependency
│ └── utils/ # pure helpers (unit-tested), Supabase clients, i18n dictionaries
├── supabase/
│ ├── migrations/ # schema, RLS policies, views, functions — plain SQL, in order
│ ├── scripts/ # hand-run SQL (bootstrap, diagnostics), not part of history
│ └── tests/ # Docker replay and tenant-isolation checks
├── docs/
│ ├── claude/ # detailed guidance per area (database, access, members, …)
│ ├── superpowers/ # design specs and implementation plans
│ └── handoff/ # session handoffs: what was decided and why
├── directives/ # SOPs (see AGENT.md)
├── execution/ # deterministic scripts (see AGENT.md)
├── AGENT.md # the 3-tier agent architecture this repo follows
├── CLAUDE.md # working notes for contributors and coding agents
├── belt-criteria.md # promotion criteria source (IBJJF + academy practice)
└── brand-guidelines.md # colours and typography
The app runs against real data end to end: registry, promotions and eligibility, courses, attendance with geofenced and QR check-in, invite registration, dashboards, multi-gym administration and three languages. What is missing before a gym relies on it is the pilot itself.