Files

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
06-preset-data-first-run-detection-and-db-safety 03 execute 2
06-01
06-02
src/hooks/useFirstRunState.ts
false
AUTO-01
AUTO-03
SETUP-01
SETUP-02
truths artifacts key_links
useFirstRunState hook returns isFirstRun=true when categories array is empty
useFirstRunState hook returns isFirstRun=true when template items array is empty
useFirstRunState hook returns isFirstRun=false when user has both categories and template items
useFirstRunState hook returns loading=true while either underlying query is in flight
DB schema push completes without errors — uniqueness constraints and setup_completed column live in Supabase
path provides exports
src/hooks/useFirstRunState.ts Derived first-run state from cached useCategories + useTemplate queries
useFirstRunState
from to via pattern
src/hooks/useFirstRunState.ts src/hooks/useCategories.ts useCategories() call — reads from cache key ['categories'] useCategories
from to via pattern
src/hooks/useFirstRunState.ts src/hooks/useTemplate.ts useTemplate() call — reads from cache keys ['template', 'template-items'] useTemplate
Write the `useFirstRunState` hook and push all migrations to the database.

useFirstRunState derives first-run state from existing React Query caches — no extra network call. It returns { isFirstRun: boolean, loading: boolean } where isFirstRun is true when either categories or template items count is zero, and loading is true while either underlying query is still fetching.

The DB schema push applies migrations 006 and 007, making uniqueness constraints and the setup_completed column live. This is a [BLOCKING] step — all verification depends on the schema being applied.

Purpose: Phase 7 wizard reads useFirstRunState().isFirstRun to decide whether to redirect new users to setup. The DB constraints prevent the data corruption that would require rollback work in Phase 8. Output: src/hooks/useFirstRunState.ts + DB schema pushed + human verification checkpoint.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-CONTEXT.md @.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-RESEARCH.md @.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-01-SUMMARY.md @.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-02-SUMMARY.md From src/hooks/useCategories.ts: ```typescript export function useCategories() { // queryKey: ["categories"] return { categories: query.data ?? [], // Category[] loading: query.isLoading, create, update, remove, } } ```

From src/hooks/useTemplate.ts:

export function useTemplate() {
  // queryKeys: ["template"], ["template-items"]
  return {
    template: templateQuery.data ?? null,
    items: itemsQuery.data ?? [],   // TemplateItem[]
    loading: templateQuery.isLoading || itemsQuery.isLoading,
    updateName, createItem, updateItem, deleteItem, reorderItems,
  }
}

From src/lib/types.ts (after Plan 01):

export interface Profile {
  id: string
  display_name: string | null
  locale: string
  currency: string
  setup_completed: boolean  // added by Plan 01
  created_at: string
  updated_at: string
}
Task 1: Create src/hooks/useFirstRunState.ts src/hooks/useFirstRunState.ts - src/hooks/useCategories.ts - src/hooks/useTemplate.ts - .planning/phases/06-preset-data-first-run-detection-and-db-safety/06-RESEARCH.md (Pattern 3: Derived State Hook, Pitfall 2) Create `src/hooks/useFirstRunState.ts` with the following exact implementation. Do not add mutations, do not import supabase directly — this hook is read-only and derives state from already-cached queries.
import { useCategories } from "@/hooks/useCategories"
import { useTemplate } from "@/hooks/useTemplate"

/**
 * Derives first-run state from cached category and template queries.
 * No additional network calls are made — data is read from React Query cache.
 *
 * isFirstRun is true when:
 *   - categories.length === 0 (user has not created any categories), OR
 *   - items.length === 0 (user has no template items)
 *
 * IMPORTANT: Always check `loading` before acting on `isFirstRun`.
 * While queries are in flight, both arrays default to [] which would
 * cause isFirstRun to be true spuriously.
 *
 * Usage:
 *   const { isFirstRun, loading } = useFirstRunState()
 *   if (!loading && isFirstRun) { redirect to /setup }
 */
export function useFirstRunState() {
  const { categories, loading: catLoading } = useCategories()
  const { items, loading: tmplLoading } = useTemplate()

  return {
    isFirstRun: categories.length === 0 || items.length === 0,
    loading: catLoading || tmplLoading,
  }
}

The loading guard is critical — see Pitfall 2 in RESEARCH.md. Callers in Phase 7 MUST check !loading && isFirstRun before redirecting. grep -n "isFirstRun" src/hooks/useFirstRunState.ts && grep -n "loading" src/hooks/useFirstRunState.ts && npx tsc --noEmit File exists. Exports useFirstRunState function. Returns { isFirstRun: boolean, loading: boolean }. tsc --noEmit passes with no errors.

