Frontend Svelte Implementation Plan
Goal
Create a second frontend in tracker-frontend-svelte/ that can be developed in parallel with the current React app, using:
- Svelte 5
- TailwindCSS
- daisyUI
- multiple curated themes
- Docker Compose for local development
The target is not a quick rewrite. The goal is a controlled migration path that preserves the backend API, keeps the current app available, and makes it possible to cut over route by route.
Code Style Rules
The Svelte frontend should be written for Svelte 5, not Svelte 4 legacy syntax.
That means:
- use Svelte 5 runes and current component patterns
- avoid legacy Svelte 4 reactive syntax unless there is a very specific reason
- keep components small and composable
- extract reusable UI and state logic into modules instead of duplicating it
- prefer feature-level decomposition over large monolithic page files
The migration should intentionally avoid huge single-file components. Complex screens should be split into manageable modules, with shared pieces promoted into src/lib/components/ or src/lib/utils/ when they are reused more than once.
Recommended Repo Shape
Use a separate folder instead of mixing React and Svelte inside the same frontend package.
Suggested layout:
tracker-frontend-svelte/
├── build/
│ └── dev/
├── public/
├── src/
│ ├── lib/
│ │ ├── api/
│ │ ├── components/
│ │ ├── theme/
│ │ └── utils/
│ ├── routes/
│ ├── app.css
│ ├── app.html
│ └── main.ts
├── package.json
├── tailwind.config.ts
├── vite.config.ts
└── svelte.config.js
Keep the Svelte app self-contained so it can be:
- started independently in development
- built independently in CI
- deployed independently until parity is reached
Docker Plan
Add a new Compose service for the Svelte frontend alongside the existing React service.
Recommended dev service shape:
- build context:
./tracker-frontend-svelte - Dockerfile:
build/dev/Dockerfile - port mapping:
3101:3000or similar - volume mount:
./tracker-frontend-svelte:/app - env file:
./tracker-frontend-svelte/.env - dependency on the API dev service, not on the React frontend
This keeps the development workflow consistent with the current frontend:
- source edits remain hot-reloaded
- API requests still go through the local backend
- the new app can be tested without replacing the old one
Use a second production image only after the Svelte app is feature-complete enough to be promoted.
Theme System
This should be a curated daisyUI theme setup, not a single dark/light toggle.
Start with a small, stable set of themes:
lightdarkcorporateretro
If the team wants more variety after the first pass, the next sensible additions are:
dimnighttokyonightnordnorddark
The theme system should have three layers:
tailwind.config.tsdefines the allowed daisyUI themes.app.htmlapplies the initial theme before the app renders.- A Svelte theme selector updates the active theme and persists it in
localStorage.
That gives you:
- no flash of the wrong theme on first paint
- browser
color-schemealignment - a single source of truth for supported themes
- a path to expand styling without redesigning the whole app
Theme Files
Use a small theme module similar to the Cortex pattern.
Suggested files:
src/lib/theme/index.tssrc/lib/theme/themes.tssrc/lib/components/ThemeSelector.svelte
Responsibilities:
- resolve the current theme from
localStorage - fall back to the system theme when nothing is stored
- set
document.documentElement.dataset.theme - set
document.documentElement.style.colorScheme - expose the curated theme list to the selector
The selector should live in the app shell so every page gets the same theme controls.
Startup Behavior
Implement theme bootstrapping in app.html so the page starts with the right theme before Svelte mounts.
The boot script should:
- read the stored theme key
- validate it against the supported theme list
- fall back to
prefers-color-scheme - set the root
data-theme - set the browser
color-scheme
This matters more in Svelte than it might seem, because daisyUI themes affect a lot of surface styling. If the initial theme is applied late, the user will see a jarring flash.
Svelte 5 Setup
The first Svelte milestone should be thin and boring:
- Svelte 5 project scaffolding
- Vite dev server
- Tailwind and daisyUI wired together
- app shell layout
- theme selector
- API client wrapper
- auth/session bootstrap
Avoid porting large feature pages before the shell is stable.
UI Migration Order
The UI should move in this order:
- app shell
- theme selector
- buttons, inputs, selects, textareas
- modal and drawer primitives
- cards, tabs, badges, alerts
- table and pagination primitives
- login and auth flows
- dashboard and tracker list pages
- location CRUD and import flows
- campaign management
- map-heavy screens
This order front-loads the shared design system and reduces duplicated styling work.
DRY Structure Guidance
Keep the new app DRY by design:
- shared form controls should be reused across login, locations, campaigns, and reports
- map controls should be separate from map page containers
- modal shells should be shared instead of reimplemented per feature
- table layout and pagination should be centralized
- theme logic should live in one module and one selector component
If a page starts to grow large, split it by responsibility rather than keeping everything in one component. Good boundaries are:
- page container
- data loading
- state and derived values
- presentational components
- feature-specific controls
- API adapter or store logic
That keeps the Svelte app maintainable while the rewrite is still in flight.
Custom Component Replacement Priority
Replace the most reusable custom React components first.
Good first candidates:
- layout shell
- top navigation
- sidebar
- theme switcher
- button variants
- form fields
- modal shells
- dropdown menus
- table wrappers
- pagination controls
These components are the best place to introduce daisyUI because they appear across many routes.
Lower priority components:
- map overlays
- tracker history visualisations
- complex batch-action dialogs
- Leaflet custom controls
- route-specific dashboards with dense filtering logic
Those should be ported after the shared primitives are stable.
Leaflet Plan
Keep Leaflet.
The migration should replace React wrappers, not the map library itself. The Svelte version should use:
- direct Leaflet integration where practical
- Svelte lifecycle hooks for map setup and teardown
- small wrapper components for reusable map UI
The main risk is not Leaflet itself. The risk is rebuilding the current interaction model cleanly without leaking DOM and state issues across route changes and modal opens.
Suggested Phase Breakdown
Phase 1: Scaffold
- create
tracker-frontend-svelte/ - add the Svelte build and dev container
- add Tailwind and daisyUI
- add the theme system
- add the app shell
Phase 2: Shared Foundation
- implement API client and auth/session handling
- port the theme selector
- port the top-level layout components
- establish a reusable component library for form and layout primitives
Phase 3: Core Routes
- login
- dashboard
- trackers
- locations
- reports
Phase 4: Complex Features
- campaigns
- Leaflet-driven maps
- editable maps
- import flows
Phase 5: Cutover
- compare parity route by route
- remove dead React-only code paths
- switch deployment traffic when the Svelte app is stable
Risks To Track
- theme palette drift if the allowed theme list is not centralized
- extra UI churn if custom components are rebuilt before the theme system is stable
- Leaflet DOM sizing issues during mount and unmount
- duplicated API logic if the new app diverges from the existing request layer
- scope creep if the visual refresh becomes a redesign
Practical Recommendation
If the team wants better styling flexibility, the best combination is:
- Svelte 5 for the new frontend
- TailwindCSS for utility-level layout
- daisyUI for component primitives
- a curated theme switcher from day one
That gives you more visual options without turning the migration into a purely bespoke CSS project.