Skip to content

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/ or src/lib/utils/ once reused more than once — this matters more here than it did for the frontend, given the CRUD repetition described below

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 (see tracker-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), reusing LoginForm.svelte's structure.
  • vite.config.ts — same dev-proxy pattern (/api/v1, /statichttp://dev:8000).

UI Migration Order

  1. app shell + sidebar (admin nav)
  2. theme selector
  3. auth/login flow
  4. placeholder dashboard (proves the shell end-to-end)
  5. 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
  6. remaining CRUD quadruplets (brands, production runs, trackers, locations), each should get faster than the last if step 5 produced a real shared scaffold
  7. locations/campaign-management: wire in the already-built tracker-frontend-svelte components (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
  8. Images gallery, Users
  9. 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.svelte and SortableHeaderButton.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.svelte are already prop-driven, not hand-rolled per page)
  • a generic delete-confirmation modal (CampaignDeleteModal.svelte is 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.svelte component 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-svelte location/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, and README.md would 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.