Admin Svelte Implementation Plan
Goal
Create a second admin panel in tracker-admin-svelte/ that can be developed in
parallel with the current React tracker-admin app, reusing as much of
tracker-frontend-svelte as is genuinely reusable: the app shell, theme system, API
client, auth/session logic, and (once its own CRUD pages exist) the locations and
campaign-management components.
Unlike the original frontend migration, this is not a from-scratch design exercise —
tracker-frontend-svelte already answered the framework, theming, and tooling
questions. The work here is porting the generic pieces, building tracker-admin's own
navigation and pages on top of them, and deciding how much of the CRUD volume to
generalize before repeating it five times. The first admin CRUD surface should be a
combined clients/brands/campaigns workspace rather than separate one-off pages, so
the shared scaffold is designed around linked columns, in-column filtering, and
frontend-compatible pagination from the start.
Code Style Rules
Same rules as the frontend migration, unchanged:
- Svelte 5 runes only, no Svelte 4 legacy syntax
- keep components small and composable
- extract reusable UI/state logic into modules instead of duplicating it
- prefer feature-level decomposition over large monolithic page files
- promote shared pieces into
src/lib/components/orsrc/lib/utils/once reused more than once — this matters more here than it did for the frontend, given the CRUD repetition described below
Recommended Repo Shape
Same shape as tracker-frontend-svelte:
tracker-admin-svelte/
├── build/
│ └── dev/
├── static/
├── src/
│ ├── lib/
│ │ ├── api/
│ │ ├── components/
│ │ ├── state/
│ │ ├── theme/
│ │ └── utils/
│ ├── routes/
│ ├── app.css
│ ├── app.html
│ └── app.d.ts
├── package.json
├── tailwind.config.ts
├── vite.config.ts
├── svelte.config.js
└── tsconfig.json
Docker Plan
New Compose service alongside both existing admin/frontend services:
admin-svelte-dev:
build:
context: ./tracker-admin-svelte
dockerfile: build/dev/Dockerfile
ports:
- 3102:3000
volumes:
- ./tracker-admin-svelte:/app
- ./openapi.json:/app/openapi.json:ro
env_file:
- ./tracker-admin-svelte/.env
depends_on:
- dev
networks:
- tracker-network
restart: unless-stopped
user: root
Port 3102 continues the existing 310x frontend-family numbering (3100 React
frontend, 3101 Svelte frontend) rather than reusing 3000 (the old admin's own slot).
build/dev/Dockerfile and build/dev/entrypoint.sh are framework-agnostic and can be
copied from tracker-frontend-svelte/build/dev/ unchanged.
What Gets Ported Verbatim
These files have no app-specific coupling and can move over close to as-is:
| File | Notes |
|---|---|
src/lib/api/client.ts |
Fetch wrapper, ApiError, token storage — only assumption is VITE_API_URL and the token localStorage key, both fine to keep |
src/lib/api/auth.ts + src/lib/state/auth.ts |
Same backend endpoints tracker-frontend-svelte already uses (/auth/login/json, /auth/refresh-cookie, /auth/logout, /users/me) |
src/lib/theme/index.ts, src/lib/theme/themes.ts |
Zero app coupling |
src/lib/components/ThemeSelector.svelte |
Only external dependency is the collapsed prop |
src/lib/utils/animatedDialog.svelte.ts |
Zero app coupling |
daisyUI theme blocks + dialog animation CSS in app.css |
Copy the @plugin "daisyui" { ... } theme list, the custom theme color blocks, and the dialog.modal/.modal-closing rules |
build/dev/Dockerfile, build/dev/entrypoint.sh |
Framework-agnostic |
svelte.config.js (adapter-static, SPA fallback), tailwind.config.ts |
Same choice applies: no SSR need for an API-backed admin panel |
What Gets Ported and Adapted
src/lib/components/AppShell.svelte+Sidebar.svelte— same collapse/mobile-drawer structure, but the nav array changes to tracker-admin's actual 8 items (seetracker-admin/src/components/layout/MainLayout.tsx:31-38):
| Label | Route |
|---|---|
| Dashboard | / |
| Clients | /clients |
| Brands | /brands |
| Production Runs | /production-runs |
| Trackers | /trackers |
| Locations | /locations |
| Images | /images |
| Users | /users |
Health Dashboard and Service Controller are out of scope (unused — see the assessment doc) and stay out of this list; Tracker Heatmaps is deferred to Phase 4 and can join the nav then.
src/routes/+layout.svelte,(app)/+layout.svelte,(auth)/+layout.svelte,(auth)/login/+page.svelte— same auth-gating pattern (initializeAuth()/redirect-if-unauthenticated, redirect-if-authenticated), reusingLoginForm.svelte's structure.vite.config.ts— same dev-proxy pattern (/api/v1,/static→http://dev:8000).
UI Migration Order
- app shell + sidebar (admin nav)
- theme selector
- auth/login flow
- placeholder dashboard (proves the shell end-to-end)
- first CRUD quadruplet, built as a reusable pattern, not a one-off — this is the highest-leverage decision in the whole migration, since four more domains repeat it
- remaining CRUD quadruplets (brands, production runs, trackers, locations), each should get faster than the last if step 5 produced a real shared scaffold
- locations/campaign-management: wire in the already-built
tracker-frontend-sveltecomponents (LocationEditorModal.svelte,LocationsImportModal.svelte,CampaignEditorModal.svelte,CampaignRow.svelte,TrackerCard.svelte/TrackerListRow.svelte,MoveTrackerModal.svelte,PasteSelectModal.svelte,CampaignDeleteModal.svelte) rather than rebuilding admin-specific equivalents from scratch - Images gallery, Users
- Tracker Heatmaps — no existing precedent, treat as fresh design work (charting approach needs its own small spike, similar to how Leaflet got one in the original migration). Health Dashboard and Service Controller are excluded (unused).
DRY Structure Guidance
Same guidance as the frontend plan, with one addition specific to this app: because
five domains (clients/brands/production-runs/trackers/locations) share the same
List/Detail/Create/Edit shape, resist building each as a bespoke page. Reasonable
shared boundaries to design once, in src/lib/components/:
- a generic paginated/sortable table wrapper (the frontend already has
src/lib/components/Pagination.svelteandSortableHeaderButton.svelte— reuse those directly rather than rebuilding) - a generic filter panel shell, including per-column text inputs where the combined CRUD surface needs inline narrowing
- a generic create/edit form shell (field list in, save/cancel callbacks out — mirrors
how
CampaignEditorModal.svelte/LocationEditorModal.svelteare already prop-driven, not hand-rolled per page) - a generic delete-confirmation modal (
CampaignDeleteModal.svelteis already exactly this shape and could be generalized/renamed rather than copied per domain)
The combined clients/brands/campaigns flow should be treated as the proving ground for that scaffold. It needs to preserve the frontend campaign tracker card motion when rendering entity cards, and it should share the same pagination component and visual layout so the admin and frontend surfaces feel related instead of adjacent-by-accident.
If a quadruplet ends up needing something the shared scaffold doesn't support, extend the scaffold — don't fork it.
Custom Component Replacement Priority
Good first candidates (already built, just need porting/wiring):
- app shell, sidebar, theme switcher (verbatim/adapted per above)
- modal shell (
AnimatedDialog) - pagination, sortable headers
- location editor/import modals, campaign editor/row/delete modals
Lower priority (no existing precedent, design fresh):
- tracker status heatmap visualization
- images gallery
Excluded entirely (unused, not part of this migration): Health Dashboard, Service Controller.
Charting Plan
Tracker Heatmaps currently uses chart.js + react-chartjs-2 (Health Dashboard also
did, but is excluded from this migration — see "Not In Scope" in the assessment doc).
Keep Chart.js itself (same reasoning as keeping Leaflet in the frontend migration — the
underlying library is framework-agnostic); replace the React wrapper with direct
Chart.js calls from Svelte lifecycle (onMount/onDestroy to create/tear down the
chart instance against a bound canvas element). This has no existing precedent in
tracker-frontend-svelte to copy from, so budget a small spike before committing to
the pattern.
Suggested Phase Breakdown
Phase 1: Scaffold (this pass)
- create
tracker-admin-svelte/ - add the Svelte build and dev container (
admin-svelte-dev, port 3102) - port theme system, API client, auth/session verbatim
- app shell + sidebar with tracker-admin's real nav
- login flow wired to the shared backend
- placeholder dashboard page
Phase 2: First CRUD Quadruplet + Shared Scaffold
- pick one domain slice, starting with the combined clients/brands/campaigns flow, and build its linked List/Detail/Create/Edit experience as a genuinely reusable pattern
- include in-column text filters at the top of each entity column where they narrow the current list without leaving the workspace
- reuse the frontend
Pagination.sveltecomponent and its page-size controls so the combined admin CRUD uses the same pager appearance and behavior as campaign management - match the frontend campaign tracker card animation for selectable cards in the combined CRUD columns, so hover/focus/selected states behave consistently
- extract the shared table/filter/form/delete-confirm primitives from that work
Phase 3: Remaining CRUD Quadruplets
- brands, production runs, trackers, locations, using the Phase 2 scaffold
- wire in the existing
tracker-frontend-sveltelocation/campaign components rather than rebuilding equivalents
Phase 4: Singleton Pages
- images gallery, users
- tracker heatmaps (fresh design, no precedent)
- Health Dashboard and Service Controller excluded (unused)
User CRUD should stay mail-agnostic for now. Invite emails and forgotten-password reset emails are a shared future task once SMTP/config plumbing exists, because both features need the same delivery, token, and template infrastructure.
Phase 5: Cutover
- compare parity route by route against the React app for everything actually ported
- remove dead React-only code paths
- switch deployment traffic when stable (this is also when
docker-bake.hcl,scripts/build_images_common.py, Terraform,run_frontend_tests.sh, andREADME.mdwould need updating — none of that is part of this dev-only scaffold)
Risks To Track
- CRUD quadruplet scaffold decisions made in Phase 2 are expensive to change later — don't rush past that phase to start "shipping pages."
- Chart.js/Svelte lifecycle integration is unproven in this codebase; validate early.
- Test coverage will need to be built from near-zero (tracker-admin has one test file today).
- This app's blast radius is every entity in the system — be stricter than usual about scope creep during the rewrite.
Practical Recommendation
Land Phase 1 (this pass) as a working, authenticated, themed shell with nothing but a placeholder dashboard behind it — enough to prove the side-by-side setup works and to give the next phase a real foundation to build the first CRUD scaffold against, rather than trying to port everything at once.