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 |
|
|
false |
|
|
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
}
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.
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".
<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> |
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>