A focused, no-account mobile productivity app for indie makers and solo developers. Built with Expo (React Native), Supabase, and a device-scoped anonymous data model.
Gatekeeper forces intentional prioritization using the RICE scoring framework combined with MoSCoW filtering. Every task earns its place.
- Manage multiple projects, one active at a time (The Vault)
- Capture raw ideas instantly (Brain Dump → "To Sort")
- Score tasks with RICE (Reach × Impact × Confidence ÷ Effort) on a Fibonacci scale
- Override scores with MoSCoW categorization (Must / Should / Could / Won't)
- Auto-assign tasks to a priority column: High, Med, Low, Later, or To Sort
- View a live GO Roadmap (Now / Next) and Devlog (completed wins)
- Swipe between priority columns on the Tasks screen
- Long-press any task card to re-score it in the Wizard
| Layer | Technology |
|---|---|
| Framework | Expo SDK 54 / React Native 0.81 |
| Navigation | Expo Router v6 (file-based tabs) |
| Database | Supabase (PostgreSQL + PostgREST) |
| Auth model | Anonymous device identity (no login) |
| Secure storage | expo-secure-store |
| Icons | lucide-react-native |
| SVG graphics | react-native-svg |
| Language | TypeScript |
app/
_layout.tsx Root layout (Expo Router Stack)
(tabs)/
_layout.tsx Tab bar layout with AppProvider wrapper
index.tsx Home tab — Strategy Dashboard
tasks.tsx Tasks tab — Kanban column view
components/
modals/
WizardModal.tsx "The Gatekeeper" — RICE + MoSCoW task wizard
QuickNoteModal.tsx Brain Dump — instant idea capture
VaultModal.tsx Project switcher bottom sheet
OnboardingModal.tsx New project creation form
context/
AppContext.tsx React Context wrapping useAppStore hook
store/
appStore.ts Central data layer — Supabase queries, device init,
RICE logic, column prediction
lib/
supabase.ts Supabase client with dynamic device header injection
deviceId.ts Persistent anonymous device UUID (SecureStore / localStorage)
types/
database.ts TypeScript types for the Supabase schema
supabase/migrations/
..._create_projects_and_tasks.sql Initial schema, RLS enable, seed data
..._add_device_id_and_scope_rls.sql device_id columns + per-device RLS policies
- Circular progress ring showing % of project tasks completed
- Project mission statement
- Strategy Specs placeholders (WHO / WHAT / WHY) — ready for content
- GO Roadmap: "Now" (High priority tasks) and "Next" (Med / Low tasks)
- Devlog: reverse-chronological list of completed tasks
- FAB (+) button for New Priority Task, Quick Note, and New Project
- Five columns: High, Med, Low, Later, To Sort
- Swipe left / right to move between columns (touch gesture on scroll area and header)
- Tap dot indicators to jump directly to any column
- Each task card shows title, tags, RICE score, and MoSCoW badge
- Tap the circle button to mark a task complete (brief confetti moment)
- Long-press a card (500 ms) to open the Wizard and re-score it
- "To Sort" column shows a Brain Dump button and a Prioritize action per card
- Peek arrow on the right edge advances to the next column
The intentional prioritization modal. Forces you to think before committing a task.
- Name the goal
- Apply quick tags (Growth, Retention, Tech Debt, Core Loop, Bugfix)
- Set MoSCoW category — overrides RICE for Must (always High) and Won't (always Later)
- Rate each RICE dimension on the Fibonacci scale: 1, 2, 3, 5, 8
- Reach — how many users does this affect per period?
- Impact — how much does it move the needle? (massive = 8, minimal = 1)
- Confidence — how sure are you of these estimates?
- Effort — how many person-weeks? (lower is better)
- Live gauge shows the calculated tier and RICE points as you adjust sliders
- Tap "Commit to X Priority" to save
One-tap idea capture. Sends the note straight to "To Sort" with default RICE values. Return later to prioritize it properly when you have time.
Bottom sheet listing all projects. Tap to switch the active project. Includes a shortcut to create a new project.
Shown automatically when no projects exist (fresh install or all projects deleted). Requires a project name; mission statement is optional.
| Column | Type | Notes |
|---|---|---|
| id | uuid PK | Auto-generated |
| name | text | Project display name |
| mission | text | One-sentence purpose statement |
| device_id | text | Owning device UUID (RLS key) |
| user_id | uuid | Reserved for future auth (nullable) |
| created_at | timestamptz |
| Column | Type | Notes |
|---|---|---|
| id | uuid PK | Auto-generated |
| project_id | uuid FK | References projects(id), CASCADE delete |
| title | text | Task / goal name |
| reach | int | Fibonacci: 1 / 2 / 3 / 5 / 8 |
| impact | int | Fibonacci: 1 / 2 / 3 / 5 / 8 |
| confidence | int | Fibonacci: 1 / 2 / 3 / 5 / 8 |
| effort | int | Fibonacci: 1 / 2 / 3 / 5 / 8 |
| moscow | text | Must / Should / Could / Won't |
| priority_column | text | High / Med / Low / Later / To Sort |
| completed | boolean | Default false |
| tags | text[] | Array of tag strings |
| device_id | text | Owning device UUID (RLS key) |
| created_at | timestamptz |
This app uses anonymous device-scoped data isolation — no accounts, no login.
-
On first launch,
lib/deviceId.tsgenerates a UUID v4 and stores it permanently in the device keychain viaexpo-secure-store(iOS / Android) orlocalStorage(web). -
lib/supabase.tsexposessetDeviceId(id)which injects the UUID into thex-device-idHTTP header on every Supabase request viaglobal.headers. -
store/appStore.tscallsgetOrCreateDeviceId()on mount, injects the ID into the Supabase client, then fetches data. AllINSERTcalls stampdevice_idonto every new row. -
PostgreSQL RLS policies on both tables evaluate:
device_id = (current_setting('request.headers', true)::json)->>'x-device-id'
A device can only SELECT, INSERT, UPDATE, or DELETE its own rows. Any request without the correct header — or with a different device ID — returns zero rows on reads and fails the
WITH CHECKclause on writes.
- Each phone install is a completely isolated data silo
- No user can access another user's data, even with the anon key
- The Supabase anon key is safe to ship in the app bundle because RLS enforces cross-device isolation at the database layer
- Uninstalling the app loses the device ID and therefore access to that device's data (by design for an MVP — a future auth layer can replace this)
- Node.js 18+
- A Supabase project (free tier is sufficient)
-
Clone the repo and install dependencies:
npm install
-
Create a
.envfile at the project root:EXPO_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key -
Apply the database migrations by pasting the two SQL files from
supabase/migrations/into the Supabase SQL Editor in chronological order, or by running:npx supabase db push
-
Start the development server:
npm run dev
Press
ifor iOS simulator,afor Android emulator, orwfor web.
| Script | Description |
|---|---|
npm run dev |
Start Expo dev server |
npm run build:web |
Export static web build to dist/ |
npm run lint |
Run Expo linter |
npm run typecheck |
Run TypeScript type checks only |
RICE Score = (Reach × Impact × Confidence) ÷ Effort
| MoSCoW / Score | Predicted column |
|---|---|
| Must | High (always) |
| Won't | Later (always) |
| Score >= 25 | High |
| Score 10-24 | Med |
| Score < 10 | Low |
Fibonacci values prevent false precision on large estimates. If an estimate feels bigger than a 3, jump to 5 — the gap is intentional.
- Strategy Specs — fill in WHO / WHAT / WHY per project
- User authentication — replace device ID with real accounts for cross-device sync
- Task archiving — permanent delete vs. archive completed tasks
- Project analytics — velocity and completion rate over time
- Drag-and-drop reordering within a column
- Push notifications for stale High-priority tasks
- Export / share roadmap as image or link