chore: archive v1.0 phase directories
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
---
|
||||
phase: 04-full-app-design-consistency
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [04-01]
|
||||
files_modified:
|
||||
- src/pages/CategoriesPage.tsx
|
||||
- src/pages/TemplatePage.tsx
|
||||
- src/pages/QuickAddPage.tsx
|
||||
- src/pages/SettingsPage.tsx
|
||||
- src/i18n/en.json
|
||||
- src/i18n/de.json
|
||||
autonomous: true
|
||||
requirements: [UI-CATEGORIES-01, UI-TEMPLATE-01, UI-QUICKADD-01, UI-SETTINGS-01, UI-DESIGN-01]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Categories page uses PageShell for header with title and Add Category button"
|
||||
- "Categories page shows category group headers with left-border accent styling"
|
||||
- "Categories page shows skeleton loading state instead of blank screen"
|
||||
- "Template page uses PageShell with inline-editable name and Add Item button"
|
||||
- "Template page shows category group headers with left-border accent styling"
|
||||
- "QuickAdd page uses PageShell for header"
|
||||
- "QuickAdd page shows skeleton loading state instead of blank screen"
|
||||
- "Settings page uses PageShell with no duplicate heading"
|
||||
- "Settings page shows skeleton loading state instead of blank screen"
|
||||
- "German locale shows all text translated on all four pages"
|
||||
artifacts:
|
||||
- path: "src/pages/CategoriesPage.tsx"
|
||||
provides: "PageShell adoption, skeleton, group header upgrade"
|
||||
contains: "PageShell"
|
||||
- path: "src/pages/TemplatePage.tsx"
|
||||
provides: "PageShell adoption, skeleton, group header upgrade"
|
||||
contains: "PageShell"
|
||||
- path: "src/pages/QuickAddPage.tsx"
|
||||
provides: "PageShell adoption, skeleton"
|
||||
contains: "PageShell"
|
||||
- path: "src/pages/SettingsPage.tsx"
|
||||
provides: "PageShell adoption, skeleton, no double heading"
|
||||
contains: "PageShell"
|
||||
key_links:
|
||||
- from: "src/pages/CategoriesPage.tsx"
|
||||
to: "src/components/shared/PageShell.tsx"
|
||||
via: "import and render"
|
||||
pattern: 'import.*PageShell.*from.*shared/PageShell'
|
||||
- from: "src/pages/SettingsPage.tsx"
|
||||
to: "src/components/shared/PageShell.tsx"
|
||||
via: "import and render — replacing redundant h1"
|
||||
pattern: 'import.*PageShell.*from.*shared/PageShell'
|
||||
---
|
||||
|
||||
<objective>
|
||||
Apply PageShell, skeleton loading states, and group header upgrades to the four CRUD/settings pages (Categories, Template, QuickAdd, Settings).
|
||||
|
||||
Purpose: These four authenticated pages currently use inline `<h1>` + action div headers, return `null` while loading, and use small-dot group headers. This plan upgrades them to match the dashboard's design language -- consistent headers via PageShell, skeleton loading placeholders, and left-border accent group headers.
|
||||
|
||||
Output: Four updated page components with consistent design system application, plus new i18n keys for page descriptions.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/jlmak/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/04-full-app-design-consistency/04-CONTEXT.md
|
||||
@.planning/phases/04-full-app-design-consistency/04-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
From src/components/shared/PageShell.tsx:
|
||||
```tsx
|
||||
interface PageShellProps {
|
||||
title: string
|
||||
description?: string
|
||||
action?: React.ReactNode
|
||||
children: React.ReactNode
|
||||
}
|
||||
export function PageShell({ title, description, action, children }: PageShellProps)
|
||||
```
|
||||
|
||||
From src/components/ui/skeleton.tsx:
|
||||
```tsx
|
||||
// Skeleton primitive -- use for building page-specific loading states
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
// Usage: <Skeleton className="h-4 w-32" />
|
||||
```
|
||||
|
||||
From src/lib/palette.ts:
|
||||
```tsx
|
||||
export const categoryColors: Record<CategoryType, string>
|
||||
// Maps category type to CSS variable string like "var(--color-income)"
|
||||
```
|
||||
|
||||
Group header upgrade pattern (from RESEARCH.md):
|
||||
```tsx
|
||||
// Replace plain dot headers with left-border accent
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
<span className="text-sm font-semibold">{t(`categories.types.${type}`)}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
Current pattern in all CRUD pages to replace:
|
||||
```tsx
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<div className="size-3 rounded-full" style={{ backgroundColor: categoryColors[type] }} />
|
||||
<h2 className="text-sm font-medium text-muted-foreground">{t(`categories.types.${type}`)}</h2>
|
||||
</div>
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Upgrade CategoriesPage and TemplatePage with PageShell, skeletons, and group headers</name>
|
||||
<files>src/pages/CategoriesPage.tsx, src/pages/TemplatePage.tsx, src/i18n/en.json, src/i18n/de.json</files>
|
||||
<action>
|
||||
**CategoriesPage.tsx changes:**
|
||||
|
||||
1. **Import PageShell:** Add `import { PageShell } from "@/components/shared/PageShell"` and `import { Skeleton } from "@/components/ui/skeleton"`.
|
||||
|
||||
2. **Replace header:** Remove the `<div className="mb-6 flex items-center justify-between">` block containing the `<h1>` and `<Button>`. Wrap the entire return content in:
|
||||
```tsx
|
||||
<PageShell
|
||||
title={t("categories.title")}
|
||||
action={
|
||||
<Button onClick={openCreate} size="sm">
|
||||
<Plus className="mr-1 size-4" />
|
||||
{t("categories.add")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{/* existing content (empty state check + grouped sections) */}
|
||||
</PageShell>
|
||||
```
|
||||
|
||||
3. **Skeleton loading:** Replace `if (loading) return null` with:
|
||||
```tsx
|
||||
if (loading) return (
|
||||
<PageShell title={t("categories.title")}>
|
||||
<div className="space-y-6">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="space-y-2">
|
||||
<div className="flex items-center gap-3 rounded-sm border-l-4 border-muted bg-muted/30 px-3 py-2">
|
||||
<Skeleton className="h-4 w-28" />
|
||||
</div>
|
||||
{[1, 2].map((j) => (
|
||||
<div key={j} className="flex items-center gap-4 px-4 py-2.5 border-b border-border">
|
||||
<Skeleton className="h-4 w-36" />
|
||||
<Skeleton className="h-5 w-16 rounded-full" />
|
||||
<Skeleton className="ml-auto h-7 w-7 rounded-md" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PageShell>
|
||||
)
|
||||
```
|
||||
|
||||
4. **Group header upgrade:** Replace the plain dot group header pattern in the `grouped.map` with the left-border accent pattern:
|
||||
```tsx
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
<span className="text-sm font-semibold">{t(`categories.types.${type}`)}</span>
|
||||
</div>
|
||||
```
|
||||
Remove the old `<div className="mb-2 flex items-center gap-2">` block with the `size-3 rounded-full` dot and `<h2>`.
|
||||
|
||||
**TemplatePage.tsx changes:**
|
||||
|
||||
1. **Import PageShell and Skeleton:** Same imports as CategoriesPage.
|
||||
|
||||
2. **Replace header:** The TemplatePage header has an inline-editable `TemplateName` component. Wrap with PageShell, putting TemplateName as the title area. Since PageShell accepts a `title` string but TemplateName is a component, use PageShell differently here:
|
||||
|
||||
Instead of wrapping with PageShell using `title` prop, replace the header div with PageShell but pass the template name as a plain string title when NOT editing. Actually, the TemplateName component handles its own editing state inline. The cleanest approach: keep the TemplateName component but wrap the page content differently.
|
||||
|
||||
Replace the entire page structure:
|
||||
```tsx
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex items-center justify-between gap-4">
|
||||
<TemplateName ... />
|
||||
<Button ...>...</Button>
|
||||
</div>
|
||||
...
|
||||
</div>
|
||||
```
|
||||
|
||||
With:
|
||||
```tsx
|
||||
<PageShell
|
||||
title={template?.name ?? t("template.title")}
|
||||
action={
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={openCreate} size="sm" disabled={isSaving}>
|
||||
<Plus className="mr-1 size-4" />
|
||||
{t("template.addItem")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
...
|
||||
</PageShell>
|
||||
```
|
||||
|
||||
**Note:** The TemplateName inline-edit functionality is a nice feature that will be lost if we just use a plain title string. To preserve it while using PageShell: remove the `title` prop from PageShell and instead render TemplateName inside the PageShell children, ABOVE the content. Actually, the simplest correct approach is to NOT use PageShell's title prop for TemplatePage -- instead, pass a custom `action` that includes the Add button, and render TemplateName as the first child inside PageShell with the title styling matching PageShell's own h1 style. But this defeats the purpose.
|
||||
|
||||
Best approach: Use PageShell for the layout but pass the TemplateName component as a React node for the title slot. Since PageShell only accepts `title: string`, we need to slightly modify the approach. Just use PageShell's wrapper layout manually:
|
||||
|
||||
Replace the header with:
|
||||
```tsx
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<TemplateName
|
||||
name={template?.name ?? t("template.title")}
|
||||
onSave={handleNameSave}
|
||||
/>
|
||||
<div className="shrink-0">
|
||||
<Button onClick={openCreate} size="sm" disabled={isSaving}>
|
||||
<Plus className="mr-1 size-4" />
|
||||
{t("template.addItem")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* rest of content */}
|
||||
</div>
|
||||
```
|
||||
|
||||
This mirrors PageShell's exact DOM structure (flex flex-col gap-6 > flex items-start justify-between gap-4) without importing PageShell, since TemplateName is a custom component that cannot be a plain string. This keeps visual consistency.
|
||||
|
||||
Additionally, update TemplateName's `<h1>` to use `className="text-2xl font-semibold tracking-tight"` (add `tracking-tight` to match PageShell's h1 styling).
|
||||
|
||||
3. **Skeleton loading:** Replace `if (loading) return null` with a skeleton that mirrors the template page layout:
|
||||
```tsx
|
||||
if (loading) return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-9 w-24" />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{[1, 2].map((i) => (
|
||||
<div key={i} className="space-y-2">
|
||||
<div className="flex items-center gap-3 rounded-sm border-l-4 border-muted bg-muted/30 px-3 py-2">
|
||||
<Skeleton className="h-4 w-28" />
|
||||
</div>
|
||||
{[1, 2, 3].map((j) => (
|
||||
<div key={j} className="flex items-center gap-4 px-4 py-2.5 border-b border-border">
|
||||
<Skeleton className="h-4 w-36" />
|
||||
<Skeleton className="h-5 w-16 rounded-full" />
|
||||
<Skeleton className="ml-auto h-4 w-20" />
|
||||
<Skeleton className="h-7 w-7 rounded-md" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
4. **Group header upgrade:** Same left-border accent pattern as CategoriesPage. Replace the dot+h2 pattern in grouped.map with:
|
||||
```tsx
|
||||
<div
|
||||
className="mb-2 flex items-center gap-3 rounded-sm border-l-4 bg-muted/30 px-3 py-2"
|
||||
style={{ borderLeftColor: categoryColors[type] }}
|
||||
>
|
||||
<span className="text-sm font-semibold">{t(`categories.types.${type}`)}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
**i18n: No new keys needed for this task.** Categories and Template pages already have all required i18n keys. The page descriptions are optional (Claude's discretion) -- skip them for these two pages since the page purpose is self-evident from the content.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build</automated>
|
||||
</verify>
|
||||
<done>CategoriesPage and TemplatePage both show: consistent header layout matching PageShell spacing (flex-col gap-6), left-border accent group headers replacing dot headers, skeleton loading states replacing `return null`. No inline h1 header pattern remains. Build passes.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Upgrade QuickAddPage and SettingsPage with PageShell and skeletons</name>
|
||||
<files>src/pages/QuickAddPage.tsx, src/pages/SettingsPage.tsx</files>
|
||||
<action>
|
||||
**QuickAddPage.tsx changes:**
|
||||
|
||||
1. **Import PageShell and Skeleton:** Add `import { PageShell } from "@/components/shared/PageShell"` and `import { Skeleton } from "@/components/ui/skeleton"`.
|
||||
|
||||
2. **Replace header:** Remove the `<div className="mb-6 flex items-center justify-between">` header block. Wrap the entire return in:
|
||||
```tsx
|
||||
<PageShell
|
||||
title={t("quickAdd.title")}
|
||||
action={
|
||||
<Button onClick={openCreate} size="sm">
|
||||
<Plus className="mr-1 size-4" />
|
||||
{t("quickAdd.add")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{/* empty state + table + dialog */}
|
||||
</PageShell>
|
||||
```
|
||||
Remove the wrapping `<div>` root since PageShell provides the outer container.
|
||||
|
||||
3. **Skeleton loading:** Replace `if (loading) return null` with:
|
||||
```tsx
|
||||
if (loading) return (
|
||||
<PageShell title={t("quickAdd.title")}>
|
||||
<div className="space-y-1">
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<div key={i} className="flex items-center gap-4 px-4 py-2.5 border-b border-border">
|
||||
<Skeleton className="h-5 w-10 rounded-full" />
|
||||
<Skeleton className="h-4 w-36" />
|
||||
<Skeleton className="ml-auto h-7 w-7 rounded-md" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PageShell>
|
||||
)
|
||||
```
|
||||
|
||||
**SettingsPage.tsx changes:**
|
||||
|
||||
1. **Import PageShell and Skeleton:** Add `import { PageShell } from "@/components/shared/PageShell"` and `import { Skeleton } from "@/components/ui/skeleton"`.
|
||||
|
||||
2. **Remove duplicate heading:** Delete the `<h1 className="mb-6 text-2xl font-semibold">{t("settings.title")}</h1>` on line 67. This creates a double heading since the Card below also has a CardTitle with "Settings".
|
||||
|
||||
3. **Wrap with PageShell:** Replace the `<div className="max-w-lg">` root with:
|
||||
```tsx
|
||||
<PageShell title={t("settings.title")}>
|
||||
<div className="max-w-lg">
|
||||
<Card>
|
||||
{/* Remove CardHeader with CardTitle since PageShell provides the title.
|
||||
Keep CardContent as-is. */}
|
||||
<CardContent className="space-y-4 pt-6">
|
||||
{/* existing form fields unchanged */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</PageShell>
|
||||
```
|
||||
Remove the CardHeader and CardTitle entirely -- PageShell provides the page-level title, and the Card should just contain the form. Add `pt-6` to CardContent's className since without CardHeader the content needs top padding.
|
||||
|
||||
4. **Skeleton loading:** Replace `if (loading) return null` with:
|
||||
```tsx
|
||||
if (loading) return (
|
||||
<PageShell title={t("settings.title")}>
|
||||
<div className="max-w-lg">
|
||||
<Card>
|
||||
<CardContent className="space-y-4 pt-6">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="space-y-2">
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
))}
|
||||
<Skeleton className="h-10 w-20" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</PageShell>
|
||||
)
|
||||
```
|
||||
|
||||
5. **Clean up unused imports:** After removing CardHeader and CardTitle usage, update the import to: `import { Card, CardContent } from "@/components/ui/card"`. Remove `CardHeader` and `CardTitle` from the import.
|
||||
|
||||
**No i18n changes needed for this task.** QuickAdd and Settings pages already have all required translation keys.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jlmak/Projects/jlmak/SimpleFinanceDash && bun run build</automated>
|
||||
</verify>
|
||||
<done>QuickAddPage uses PageShell with title and action button, shows skeleton on load. SettingsPage uses PageShell with no double "Settings" heading, Card contains only the form, shows skeleton on load. No `return null` loading patterns remain in either file. Build passes.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `bun run build` compiles without TypeScript errors
|
||||
- `grep -c "return null" src/pages/CategoriesPage.tsx src/pages/TemplatePage.tsx src/pages/QuickAddPage.tsx src/pages/SettingsPage.tsx` returns 0 for all files
|
||||
- `grep -c "size-3 rounded-full" src/pages/CategoriesPage.tsx src/pages/TemplatePage.tsx` returns 0 for both (old dot headers removed)
|
||||
- `grep -c "border-l-4" src/pages/CategoriesPage.tsx src/pages/TemplatePage.tsx` returns at least 1 for each (new accent headers applied)
|
||||
- `grep -c "PageShell" src/pages/CategoriesPage.tsx src/pages/QuickAddPage.tsx src/pages/SettingsPage.tsx` returns at least 1 for each
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All four pages (Categories, Template, QuickAdd, Settings) show consistent PageShell-style headers
|
||||
- All four pages show skeleton loading states instead of blank screens
|
||||
- Categories and Template pages show left-border accent group headers
|
||||
- Settings page has exactly ONE "Settings" heading (via PageShell), not two
|
||||
- `bun run build` passes
|
||||
- No `return null` loading patterns remain in any of the four files
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-full-app-design-consistency/04-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user