Files
SimpleFinanceDash/.planning/phases/06-preset-data-first-run-detection-and-db-safety/06-02-PLAN.md

284 lines
11 KiB
Markdown

---
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\":"
---
<objective>
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`.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<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
<interfaces>
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).
</action>
<verify>
<automated>grep -c "slug:" src/data/presets.ts</automated>
</verify>
<done>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.</done>
</task>
<task type="auto">
<name>Task 2: Add preset translations to en.json and de.json</name>
<files>src/i18n/en.json, src/i18n/de.json</files>
<read_first>
- src/i18n/en.json
- src/i18n/de.json
</read_first>
<action>
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.
</action>
<verify>
<automated>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</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<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>
<verification>
```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>