A calendar and chore planner kids run themselves — parents watch, suggest, and cheer from the sidelines. Never edit, never override.
Kid Compass is a role-based family planner. Kids plan, log, and check off their own day — activities, chores, to-dos. Parents get full visibility and a way to encourage from the sidelines (comments, rewards), but never a way to edit a child's own entries.
Underneath the product, Kid Compass is also an authorization-testing app: every access-control boundary described below is a deliberate design decision, documented inline in src/lib/auth.ts with the reasoning behind it.
Roles & PermissionsTech StackLive App & DocumentationSecurity & PerfAI TestingAuthorization ModelGetting StartedDeploymentProject Notes
Kid Compass has four roles, scoped per family:
| Role | Can do |
|---|---|
| Family Admin | |
| Everything a Guardian can, plus: manage membership, create invites, revoke guardian grants, toggle a child's "maturity mode" | |
| Guardian | |
| See every child's schedule/chores/tasks in the family; leave suggestions; assign chores or drop them in the shared pool; set rewards | |
| Secondary Guardian | |
| See only what an explicit, revocable grant allows — one child, optionally one entry kind, optionally a bounded time window | |
| Child | |
| Create/edit/delete their own schedule blocks and tasks; mark entries private (if maturity mode is on); claim pool chores; mark their own tasks complete |
A parent can always see what's planned. Only the child who owns an entry can ever check it off, edit it, or mark it done. Once maturity mode is on for a child, private entries are unreadable by anyone else — including a Family Admin.
| Layer | Choice |
|---|---|
| Framework | Next.js 15 (App Router) |
| Language | TypeScript |
| UI | React, Tailwind CSS, Radix-based shadcn/ui |
| Auth | Clerk |
| Database | PostgreSQL (Neon) |
| ORM | Prisma |
| Validation | Zod |
| Hosting | Vercel |
| Built with | |
| Resource | URL |
|---|---|
| Live app | |
https://kid-compass-teal.vercel.app/demohttps://kid-compass-teal.vercel.app/docs.htmlhttps://kid-compass-teal.vercel.app/openapi.jsonhttps://kid-compass-teal.vercel.app/openapi.yamlThe /demo
route renders one seeded family's real data with no account required — including a private entry, shown correctly redacted, to demonstrate the core privacy guarantee live.
Kid Compass was purpose-built as a testing target for PerfAI Security's Broken Access Control review. The boundaries below are specifically the kind of thing a BOLA/IDOR-focused review is meant to probe:
Same-tier sibling isolation— two children in one family hold the identical role (CHILD
) and must be fully isolated from each other, despite sharing every other membership property.Field-level ownership on a single row— a chore'stitle
/notes
/status
belong to the child; itsrewardNote
belongs to the guardian who assigned it. Two legitimate owners, two different fields, one database row.Privacy that ignores a later setting change— a private entry's visibility is baked in at creation time from the child's maturity-mode flagat that moment. Toggling the flag off later never retroactively exposes something written while it was on.Grant-scoped access with no membership shortcut— a secondary guardian's access comes entirely from an explicit, revocable grant row, never inferred from family membership alone.Ownership validation on every mutating request— task completion, block edits, and chore claims all re-verify the caller's relationship to the resource server-side, never trusting client-supplied IDs.
Review questions this app is designed to be tested against:
- Can one child access another child's private schedule?
- Can a guardian modify data they should only be able to view?
- Can a secondary guardian access data outside their granted scope?
- Are private calendar entries actually protected at the API layer, not just hidden in the UI?
- Are ownership checks enforced on every request, not just the ones the UI exercises?
The full authorization model is documented inline in src/lib/auth.ts, and every endpoint's access rules are spelled out in the
Two layers:
Site-level(Role
USER
/ADMIN
) — moderation only (ban a user). Never grants access to any family's data.Per-family(FamilyRole
FAMILY_ADMIN
/GUARDIAN
/SECONDARY_GUARDIAN
/CHILD
) — governs everything else.
Full reasoning for every check lives in src/lib/auth.ts and the model comments in
prisma/schema.prisma
npm install
npm run db:push # sync the schema to your local Postgres
npm run db:seed # 3 fictional families with kids/chores/activities
npm run dev
Requires a local Postgres database and a .env
with DATABASE_URL
/ DIRECT_URL
pointing at it (see .env.example
). Clerk runs in keyless dev mode locally — no API keys needed to start; visiting the app for the first time auto-provisions temporary dev keys.
To promote a signed-up user to site-level ADMIN
(moderation only — never grants access to family data):
npm run make-admin -- <username>
Database— provision Postgres (e.g.Neon). SetDATABASE_URL
(pooled) andDIRECT_URL
(direct) in your host's env vars, then runnpm run db:push
andnpm run db:seed
against it.Clerk— create a production Clerk application, copyNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
/CLERK_SECRET_KEY
into your host's env vars, and point a webhook at/api/webhooks/clerk
(user.created
,user.updated
,user.deleted
) — copy the signing secret intoCLERK_WEBHOOK_SECRET
.Deploy— import the repo into Vercel (or any Next.js host), add the env vars from.env.example
, deploy.npm run build
runsprisma generate
automatically.- Run
npm run make-admin -- <username>
against production once your own account exists, if a site admin is needed.
Notable issues found and fixed during development:
Redaction bug— an early generic redaction helper spread every field through except theisPrivate
flag itself, which would have leaked a private entry's title/location/notes to every guardian. Replaced with explicit, type-specific redaction functions with a hard field allowlist.Dependency version mismatch— a few UI components pulled in newer patch versions of shared Radix packages than the rest of the stack, breakingnext build
on every route with an unrelated-lookingTypeError
. Fixed by pinning shared packages viapackage.json
overrides.UI/API mismatch on chore claiming— the "claim" button rendered for guardians in the shared chore pool, even though only a child can claim (the API already rejected it correctly). Fixed by gating the button on the viewer's actual role.Middleware gap— signed-out visits to protected pages weren't redirecting to sign-in, they were throwing an uncaught error. Fixed with an explicit public-route matcher insrc/middleware.ts
.
Built in a single session with Claude Code.