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