# I Built a Calendar App Kids Control – Even Parents Can't Access It

> Source: <https://github.com/mihirshaikbugs/kid-compass>
> Published: 2026-07-28 17:21:41+00:00

**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](/mihirshaikbugs/kid-compass/blob/main/src/lib/auth.ts) with the reasoning behind it.

[Roles & Permissions](#roles--permissions)[Tech Stack](#tech-stack)[Live App & Documentation](#live-app--documentation)[Security & PerfAI Testing](#security--perfai-testing)[Authorization Model](#authorization-model)[Getting Started](#getting-started)[Deployment](#deployment)[Project Notes](#project-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/demo](https://kid-compass-teal.vercel.app/demo)[https://kid-compass-teal.vercel.app/docs.html](https://kid-compass-teal.vercel.app/docs.html)[https://kid-compass-teal.vercel.app/openapi.json](https://kid-compass-teal.vercel.app/openapi.json)[https://kid-compass-teal.vercel.app/openapi.yaml](https://kid-compass-teal.vercel.app/openapi.yaml)The `/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's`title`

/`notes`

/`status`

belong to the child; its`rewardNote`

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 flag*at 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](/mihirshaikbugs/kid-compass/blob/main/src/lib/auth.ts), and every endpoint's access rules are spelled out in the

[API documentation](https://kid-compass-teal.vercel.app/docs.html).

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](/mihirshaikbugs/kid-compass/blob/main/src/lib/auth.ts) and the model comments in

[.](/mihirshaikbugs/kid-compass/blob/main/prisma/schema.prisma)

`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](https://neon.tech)). Set`DATABASE_URL`

(pooled) and`DIRECT_URL`

(direct) in your host's env vars, then run`npm run db:push`

and`npm run db:seed`

against it.**Clerk**— create a production Clerk application, copy`NEXT_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 into`CLERK_WEBHOOK_SECRET`

.**Deploy**— import the repo into Vercel (or any Next.js host), add the env vars from`.env.example`

, deploy.`npm run build`

runs`prisma 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 the`isPrivate`

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, breaking`next build`

on every route with an unrelated-looking`TypeError`

. Fixed by pinning shared packages via`package.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 in`src/middleware.ts`

.

Built in a single session with [Claude Code](https://claude.com/claude-code).
