Files
Dash/FrontendPlan.md
T
Tomas Dvorak 17a579880f refactor(frontend): restructure project layout and update API schema
Relocate frontend source code from `next-app/` to `frontend/` to align with the new project structure. This includes removing the old Next.js boilerplate files and establishing a cleaner workspace.

Additionally, updates the OpenAPI specification to include support for the `immich` widget type and its corresponding configuration schema.

- Move frontend files to `frontend/`
- Delete obsolete `next-app/` directory and its configuration
- Add `immich` widget type to `openapi.yaml`
- Update `FrontendPlan.md` with dashboard refactor and UX direction
2026-05-04 12:31:34 +02:00

1264 lines
21 KiB
Markdown

# FrontendPlan.md
## Mission
Build the complete frontend for the self-hosted homelab dashboard. The frontend owns the Next.js app, shadcn UI composition, dashboard interactions, drag/drop UX, theme system, generated API client usage, frontend tests, and production-grade responsive design.
This agent may edit:
- `/frontend`
This agent must not edit `/backend`, `/db`, or `/openapi`. Read `openapi/openapi.yaml` only to generate client/types. If the API contract is missing or wrong, document the needed backend change instead of creating duplicate schemas.
## Product Context
The app is a fast, clean dashboard for home lab services. It should feel closer to Vercel, Linear, and a Nothing-style instrument panel than older dashboard tools. The first screen is the actual dashboard, not a landing page.
Core frontend responsibilities:
- Show date/time/timezones.
- Show dashboard widgets.
- Show grouped and ungrouped services.
- Add/edit/delete services with multiple URLs.
- Let user choose local/external/custom URL before opening when service has multiple URLs.
- Create/edit/collapse/delete groups.
- Drag/drop reorder groups, services, and widgets.
- Support dark and light mode.
- Use generated API types/client only.
## Stack
- Next.js App Router.
- React with TypeScript strict mode.
- Tailwind CSS.
- shadcn/ui.
- `@dnd-kit` for drag/drop.
- `@tanstack/react-query` for server state.
- `openapi-typescript` for generated types.
- `openapi-fetch` or a thin generated client wrapper for requests.
- Playwright for end-to-end tests.
- MSW or local fixtures while backend is incomplete.
## Repository Layout
Use this frontend layout:
```text
frontend/
app/
layout.tsx
page.tsx
globals.css
components/
dashboard/
groups/
services/
widgets/
shell/
ui/
lib/
api/
mocks/
theme/
utils/
hooks/
tests/
e2e/
```
Intent:
- `app`: Next.js routes and global shell.
- `components/ui`: shadcn components only.
- `components/dashboard`: dashboard composition and layout.
- `components/groups`: group panels, group controls, group reorder.
- `components/services`: service cards, service form, URL picker.
- `components/widgets`: clock, image, Pi-hole widget cards, widget form.
- `lib/api`: generated client and React Query hooks.
- `lib/mocks`: fixtures/MSW handlers matching OpenAPI.
- `lib/theme`: theme constants and helpers.
## API Contract
Frontend consumes `../openapi/openapi.yaml`.
Generate types/client through scripts:
```json
{
"scripts": {
"api:generate": "openapi-typescript ../openapi/openapi.yaml -o lib/api/schema.ts",
"typecheck": "tsc --noEmit",
"test:e2e": "playwright test"
}
}
```
Rules:
- Do not hand-write API response types.
- All API DTOs come from generated OpenAPI types.
- UI view models may exist, but must be derived from generated types.
- If backend is unavailable, use MSW fixtures shaped exactly like generated types.
- API base URL comes from `NEXT_PUBLIC_API_BASE_URL`, default `http://localhost:8080`.
Expected resources:
```text
Dashboard
Group
Service
ServiceUrl
WidgetInstance
WidgetData
AssetFile
ErrorResponse
```
Required frontend API hooks:
```text
useDashboard()
useCreateGroup()
useUpdateGroup()
useDeleteGroup()
useCreateService()
useUpdateService()
useDeleteService()
useUpdateLayout()
useUploadIcon()
useCreateWidget()
useUpdateWidget()
useDeleteWidget()
useWidgetData(widgetId)
useRefreshWidget(widgetId)
```
Mutation behavior:
- Optimistically update layout reorder where safe.
- Roll back reorder on API failure.
- For CRUD forms, close dialog only after success.
- Render inline errors as `[ERROR: message]`.
- Avoid toast dependency for core flows; inline status preferred.
## Design Direction
Primary design direction:
- Dark-first dashboard.
- Light mode complete and polished.
- Vercel-inspired structure: restrained cards, shadow-as-border, tight typography.
- Nothing-inspired accents: OLED black, mono uppercase labels, red signal accent only when meaningful.
Fonts:
- Load `Geist` and `Geist_Mono` with `next/font/google`.
- Optional `Doto` only for large clock/hero metric. Do not use Doto for body text.
Visual tokens:
- Background dark: near/OLED black.
- Background light: off-white/white.
- Text dark mode: white/soft gray hierarchy.
- Text light mode: `#171717` and neutral grays.
- Radius: 6px for controls, 8px for cards, no large rounded card stacks.
- Borders: use shadow-as-border or tokenized border, not heavy outlines.
- Accent: red only for alert/active signal/destructive state.
- Icons: monoline Lucide via shadcn project icon library.
Nothing/Vercel constraints:
- No gradients in app chrome.
- No decorative blobs/orbs.
- No emojis.
- No nested cards.
- No large marketing hero.
- No skeleton-only loading screens; prefer `[LOADING...]` or compact shadcn loading state.
- No visible instructional paragraphs explaining obvious UI.
- Use strong spacing and hierarchy instead of decorative containers.
## shadcn Rules
Use shadcn components before custom markup:
- Button
- Card
- Dialog
- Sheet
- Input
- Select
- Switch
- Tabs
- Badge
- DropdownMenu
- Tooltip
- Collapsible
- Alert
- Empty
- Separator
- ScrollArea
- Field / FieldGroup where available
Rules:
- Forms use `FieldGroup` and `Field` patterns.
- Dialogs, Sheets, and Drawers always have titles.
- Buttons with icons use `data-icon`.
- Use `gap-*`, never `space-x-*` or `space-y-*`.
- Use semantic Tailwind tokens, not random raw colors in component classes.
- Use `cn()` for conditional class names.
- Use shadcn `Empty` for no services/no groups.
- Use shadcn `Alert` for blocking errors.
## Screens and Components
### Dashboard Page
The first viewport is the dashboard.
Layout:
- Top shell with app name, current date/time, theme toggle, add button.
- Widget strip below header.
- Service area below widgets.
- Groups render as collapsible sections.
- Ungrouped services render in their own section when present.
- Empty dashboard renders a minimal empty state with primary add action.
Desktop:
- Max width around 1200-1400px.
- Dense but calm grid.
- Service cards use responsive columns.
Mobile:
- Single-column service grid.
- Add/edit forms use full-screen or near-full-height Sheet/Dialog.
- Drag handles must remain tappable.
### Service Cards
Show:
- Icon or generated initials fallback.
- Service name.
- URL kind badges or compact count.
- Optional primary URL label.
- Menu for edit/delete/move.
Click behavior:
- If service has one URL, open it directly in same tab or new tab based on app setting/default.
- If service has multiple URLs, open URL picker dialog.
- URL picker lists label, kind, and hostname.
Drag behavior:
- Drag service within group.
- Drag service across groups.
- Drag service to ungrouped.
- Show clear drop target state.
- Persist through `PUT /api/v1/layout`.
### Service Form
Fields:
- Name.
- Icon mode: icon URL or upload file.
- URLs dynamic list:
- label
- kind: local, external, custom
- URL
- primary toggle
- Group select.
Validation:
- Name required.
- At least one URL required.
- URL must be absolute `http` or `https`.
- Exactly one primary URL in UI; if user never chooses, first is primary.
- Show field-level errors, not generic banners only.
### Groups
Group section includes:
- Name.
- Service count.
- Collapse/expand control.
- Drag handle.
- Menu for rename/delete.
Behavior:
- Collapsed state persists via group PATCH.
- Empty group can be deleted.
- Non-empty group delete should ask user to move services to ungrouped, matching backend option.
- Group reorder persists through layout endpoint.
### Widgets
Widget strip supports:
- Clock widget.
- Image widget.
- Pi-hole widget.
Clock:
- Shows large time and date.
- Supports configured timezones when backend exposes config.
- Uses Geist Mono or optional Doto for display moment.
Image:
- Shows configured image in restrained card.
- Optional link click.
- No decorative cropping that hides important content.
Pi-hole:
- Shows status, blocked count, query count, percent blocked.
- Loading: `[LOADING...]`.
- Error: `[ERROR: ...]`.
- Stale data: show data with compact stale label.
- Manual refresh button.
Widget management:
- Add/edit widget Sheet.
- Widget type select.
- Type-specific config fields.
- Widget drag reorder persisted through layout endpoint.
## State Management
Use React Query for server state:
- Dashboard query is primary source for layout.
- Widget data can be separate query per widget.
- Invalidate dashboard after CRUD mutations.
- Use optimistic mutation for layout reorder.
Local UI state:
- Dialog open/closed.
- Drag active item.
- Theme.
- Form draft state.
Do not mirror entire dashboard into global client state unless needed for drag preview. If local reorder state is needed, keep it scoped to dashboard components and reconcile from query data.
## Accessibility
Required:
- Keyboard focus visible on all controls.
- Dialog title for every Dialog/Sheet.
- Service cards keyboard-accessible.
- Drag/drop has keyboard fallback or at least accessible controls for move up/down.
- Form fields have labels and error messages.
- Theme toggle has accessible label.
- Icon-only buttons have labels/tooltips.
- Color is not only signal for URL kind or errors.
## Testing Plan
Unit/component tests:
- Service form validates required name and URL.
- Dynamic URL rows add/remove correctly.
- URL picker opens for multi-URL service.
- Group collapse button updates state.
- Widget cards render loading/error/stale/data states.
Playwright tests:
- Dashboard loads fixture data.
- Add service with two URLs.
- Click multi-URL service and choose local/external URL.
- Create group and move service into group.
- Collapse group and confirm persisted state after reload.
- Drag reorder services and verify layout mutation.
- Toggle dark/light mode.
- Pi-hole widget shows data and refresh button.
Build checks:
```bash
npm run api:generate
npm run typecheck
npm run lint
npm run build
npm run test:e2e
```
Use the package manager chosen during app scaffold. If using pnpm, scripts remain same but commands run as `pnpm ...`.
## Implementation Steps
1. Scaffold Next.js App Router project in `/frontend` with TypeScript and Tailwind.
2. Initialize shadcn/ui and install required base components.
3. Add API generation script from `../openapi/openapi.yaml`.
4. Create generated client wrapper and React Query provider.
5. Build MSW/fixture data matching OpenAPI.
6. Implement theme tokens, fonts, dark/light modes, and shell.
7. Build dashboard page with fixture data.
8. Build service cards and URL picker.
9. Build add/edit service form with dynamic URLs and icon upload field.
10. Build group sections, collapse, menus, and group forms.
11. Add drag/drop for services, groups, and widgets.
12. Build widgets: clock, image, Pi-hole.
13. Wire all CRUD and layout mutations to API client.
14. Add tests and responsive polish.
15. Run build/typecheck/e2e.
## Acceptance Criteria
- `/frontend` builds successfully.
- Frontend generates types from `../openapi/openapi.yaml`.
- No hand-written API DTO duplication.
- Dashboard works with MSW fixtures before backend is ready.
- Dashboard works against backend API once available by changing `NEXT_PUBLIC_API_BASE_URL`.
- Add/edit/delete service flows work.
- Multiple URL picker works.
- Groups can be created, collapsed, reordered, and deleted safely.
- Drag/drop persists through layout API.
- Clock, image, and Pi-hole widgets render expected states.
- Dark and light modes are polished.
- Mobile and desktop layouts do not overlap or clip text.
## Parallel Coordination
- Frontend can start with fixtures while backend builds API.
- Do not invent contract fields outside OpenAPI.
- If UI needs a new field, request OpenAPI change from backend agent.
- Regenerate API client after any OpenAPI update.
- Keep all frontend work isolated inside `/frontend`.
# Dashboard Refactor & UX Plan
## Core Product Direction
The dashboard should feel empty, intentional, and flexible on first launch.
Current issue:
* The app starts with too much structure and too many assumptions.
* Users feel boxed into layouts before they build their own workspace.
New direction:
* Start with a clean canvas.
* Let users create widgets and apps only when needed.
* Prioritize drag-and-drop, layout freedom, responsiveness, and visual clarity.
* Make the dashboard feel closer to CasaOS in usability and visual hierarchy.
---
# 1. First Launch Experience
## Current Problem
Dashboard launches with widgets/groups already visible.
## New Behavior
On first launch:
* No widgets
* No pre-created services
* No placeholder cards
* No fake demo groups
Only show:
### Section 1 — Widgets
Top-right:
* Small `+ Add Widget` button
### Section 2 — Apps / Services
Top-right:
* Small `+ Add App` button
Layout example:
```text
Widgets ------------------------------------- [+]
(empty state)
Apps ---------------------------------------- [+]
(empty state)
```
## Empty State Design
Empty states should feel premium.
Example:
```text
No widgets yet
Create your first widget to customize your dashboard.
```
and:
```text
No apps added
Start by adding your first app or service.
```
Avoid giant centered buttons.
---
# 2. Layout Architecture
## Dashboard Sections
The dashboard should always contain:
1. Widgets Section
2. Apps Section
These are not groups.
These are permanent layout containers.
Groups belong inside Apps.
---
# 3. Widget System Improvements
## Current Problems
* Hard to resize
* Limited placement
* Dragging feels disconnected
* Drag icon placement is awkward
* Widgets feel static
## Required Improvements
### Fully Resizable Widgets
Users should be able to:
* Resize width
* Resize height
* Stretch across columns
* Fill entire section width
* Create masonry/grid layouts
Examples:
* Clock widget = small
* Pi-hole widget = large
* Analytics widget = full width
Recommended implementation:
### Use Grid-Based Resizing
Strong recommendation:
```text
react-grid-layout
```
Benefits:
* Resize handles
* Dragging support
* Collision detection
* Snap-to-grid
* Persistent positions
* Responsive layouts
---
## Widget Drag Handle
Current problem:
* Drag handle outside widget feels detached.
Fix:
* Drag handle should exist inside widget card.
* Top-right or top-left.
Example:
```text
[ Widget Title ⋮⋮ ]
```
Users should instantly understand:
* drag
* settings
* resize
---
## Widget Responsiveness
Widgets must:
* Reflow on smaller screens
* Collapse naturally on mobile
* Maintain resize ratios
* Support multiple breakpoints
Recommended breakpoints:
```text
Desktop: 12-column grid
Tablet: 6-column grid
Mobile: 1-column stack
```
---
# 4. Clock Widget Improvements
## Current Problem
Timezone entry requires manual input.
## Better UX
Replace manual timezone input with:
### Searchable Dropdown
Recommended:
```text
Europe/Prague
Europe/London
America/New_York
Asia/Tokyo
```
### Better Option
Checkbox multi-select dropdown:
User can:
* Add multiple clocks
* Select timezone quickly
* Remove timezone instantly
Recommended libraries:
```text
react-select
shadcn Command + Popover
```
---
# 5. Widget Reliability
## Pi-hole Widget
### Must Validate:
* API reachable
* Token valid
* IP correct
* Live refresh updates
* Error states visible
Show:
```text
Cannot reach Pi-hole instance
Check URL or API key
```
---
## Memos Widget
### Must Validate:
Correct fields:
* API endpoint
* token/auth
* user scope
* response parsing
Must not silently fail.
---
## Refresh Button Issue
### Problem
Refresh button exists but cannot be clicked.
Likely causes:
* z-index overlap
* pointer-events disabled
* absolute layer blocking
* drag overlay intercepting clicks
Fix:
```css
pointer-events: auto;
z-index: 10;
```
Drag handles should not block interaction.
---
# 6. Drag & Drop — Highest Priority
## Core Principle
Drag-and-drop is the main feature.
It must feel effortless.
---
## Current Problems
* Dragging unreliable
* No placement preview
* Group movement broken
* Cross-group movement inconsistent
---
## Required Behavior
### Apps Should Be:
* Fully draggable
* Reorderable
* Group movable
* Cross-group movable
* Smooth animations
---
## Placement Preview
When dragging:
Show:
* Highlight insertion slot
* Ghost preview
* Position indicator
Users must know exactly where the app will land.
---
## Recommended Library
### Strong Recommendation
```text
@dnd-kit
```
Why:
* Best React drag library currently
* Excellent collision detection
* Smooth performance
* Group nesting support
* Sortable containers
* Keyboard accessible
---
## App Dragging Behavior
Allow:
```text
Ungrouped → Group
Group → Group
Group → Ungrouped
Reorder inside same group
Move group itself
```
Must be instant.
No modal.
No confirmation.
---
# 7. Apps Section Improvements
## Card View Problems
Current card view:
* Too rectangular
* Icon too small
* Doesn't feel visual enough
---
## New Card View
Make app cards square.
Inspired by CasaOS.
### New Card Structure
```text
┌───────────────┐
│ │
│ ICON │
│ │
│ App Name │
└───────────────┘
```
### Improvements
* Larger icons
* Centered content
* Better spacing
* More visual identity
* Hover interaction
* Rounded corners
Recommended:
```css
aspect-ratio: 1 / 1;
```
---
## List View
Keep mostly unchanged.
List view already works.
---
# 8. Groups System
## Problems
* Poor naming
* Not visually distinct
* Dragging unreliable
* Cannot collapse
---
## New Group Requirements
### Groups Should Support
* Expand/collapse
* Rename
* Drag reorder
* Nested app sorting
* Instant moving between groups
---
## Group Header Layout
```text
Infrastructure ▼ [⋮]
```
Avoid:
```text
GRP2
```
Groups must feel human.
---
## Group Dragging
Group itself should be draggable.
Move entire group section vertically.
---
# 9. Modal Improvements
## Problem
Modals have transparent backgrounds.
This reduces readability.
---
## Fix
Use proper modal surface.
Recommended:
```css
background: var(--surface);
backdrop-filter: blur(16px);
border: 1px solid rgba(255,255,255,.08);
```
No transparent forms.
---
# 10. Add App Flow
## Current Problem
Feels generic.
---
## Rename
Replace:
```text
Add Service
```
with:
```text
Add App
```
---
## Better Flow
### Modal Structure
Step 1:
* Choose app type
Step 2:
* Configure details
Step 3:
* Add icon/logo
Step 4:
* Select group
---
## Add App Card
When adding in-grid:
Small add tile.
Not giant button.
Example:
```text
+ Add App
```
Should visually match app cards.
---
# 11. URL Improvements
## Current Problem
URLs are visually weak.
---
## Better URL Display
Show:
```text
https://app.domain.com
```
With:
* favicon
* hostname extraction
* quick open
* copy button
Example:
```text
🌐 jellyfin.local
```
---
# 12. CasaOS-Inspired Theme
## Goal
Add an optional theme.
Not replacing current dark/light.
Add third style:
```text
CasaOS Inspired
```
---
## CasaOS Characteristics
### Visual Style
* Large spacing
* Rounded containers
* Soft shadows
* Glassmorphism feel
* Bigger cards
* Centered icons
* Calm background
* Floating panels
---
## CasaOS Dashboard Characteristics
### Keep
* App grid focus
* Icon-first navigation
* Background image
* Floating sections
* Minimal chrome
---
## Remove from CasaOS Reference
Do NOT include:
* Search bar
* Storage sync banner
* Drive discovery cards
Only use:
* Layout feel
* Card structure
* App sizing
* Background styling
---
## CasaOS Theme Structure
### Background
Use:
* gradient
* blurred wallpaper
* ambient overlay
---
### Panels
```css
background: rgba(18, 24, 40, 0.65);
backdrop-filter: blur(18px);
border-radius: 24px;
```
---
### App Cards
```css
aspect-ratio: 1;
border-radius: 28px;
transition: transform .2s ease;
```
Hover:
```css
transform: translateY(-3px);
```
---
## Theme Switcher
Add:
```text
Light
Dark
CasaOS Inspired
```
Store in:
```text
localStorage
```
---
# 13. Recommended Tech Stack Improvements
## Layout
```text
react-grid-layout
```
---
## Drag & Drop
```text
@dnd-kit
```
---
## Animations
```text
framer-motion
```
---
## UI Components
```text
shadcn/ui
```
---
## State
```text
zustand
```
---
# 14. Priority Order
## Phase 1 — Critical UX
1. Empty dashboard state
2. Widgets section + apps section
3. Smaller add buttons
4. Drag-and-drop fixes
5. Placement preview
6. Group movement
7. App movement between groups
---
## Phase 2 — Widget System
1. Resizable widgets
2. Widget drag handles
3. Better timezone picker
4. Fix refresh buttons
5. Widget validation
---
## Phase 3 — Visual Improvements
1. Square app cards
2. Better icon sizing
3. Modal redesign
4. Better group styling
5. URL redesign
---
## Phase 4 — CasaOS Theme
1. Theme architecture
2. Background system
3. Glass panels
4. CasaOS grid cards
5. Theme switcher
---
# 15. Biggest Product Rule
The dashboard should feel like:
* A workspace
* A customizable OS
* A clean home-lab control center
* A visual launcher
* Not a traditional admin panel
Users should instantly understand:
* Add
* Move
* Resize
* Organize
* Customize
without reading instructions.