--- phase: 06-preset-data-first-run-detection-and-db-safety plan: 02 type: execute wave: 1 depends_on: [] files_modified: - src/data/presets.ts - src/i18n/en.json - src/i18n/de.json autonomous: true requirements: - SETUP-02 must_haves: truths: - "src/data/presets.ts exports a PRESETS array of exactly 19 typed items" - "PRESETS covers all 6 category types with the agreed distribution (4+4+5+2+2+2)" - "Every preset item has a slug, type (valid CategoryType), defaultAmount (round number EUR), and item_tier (fixed or variable)" - "en.json has a top-level presets key with all 19 slugs translated to English" - "de.json has a top-level presets key with all 19 slugs translated to German" artifacts: - path: "src/data/presets.ts" provides: "PresetItem interface and PRESETS array" exports: ["PresetItem", "PRESETS"] - path: "src/i18n/en.json" provides: "English preset translations under presets.* key" contains: "presets" - path: "src/i18n/de.json" provides: "German preset translations under presets.* key" contains: "presets" key_links: - from: "src/data/presets.ts" to: "src/lib/types.ts" via: "import CategoryType" pattern: "import.*CategoryType.*from.*types" - from: "src/i18n/en.json" to: "presets.{type}.{slug}" via: "react-i18next t() dot-path" pattern: "\"presets\":" --- Create the static preset budget item library and its i18n translations. `src/data/presets.ts` exports `PresetItem` interface and `PRESETS` array with 19 items across 6 category types (4 income, 4 bill, 5 variable_expense, 2 debt, 2 saving, 2 investment). Both `src/i18n/en.json` and `src/i18n/de.json` get a new top-level `"presets"` key containing all 19 English/German display names. Purpose: This is the curated item library the Phase 7 wizard shows to new users for one-click budget template setup. All amounts are plain EUR numbers — the wizard reads currency from `profiles.currency`, not from this file. Output: `src/data/presets.ts`, updated `en.json`, updated `de.json`. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.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 From src/lib/types.ts: ```typescript export type CategoryType = | "income" | "bill" | "variable_expense" | "debt" | "saving" | "investment" export type ItemTier = "fixed" | "variable" | "one_off" ``` From src/i18n/en.json — existing top-level structure (add "presets" alongside these): "app", "nav", "auth", "categories", "template", "budget", "settings", "common" i18n library: react-i18next — uses t('dot.path.key') syntax confirmed. NOTE: Do NOT hardcode currency symbols in preset display names or amounts. Amounts are plain numbers (EUR value). The wizard (Phase 7) will format them. Task 1: Create src/data/presets.ts — 19-item preset library src/data/presets.ts - src/lib/types.ts - .planning/phases/06-preset-data-first-run-detection-and-db-safety/06-RESEARCH.md (Pattern 4: Preset Data File Shape) Create `src/data/presets.ts`. The file must NOT import from Supabase or React — it is a pure static data module. ```typescript import type { CategoryType } from "@/lib/types" export interface PresetItem { slug: string type: CategoryType defaultAmount: number // EUR, round number — do NOT suffix with currency symbol item_tier: "fixed" | "variable" } export const PRESETS: PresetItem[] = [ // income (4) { slug: "salary", type: "income", defaultAmount: 3000, item_tier: "fixed" }, { slug: "freelance", type: "income", defaultAmount: 500, item_tier: "variable" }, { slug: "rental_income", type: "income", defaultAmount: 800, item_tier: "fixed" }, { slug: "other_income", type: "income", defaultAmount: 200, item_tier: "variable" }, // bill (4) { slug: "rent", type: "bill", defaultAmount: 1000, item_tier: "fixed" }, { slug: "electricity", type: "bill", defaultAmount: 80, item_tier: "fixed" }, { slug: "internet", type: "bill", defaultAmount: 40, item_tier: "fixed" }, { slug: "phone", type: "bill", defaultAmount: 30, item_tier: "fixed" }, // variable_expense (5) { slug: "groceries", type: "variable_expense", defaultAmount: 400, item_tier: "variable" }, { slug: "transport", type: "variable_expense", defaultAmount: 100, item_tier: "variable" }, { slug: "dining_out", type: "variable_expense", defaultAmount: 150, item_tier: "variable" }, { slug: "health", type: "variable_expense", defaultAmount: 50, item_tier: "variable" }, { slug: "clothing", type: "variable_expense", defaultAmount: 100, item_tier: "variable" }, // debt (2) { slug: "loan_repayment", type: "debt", defaultAmount: 200, item_tier: "fixed" }, { slug: "credit_card", type: "debt", defaultAmount: 100, item_tier: "fixed" }, // saving (2) { slug: "emergency_fund", type: "saving", defaultAmount: 200, item_tier: "fixed" }, { slug: "vacation", type: "saving", defaultAmount: 100, item_tier: "fixed" }, // investment (2) { slug: "etf", type: "investment", defaultAmount: 200, item_tier: "fixed" }, { slug: "pension", type: "investment", defaultAmount: 100, item_tier: "fixed" }, ] ``` The `item_tier` for all items must be either `"fixed"` or `"variable"` — never `"one_off"` (which is excluded from template_items by the DB check constraint). grep -c "slug:" src/data/presets.ts File exists. Contains exactly 19 objects (grep -c "slug:" returns 19). All `type` values are valid CategoryType strings. No `item_tier: "one_off"` present. `tsc --noEmit` passes. Task 2: Add preset translations to en.json and de.json src/i18n/en.json, src/i18n/de.json - src/i18n/en.json - src/i18n/de.json Add a top-level `"presets"` key to both i18n files. The key structure is `presets.{category_type}.{slug}` — matching the `type` and `slug` fields in PRESETS. For `src/i18n/en.json`, add after the last existing top-level key: ```json "presets": { "income": { "salary": "Salary", "freelance": "Freelance Income", "rental_income": "Rental Income", "other_income": "Other Income" }, "bill": { "rent": "Rent", "electricity": "Electricity", "internet": "Internet", "phone": "Phone" }, "variable_expense": { "groceries": "Groceries", "transport": "Transport", "dining_out": "Dining Out", "health": "Health & Pharmacy", "clothing": "Clothing" }, "debt": { "loan_repayment": "Loan Repayment", "credit_card": "Credit Card" }, "saving": { "emergency_fund": "Emergency Fund", "vacation": "Vacation Fund" }, "investment": { "etf": "ETF / Index Fund", "pension": "Pension" } } ``` For `src/i18n/de.json`, add the same structure with German translations: ```json "presets": { "income": { "salary": "Gehalt", "freelance": "Freelance-Einkommen", "rental_income": "Mieteinnahmen", "other_income": "Sonstiges Einkommen" }, "bill": { "rent": "Miete", "electricity": "Strom", "internet": "Internet", "phone": "Telefon" }, "variable_expense": { "groceries": "Lebensmittel", "transport": "Transport", "dining_out": "Auswärts essen", "health": "Gesundheit & Apotheke", "clothing": "Kleidung" }, "debt": { "loan_repayment": "Kreditrückzahlung", "credit_card": "Kreditkarte" }, "saving": { "emergency_fund": "Notfallfonds", "vacation": "Urlaubskasse" }, "investment": { "etf": "ETF / Indexfonds", "pension": "Altersvorsorge" } } ``` Both files must remain valid JSON after the edit. Add the `"presets"` key as the last entry in each JSON object (before the closing `}`), preceded by a comma on the previous last key. node -e "JSON.parse(require('fs').readFileSync('src/i18n/en.json','utf8')); JSON.parse(require('fs').readFileSync('src/i18n/de.json','utf8')); console.log('JSON valid')" && grep -c '"slug_key"' src/i18n/en.json || grep -c '"salary"' src/i18n/en.json Both JSON files are valid (node JSON.parse succeeds). `en.json` and `de.json` each contain a top-level `"presets"` key. `grep '"salary"' src/i18n/en.json` returns 1 match. `grep '"Gehalt"' src/i18n/de.json` returns 1 match. ## Trust Boundaries | Boundary | Description | |----------|-------------| | presets.ts → wizard UI | Static read-only data — no user input, no network calls | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-06-04 | Information Disclosure | presets.ts amounts | accept | Amounts are generic EUR defaults, not user-specific data; public knowledge | | T-06-05 | Tampering | i18n JSON malformed after edit | mitigate | Verify both JSON files parse cleanly after edit (`node -e "JSON.parse(...)"`) before committing | ```bash # Confirm presets file exists with 19 items grep -c "slug:" src/data/presets.ts # Confirm no one_off tier grep "one_off" src/data/presets.ts || echo "no one_off found (correct)" # Confirm i18n JSON files are valid node -e "JSON.parse(require('fs').readFileSync('src/i18n/en.json','utf8')); console.log('en.json valid')" node -e "JSON.parse(require('fs').readFileSync('src/i18n/de.json','utf8')); console.log('de.json valid')" # Confirm presets key present in both grep '"presets"' src/i18n/en.json src/i18n/de.json # TypeScript clean npx tsc --noEmit ``` - `src/data/presets.ts` exports `PresetItem` interface and `PRESETS` array with exactly 19 items - Distribution: 4 income, 4 bill, 5 variable_expense, 2 debt, 2 saving, 2 investment - All `item_tier` values are `"fixed"` or `"variable"` only - `en.json` and `de.json` both contain a valid `"presets"` nested object with all 19 slugs translated - Both JSON files remain valid (parseable) after edits - `tsc --noEmit` passes After completion, create `.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-02-SUMMARY.md`