Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A workplace commute carpool PWA with light gamification. Employees join a group tied to a fixed
route, publish and join trips, and earn points (Driven / Kudos) on a leaderboard, with a Pooled
count showing how often you rode along (D-49: riding is counted, not scored).
Core loop: driver publishes a trip → riders join → the ride counts itself at its departure
time → the driver is paid and riders can leave kudos.README 전문 보기 ↗A workplace commute carpool PWA with light gamification. Employees join a group tied to a fixed route, publish and join trips, and earn points (Driven / Kudos) on a leaderboard, with a Pooled count showing how often you rode along (D-49: riding is counted, not scored).
Core loop: driver publishes a trip → riders join → the ride counts itself at its departure time → the driver is paid and riders can leave kudos.
There is no Start and no End (D-61, 2026-09-19). Points used to be written at the close, and drivers were not closing — so rides that really ran paid nobody, and the scheduler cancelled them a day later. Now the scheduler settles each trip as its departure passes: every booked seat counts as ridden, the driver's award is written, and a round trip's return leg is created. Everyone gets a push 15 minutes before each leg, and the driver gets one 30 minutes after departure to pay for parking. Until the end of that day the driver can still fix the list — report a rider who didn't show (−5 for them, +2 for reporting it) or add someone who rode without booking.
Everyone on a ride shares a trip chat (D-57) for the things that only matter for the next ten minutes: where you're waiting, that you've arrived, that you're running late.
On a round trip the settle does one more thing (D-35): it materialises the return leg as a real
trip of its own, seating the riders who said at join time that they were coming back, and freeing
the seats of those who said they weren't. That leg then settles at its own return time. A round trip
is therefore two rows with one departure each, not one row with two — which is what lets every rule
in the app measure against a depart_at that is actually true.
Status: Phases 0, 1, 2, 3, 4, 5, 7, 8, and 9 of docs/02_IMPLEMENTATION_PLAN.md are complete —
auth, groups, the full trip lifecycle (publish/join/settle), the append-only points ledger,
kudos, the leaderboard, Web Push, the admin console + audit log, and hardening (E2E, rate limits,
error boundaries, a11y) are all live against the real Supabase project and deployed to Vercel. Phase
6 (Google Maps) is deliberately deferred to the end of the build (see docs/DECISIONS.md) and is
the only phase left. See docs/WORKLOG.md for the current handoff and docs/DECISIONS.md for what's
still open.
Next.js 15 (App Router, TypeScript) on Vercel, Supabase (Postgres + Auth + Postgres functions for
the transactional trip-join and the return-leg generator), Web Push (VAPID, via the web-push
package), and Supabase pg_cron + pg_net for scheduling — not Vercel Cron, whose Hobby tier
caps at one run a day (D-21). Google Maps Directions API lands last. Hand-written CSS with design tokens ported from the interaction sketch —
no Tailwind, no component library. See docs/Carpool_App_Infrastructure_Plan_1.md for the full
infrastructure plan.
npx supabase, no global install needed) — for applying migrations and
regenerating typessupabase start (a fully local Postgres). Every
migration in this repo has instead been applied directly to the shared dev Supabase project via
supabase db push --linked, which doesn't need Docker.git clone <repo-url>
cd Carpool
pnpm install
pnpm will refuse to finish installing until you approve native build scripts for a few
dependencies (esbuild, sharp, supabase, unrs-resolver). Review them, then run:
pnpm approve-builds
Then:
cp .env.example .env.local
Fill in .env.local — see Environment variables below. Ask a teammate
for the shared dev Supabase project's values, or stand up your own project and run the migrations
(see Migrations).
pnpm dev
Open http://localhost:3000. /styleguide (dev-only, 404s in production) renders the design-token
primitives against the interaction sketch for comparison.
Names and purposes are documented in .env.example at the repo root — copy it to .env.local and
fill in values. Never commit .env.local or print its values; it's gitignored and unreadable by
the coding agent by design. Two groups worth knowing about:
NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, VAPID_*,
CRON_SECRET, …) — validated at boot by src/env.ts; the app won't start without these.
NEXT_PUBLIC_APP_URL must be a full URL including https:///http:// — a bare domain fails
the build with Invalid environment variables.SUPABASE_ACCESS_TOKEN, SUPABASE_DB_PASSWORD, DATABASE_URL,
DIRECT_URL) — never read by app code, only needed for ad hoc supabase CLI commands
(db push, gen types).ADMIN_BOOTSTRAP_EMAIL — not read by the running app, only by pnpm admin:bootstrap (see
Scripts) to decide which account becomes the first platform_admin.On Vercel, set the same App runtime variables in Project Settings → Environment Variables
(Production and Preview). Scheduled execution of /api/cron/tick is disabled until Phase 10,
after the project is moved to a paid Vercel plan. The endpoint remains available for manual
verification with CRON_SECRET.
| Script | Does |
|---|---|
pnpm dev | Start the Next.js dev server |
pnpm build | Production build |
pnpm start | Serve the production build |
pnpm lint | ESLint (next lint) |
pnpm typecheck | tsc --noEmit |
pnpm format | Prettier, writes in place |
pnpm test | Unit tests (Vitest) |
pnpm test:watch | Unit tests, watch mode |
pnpm test:rls | RLS integration tests (needs a running Postgres — see Tests) |
pnpm test:admin | Admin route integration tests — G9 (403 for non-admin) + G10 (audit row per mutation). Needs pnpm dev running (see Tests) |
pnpm test:integration | Points-ledger route tests — exactly-once close (incl. concurrent), the D-42 drive/pool split, kudos award rollback. Needs pnpm dev running (see Tests) |
pnpm e2e | Playwright E2E — the core publish → join → settle → kudos loop, driven through a real browser against two seeded accounts (the settle is triggered by ageing the trip and running one cron tick) |
pnpm admin:bootstrap | Promotes the account matching ADMIN_BOOTSTRAP_EMAIL to platform_admin. Idempotent — safe to re-run |
pnpm verify | typecheck && lint && test — must pass before every commit |
pnpm db:types | Regenerate src/types/database.ts from a local Supabase instance (needs Docker + supabase start) |
src/
app/
api/ Route handlers — every route: authenticate → authorize → validate (zod) → act
admin/ Platform-admin-only routes (users, groups, trips, ledger, audit-log, health)
app/ The authenticated app shell (tabs: Carpools/Ranks/Group/You) and its screens
j/[code]/ Group invite link — joins the group, then lands on it
t/[id]/ Ride share link — signed-in members only (D-20); opens the ride in the app
admin/ The admin console UI (/admin) — gated to platform_admin, tabbed like the app shell
(Overview/Users/Groups/Trips/Ledger/Feedback/Audit log/Health)
styleguide/ Dev-only route comparing tokens/primitives against the sketch
domain/ Pure domain logic (points, trip state machine, leaderboard, seat math,
chat rules, install-platform detection) — no I/O, unit-tested
lib/
share.ts shareOrCopy() — the OS share sheet, falling back to the clipboard
supabase/ Server/admin Supabase client factories (D-04: writes always go through the
service-role client; RLS bounds the session client's reads as defense-in-depth)
trips/ Shared trip-feed query (used by both the API route and the SSR page) and the
stop resolver that validates a trip's stops belong to its own group (D-29)
notify/ Shared "write a notification row + push it" helper
push/ web-push wrapper; prunes dead subscriptions on 404/410
api/adminAuth.ts authenticateAdmin() — the authenticate+authorize step shared by every /api/admin/* route
api/settleTrip.ts settleTrip() — the whole lifecycle: claim, confirm the seats, build the
return leg, pay the driver. Called only by the scheduler (D-61)
api/driveAward.ts settleDriveAward()/syncDriveAward() — writes the driver's award at the settle
and appends a correction whenever the roster changes afterwards (D-56/D-61)
api/rosterWindow.ts how long after a ride the driver may still fix its list (D-61)
audit.ts writeAuditLog() — appends to the append-only audit_log table
rateLimit.ts Postgres-backed rate limiter (serverless has no shared memory to count in)
styles/
tokens.css Design tokens ported verbatim from the sketch — the only file allowed raw hex
components.css Component primitives (.card, .btnP, .seg, .av, …) ported from the sketch
types/database.ts Generated from the live schema (`pnpm db:types:linked`), then hand-patched
scripts/
bootstrap-admin.ts Promotes ADMIN_BOOTSTRAP_EMAIL to platform_admin — see `pnpm admin:bootstrap`
reset-data.ts Clears trip and log data, keeps accounts and groups — see `pnpm db:reset-data`
audit-ledger.ts Read-only points_ledger dump grouped by trip — see `pnpm db:audit-ledger`
dedupe-close-ledger.ts Removes award rows duplicated by a replayed close — see `pnpm db:dedupe-ledger`
repool-ledger.ts Historic D-42 backfill, superseded — see `pnpm db:repool-ledger`
unpool-ledger.ts Removes the rider `pool` rows after D-49 — see `pnpm db:unpool-ledger`
supabase/migrations/ Schema migrations, applied in order
tests/
e2e/ Playwright core-loop + ride-share-link tests, shared journey helpers,
seeded-account setup/cleanup
rls/ Cross-group RLS isolation test (needs a running Postgres)
admin/ Admin route G9/G10 integration test (needs a running `pnpm dev` server)
docs/ Planning docs, decisions register, worklog, API reference
public/ manifest.webmanifest, sw.js, icons — the PWA/push surface
Migrations live in supabase/migrations/, applied in order. This repo's dev project has no
Docker available, so every migration is applied directly to the linked remote project rather than
via a local supabase start.
The Supabase CLI does not run on the current dev machine. Windows Device Guard (WDAC) is in enforcing mode and the CLI ships an unsigned
supabase.exe, so it is refused wherever it is installed —npx supabase db pushfails from a normal terminal, not just from a sandbox, and reinstalling it via scoop/winget does not help. Use the CLI-free path below. If you are on a machine without that policy, the CLI still works and the two stay in agreement, because the script records the sameschema_migrationsversions the CLI does.
CLI-free (works on the dev machine). Talks to the Supabase Management API over HTTPS — no new
dependency and no binary beyond node. Reads SUPABASE_ACCESS_TOKEN and the project ref out of
.env.local, so no secret is typed on a command line:
pnpm db:migrations
pnpm db:push-remote
pnpm db:push-remote --yes
db:migrations is read-only — it lists every local migration against what the remote has applied,
and flags any remote version with no file in this repo. db:push-remote is a dry run by default
(the repool-ledger convention) and names what would apply; --yes applies them. Each migration
runs in its own transaction together with its schema_migrations row, so a failure rolls back
whole and later migrations are not attempted.
With the CLI, on a machine that allows it:
npx supabase link --project-ref <your-project-ref> --password "$SUPABASE_DB_PASSWORD"
npx supabase db push
(SUPABASE_ACCESS_TOKEN must be set in your environment for link/push to authenticate — see
Environment variables.) db push --dry-run shows what would apply
without touching anything. If you do have Docker, supabase start + supabase db push --local (or
just editing and re-running migrations locally) works the normal way too.
After any schema change, regenerate types:
pnpm db:types:linked
That also needs the CLI. While it is blocked, src/types/database.ts is hand-patched instead — its
header already documents that the check unions are maintained by hand for the same reason.
⚠️ Read src/types/database.ts's header before you commit the result. The generator emits plain
string for every CHECK-constrained text column, which flattens the literal-union types the rest of
the app type-checks against — trip.status, trip.direction, trip_rider.state,
membership.group_role, profile.platform_role, points_ledger.kind, notification.type — and
pnpm typecheck fails immediately with a handful of "Type 'string' is not assignable to" errors.
Those unions are hand-maintained on purpose. Reapply them, or regenerate to a scratch file and copy
across only the parts that actually changed.
and reapply the literal-union patches documented in the comment at the top of
src/types/database.ts (the generator returns CHECK-constrained columns like trip.status as
plain string, not a literal union).
Know what the commit gate does and does not cover. pnpm verify is typecheck && lint && test,
and pnpm test includes only src/**/*.test.ts — every one of which is a pure function in
src/domain/ importing nothing but its own sibling module. So a green verify says nothing about
whether any API route, React component, SQL migration or RLS policy works. Every production defect
this project has had lived in exactly that gap. The four suites below that do execute real
behaviour each need a live Supabase project, and none of them runs as part of verify — run them
before shipping anything that touches a route or the schema.
pnpm test # unit tests — pure domain logic, no I/O, run anywhere
pnpm test:rls # RLS cross-group isolation — needs a real Postgres connection (Docker locally,
# or point it at a disposable project; never run against the shared dev project)
pnpm test:admin # G9 (403 for non-admin) + G10 (audit row per mutation) — needs `pnpm dev`
# already running in another terminal; creates and cleans up its own throwaway
# admin/member test accounts against the real dev Supabase project
pnpm test:integration
# points_ledger correctness at the route level: a close is paid exactly once
# even with two requests in flight at the same instant, D-42's drive/pool split
# lands on the right people, and a kudos whose award cannot be written is
# refused rather than silently dropped. Same requirements as test:admin
# (`pnpm dev` running + a real Supabase project); throwaway accounts, cleaned up
pnpm e2e # Playwright — starts the dev server itself, seeds two fixed accounts
# ([email protected] / [email protected]) idempotently, and cleans up
# each account's trip/group data before every run
pnpm e2e needs .env.local filled in (it talks to the real dev Supabase project) and Playwright's
Chromium downloaded — npx playwright install chromium if pnpm e2e reports it's missing. The specs
are the core loop (publish → join → settle → kudos), the ride share link's access rules, and
the signup email-confirmation round trip (signup-confirm.spec.ts, which mints the confirmation
token with the admin API rather than waiting for a real email, then drives the real callback).
Import the GitHub repo as a Vercel project (Next.js is auto-detected).
Set every "App runtime" variable from Environment variables in
Project Settings → Environment Variables (Production and Preview). NEXT_PUBLIC_APP_URL must
be the deployed domain with its https:// protocol — a bare domain fails the build.
Deploy. If this is the first deploy, NEXT_PUBLIC_APP_URL won't be known yet — deploy once,
then set it to the assigned domain and redeploy.
In the Supabase dashboard → Authentication → URL Configuration, set both fields — they take different values, and sign-in redirects break if they don't:
Site URL is the deployed origin and nothing more — https://<domain>, no path. It is the
fallback Supabase redirects to when a link carries no allow-listed redirect_to, and it is what
{{ .SiteURL }} expands to in the email templates below. Putting a path here (.../auth/callback)
silently doubles it in those templates and produces dead confirmation links.
Redirect URLs is the allow-list, and it must include the confirmation callback for every origin people sign up on, or the link in the signup email is rejected and the account can never be confirmed:
https://<domain>/auth/callbackhttp://localhost:3000/auth/callback (local development)https://*.vercel.app/auth/callback (preview deployments, if you use them)Optionally, in Authentication → Email Templates → "Confirm signup", switch the link to the
token-hash form so a confirmation opened on a different device than the one that signed up
still works (the default {{ .ConfirmationURL }} uses PKCE, which is tied to the original
browser). Both shapes are handled by /auth/callback:
<a href="{{ .SiteURL }}/auth/callback?token_hash={{ .TokenHash }}&type=signup">Confirm your account</a>
That template can't carry the ?next= the signup route sets, which is why the invite code is
also stashed in user_metadata.pending_group_code — the callback falls back to it and still
lands the visitor in the right group.
Configure custom SMTP before anyone else signs up — without it, nobody but you can register. Supabase's built-in sender is a demo facility, not an email service: it delivers , and it allows (retries of a failed signup count). So the project owner signs up successfully, uses up the allowance, and every colleague gets — "Too many confirmation emails have gone out in the last hour" — for an email that was never going to be delivered to them anyway. That limit cannot be raised while the built-in sender is in use.
Web Push (VAPID) is implemented and verified live (subscribe/unsubscribe routes, the server-side send path, dead-subscription pruning, the cron reminder/auto-close logic) — but actual delivery to a device has not been verified, since no physical device was available during development. Before trusting push in production:
VAPID_SUBJECT (mailto: or https: — web-push rejects anything else).docs/Carpool App.dc.html — the interaction sketch; the visual and behavioural specdocs/Carpool_App_Infrastructure_Plan_1.md — infrastructure plan (PWA + Vercel + Supabase + Web Push + Maps)docs/02_IMPLEMENTATION_PLAN.md — phases, goal function, data model, API surfacedocs/API.md — route reference, kept current with every route changedocs/DECISIONS.md — open decisions register; unanswered = blocked, not guesseddocs/WORKLOG.md — session handoff notespnpm db:types:linked | Regenerate src/types/database.ts from the linked remote project — what this repo actually uses, since Docker isn't assumed. Hand-patches literal-union types for CHECK-constrained columns afterward (see the note at the top of database.ts) |
pnpm db:diff | Diff local schema against migrations |
pnpm db:migrations | Read-only. List every local migration against what the linked remote has applied. Works without the Supabase CLI |
pnpm db:push-remote | Apply unapplied migrations to the linked remote over the Management API — no CLI, no Docker. Dry run by default; --yes applies |
pnpm db:audit-ledger | Read-only. Prints every points_ledger row grouped by trip, with that trip's riders and a per-profile total, so a duplicated close is visible at a glance. Writes nothing |
pnpm db:dedupe-ledger -- --yes | Repairs award rows duplicated by a replayed close (see [D-41]). A close writes all its awards in one insert, so a shared created_at is the batch key: the earliest batch per trip is kept and later ones deleted. Dry-run without --yes; writes a JSON backup to scripts/backups/ before deleting |
pnpm db:repool-ledger -- --yes | One-shot D-42 backfill, superseded by db:unpool-ledger. Rewrote already-closed trips from driver-side pooling to rider-side. Kept as the record of what was done; there is no reason to run it again |
pnpm db:unpool-ledger -- --yes | One-shot D-49 cleanup. Deletes the rider pool rows left by the D-42 backfill, because riders no longer earn points. Driver rows are never touched, and riders keep their pooled count — it is read from their confirmed seats now. Dry-run without --yes; backs up to scripts/backups/ first |
pnpm db:reset-data --yes | Destructive. Empties the activity tables (trip, trip_rider, kudos, points_ledger, notification, audit_log, feedback, rate_limit_hit) in the linked project, leaving accounts, groups, memberships, pickup places and push subscriptions intact. Refuses to run without --yes; pass --dry-run to see the row counts first |
429 email_send_rate_limitedAny SMTP provider fixes both restrictions at once. Which one to pick depends on whether you own a domain:
smtp-relay.brevo.com, port 587, and the login + SMTP key shown
on that page (the login is not always your account email — copy the one displayed).Then in Supabase → Authentication → Emails → SMTP Settings, enable custom SMTP and enter the host, port, username and password. Sender email must be exactly the address you verified with the provider, or it rejects the message; sender name is what employees see ("Karpool").
Finally, raise Authentication → Rate Limits → Rate limit for sending emails — it stays at the built-in 2/hour until you change it, custom SMTP or not.
Deliverability note if you went the no-domain route: gmail.com cannot be DKIM/DMARC-authenticated
by a third-party sender, so confirmation emails are likelier to land in spam. Fine for a small
internal tool; tell the first few people to check their spam folder. Moving to a real domain later
is a provider-side change plus a new sender address in Supabase — no code change.
SMTP credentials live in the Supabase dashboard only — never in this repo or in .env.local.
Turn the scheduler on. Migrations 0008/0009 create the carpool-tick pg_cron job, which
posts to /api/cron/tick every 5 minutes — that is what sends T-15min departure reminders,
auto-closes trips abandoned in started, and expires trips nobody ever started (D-23: 24h
after departure they move to Past). Without it, those three things simply never happen. The job reads its target and its secret from Supabase
Vault and does nothing until both exist, so add them once per project (Dashboard → Project
Settings → Vault → Add new secret, or the SQL editor):
select vault.create_secret('https://<domain>/api/cron/tick', 'carpool_tick_url');
select vault.create_secret('<the same value as CRON_SECRET on Vercel>', 'carpool_cron_secret');
Never commit those values. To confirm it works, sign in as a platform_admin and read
GET /api/admin/health — scheduler.lastStatus should be "succeeded" and scheduler.stale
should be false. stale: true means the ticks stopped; on the Supabase Free plan the usual
cause is the project pausing after 7 days without activity.
Run pnpm admin:bootstrap locally (it talks to Supabase directly, not through Vercel) once
you've signed up on the deployed app with the email in ADMIN_BOOTSTRAP_EMAIL.