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 02 execute 1
src/data/presets.ts
src/i18n/en.json
src/i18n/de.json
true
SETUP-02
truths artifacts key_links
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
path provides exports
src/data/presets.ts PresetItem interface and PRESETS array
PresetItem
PRESETS
path provides contains
src/i18n/en.json English preset translations under presets.* key presets
path provides contains
src/i18n/de.json German preset translations under presets.* key presets
from to via pattern
src/data/presets.ts src/lib/types.ts import CategoryType import.*CategoryType.*from.*types
from to via pattern
src/i18n/en.json presets.{type}.{slug} react-i18next t() dot-path "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.

<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 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.
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create src/data/presets.ts — 19-item preset library</name>
  <files>src/data/presets.ts</files>
  <read_first>
    - src/lib/types.ts
    - .planning/phases/06-preset-data-first-run-detection-and-db-safety/06-RESEARCH.md (Pattern 4: Preset Data File Shape)
  </read_first>
  <action>
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:

"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:

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

<threat_model>

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
</threat_model>
```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

</verification>

<success_criteria>
- `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
</success_criteria>

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