Task 2: [BLOCKING] Push database schema (migrations 006 + 007) supabase/migrations/006_uniqueness_constraints.sql, supabase/migrations/007_setup_completed.sql - supabase/migrations/006_uniqueness_constraints.sql - supabase/migrations/007_setup_completed.sql Run the Supabase schema push to apply migrations 006 and 007 to the database. This command requires the Supabase CLI and an active project link.
supabase db push

If the push prompts interactively and cannot be suppressed, set the access token first:

SUPABASE_ACCESS_TOKEN=$(cat ~/.supabase/access-token 2>/dev/null || echo "SET_TOKEN_HERE") supabase db push

Expected outcome: Both migrations apply in order (006 then 007). The command exits 0.

If the command fails with a duplicate constraint error (constraint already exists), the migrations may have been partially applied. In that case, check supabase db diff to see what's pending and resolve manually.

If supabase db push requires interactive confirmation that cannot be bypassed, flag this task with a checkpoint:human-action and have the user run it manually from their terminal. supabase db push --dry-run 2>&1 | grep -E "No migrations|already applied|006|007" || echo "Check supabase CLI output above" Both migrations applied. supabase db push exits 0 (or reports migrations already applied). No error output containing "ERROR" or "FATAL".

Task 3: Verify DB constraints and setup_completed column are live Migration 006 added UNIQUE constraints on budgets(user_id, start_date) and categories(user_id, name). Migration 007 added setup_completed boolean column to profiles and backfilled existing users. useFirstRunState hook created at src/hooks/useFirstRunState.ts. 1. Open the Supabase dashboard for this project. 2. Go to Table Editor > profiles — confirm the `setup_completed` column exists with boolean type. 3. Go to Database > Tables > budgets — confirm constraint `budgets_user_month_unique` exists on (user_id, start_date). 4. Go to Database > Tables > categories — confirm constraint `categories_user_name_unique` exists on (user_id, name). 5. If you have an existing v1.0 user row in profiles, confirm setup_completed = true for that row. 6. Run `npx tsc --noEmit` in the terminal — confirm zero TypeScript errors. Type "approved" if all constraints are live and tsc passes, or describe any issues found.

<threat_model>

Trust Boundaries

Boundary Description
useFirstRunState → React Query cache Reads cached data from existing hooks — no new trust boundary introduced
supabase db push → Supabase project CLI command with project-level credentials — run only in trusted environment

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-06-06 Spoofing useFirstRunState — isFirstRun=true while loading mitigate loading flag exported; callers must check !loading && isFirstRun before acting (documented in hook JSDoc)
T-06-07 Denial of Service supabase db push failing mid-migration accept Transaction wrapping in 006 ensures atomicity; 007 is idempotent (ADD COLUMN IF NOT EXISTS can be added if needed)
</threat_model>
```bash # Hook exports correctly grep "export function useFirstRunState" src/hooks/useFirstRunState.ts

Hook uses both existing hooks

grep "useCategories|useTemplate" src/hooks/useFirstRunState.ts

TypeScript clean across all new files

npx tsc --noEmit

Migration files in place

ls supabase/migrations/006_uniqueness_constraints.sql supabase/migrations/007_setup_completed.sql

All 4 requirement IDs traceable across plans:

AUTO-01: useFirstRunState (plan 03) + budgets constraint (plan 01)

AUTO-03: setup_completed not hardcoding currency (plan 01, 02, 03 — amounts plain numbers)

SETUP-01: setup_completed column + backfill (plan 01) + useFirstRunState (plan 03)

SETUP-02: PRESETS array 19 items (plan 02)

</verification>

<success_criteria>
- `src/hooks/useFirstRunState.ts` exists and exports `useFirstRunState()` returning `{ isFirstRun: boolean, loading: boolean }`
- `isFirstRun` is derived from `categories.length === 0 || items.length === 0` — no direct Supabase calls
- DB push completes: `budgets_user_month_unique` and `categories_user_name_unique` constraints live in Supabase
- `profiles` table has `setup_completed boolean NOT NULL DEFAULT false` column
- All existing users with categories (or template items) have `setup_completed = true`
- `tsc --noEmit` passes with zero errors across all modified files
</success_criteria>

<output>
After completion, create `.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-03-SUMMARY.md`
</output>