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

21 KiB

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:

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:

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

Dashboard
Group
Service
ServiceUrl
WidgetInstance
WidgetData
AssetFile
ErrorResponse

Required frontend API hooks:

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:

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:

Widgets ------------------------------------- [+]
(empty state)

Apps ---------------------------------------- [+]
(empty state)

Empty State Design

Empty states should feel premium.

Example:

No widgets yet
Create your first widget to customize your dashboard.

and:

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:

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:

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

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:

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:

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:

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:

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.


Strong Recommendation

@dnd-kit

Why:

  • Best React drag library currently
  • Excellent collision detection
  • Smooth performance
  • Group nesting support
  • Sortable containers
  • Keyboard accessible

App Dragging Behavior

Allow:

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

┌───────────────┐
│               │
│     ICON      │
│               │
│  App Name     │
└───────────────┘

Improvements

  • Larger icons
  • Centered content
  • Better spacing
  • More visual identity
  • Hover interaction
  • Rounded corners

Recommended:

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

Infrastructure ▼           [⋮]

Avoid:

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:

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:

Add Service

with:

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:

+ Add App

Should visually match app cards.


11. URL Improvements

Current Problem

URLs are visually weak.


Better URL Display

Show:

https://app.domain.com

With:

  • favicon
  • hostname extraction
  • quick open
  • copy button

Example:

🌐 jellyfin.local

12. CasaOS-Inspired Theme

Goal

Add an optional theme.

Not replacing current dark/light.

Add third style:

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

background: rgba(18, 24, 40, 0.65);
backdrop-filter: blur(18px);
border-radius: 24px;

App Cards

aspect-ratio: 1;
border-radius: 28px;
transition: transform .2s ease;

Hover:

transform: translateY(-3px);

Theme Switcher

Add:

Light
Dark
CasaOS Inspired

Store in:

localStorage

13. Recommended Tech Stack Improvements

Layout

react-grid-layout

Drag & Drop

@dnd-kit

Animations

framer-motion

UI Components

shadcn/ui

State

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.