TODO
Frontend
Dashboard
- On the map I'd like to be able to right click and have an option to create a location (storage or delivery) at the point selected.
- The location editor now opens with the clicked coordinates prefilled.
- Clicking an existing zone now opens the popup with an Edit button that launches the location editor.
- Adding a location that overlaps an existing geofence in the same campaign is prevented, but the message "Unable to save location." is too vague to help the user understand why.
- When deleting a location, do we consider cascading it to remove history records where a tracker entered the zone?
Campaign Management
- The Open locations button at the top of the page is unnecessary.
Removed from the admin app's Campaign Management page
(
tracker-admin-svelte/.../clients/+page.svelte) -- it duplicated the "Open locations" icon already on each campaign/child-run card.
Unified CRUD
- On the campaign management page the interface does not work well on mobile.
- I think the mobile display should stack as rows not columns, so each selection fills beneath the next.
- On selecting a client the mobile interface should jump to brand, on
selecting a brand jump to campaign. Fixed all three together: the
Clients/Brands/Campaigns/Child runs columns were a fixed-height
side-by-side row, which just doesn't fit four columns on a phone.
Built a shared
EntityColumnGroupcomponent (both apps' campaign management pages now use it, replacing four hand-writtenEntityColumns each) that keeps the side-by-side layout at thelgbreakpoint and up, and below it stacks the columns as full-width rows, each with its own capped height and internal scroll. Selecting an item smooth-scrolls the next column into view (mobile only -- on desktop everything's already visible side by side).
Tracker Cards
- The tooltips on mobile flow off screen to the left for cards that are in the left column. Can the tooltips be aligned to the right for left hand cards? Fixed by flipping the status tooltip outward on compact card grids, while leaving the wider layouts unchanged.
- Trying to scroll in mobile causes the drag/drop trigger and the wheel appears. Is there anything to be done to make scrolling better? Fixed by allowing vertical touch panning on move-enabled cards/rows and only starting the radial drag wheel from mouse input.
Admin Panel
Users
- The user invite gets sent out with the link to the admin panel.
- The invite should have the frontend link, unless they are an admin,
then it should have both. Fixed: the invite link was derived from
whichever app's origin submitted the create-user request (always
the admin panel, since that's the only place
POST /usersis called from), not the invited user's own role. Now role-based —ADMIN_URL/FRONTEND_URLare both real settings, and the email includes the admin panel link only when"admin"is in the new user's roles. - The user cards should behave like tracker cards. The edit/delete only
appears on hover. Fixed in the shared
EntityCardcomponent (also used by clients and campaign management), so all three pick up the same hover-reveal behaviour asTrackerCard. - User cards could take up less space. Two columns, unless mobile?
Added an opt-in
columnsprop toEntityColumn(default stays 1, so clients/campaign management are unaffected) and set it to 2 on the Users page; falls back to a single column below thesmbreakpoint. - Cards could show the clients they have access to instead of just "1 client" Now lists the actual client names (comma-separated, capped at 3 with "+N more"), resolved from the clients already loaded for the editor modal.
- Add a filter to show users with specific client access. Added a
"Client access" dropdown next to the Role filter. Along the way,
found that
User.client_list/User.rolesused the genericsqlalchemy.ARRAYtype instead of the Postgres-specific one, so.contains()raisedNotImplementedErrorat query time -- this was already broken for the pre-existing Role filter too, just never exercised by a test. Fixed the column type (no migration needed -- the actual DB column was always a real Postgres array; this was a Python-level type annotation bug). Admins match every client filter since they implicitly have access to all clients. - Check that no password reset email is sent unless the user is using local auth.
Authentication
better-auth
- Did we over engineer our own auth? Would be better using https://better-auth.com/docs/concepts/oauth
Discussed and rejected. The current JWT + OIDC provider-registry setup
(
app/core/oidc.py) is a fairly standard size for what it does, not over-engineered. better-auth is TypeScript-only (expects a Node/Bun/Deno backend with Drizzle/Prisma/Kysely) -- this app's auth lives in the Python/FastAPI backend, so adopting it would mean standing up a separate Node auth microservice or rewriting auth in TypeScript, not a drop-in swap. - Would it give us scope for other auths facebook/github?
Not a reason to switch -- GitHub/Facebook fit the existing
_PROVIDERS-dict pattern already used for Google/Microsoft (with per-provider quirks, e.g. GitHub isn't strictly OIDC-compliant so it needs a token-exchange/userinfo step instead of JWKS validation). A scoped addition to the current architecture, not a reason to migrate.
Backend API
Alembic
- Can we merge the alembic migrations so we don't have branches?
Linearized both branch points (
alembic headswas already a single head -- the "branches" were two already-resolved fork/merge pairs inside the history, not a live problem). Checked every environment's current stamped revision first (local dev and staging both at head; production intentionally 11 migrations behind, on46408209ddfd, well before either fork) to confirm nothing was stamped at a revision being removed. Reordered the two branch pairs by creation date, repointeddown_revisions to chain them sequentially instead of merging, and deleted the now-unneeded pure no-op merge revision (ab12cd34ef56; the other merge point,e3f1a2b4c5d6, had real schema changes so it was kept, just with a singledown_revisioninstead of a tuple). Verified by building a completely fresh database through the whole chain from scratch -- all 36 revisions apply cleanly in the new linear order, ending at the same head (d0e1f2a3b4c5) as before. - Once we get staging, and production to the current head we should flatten the migrations using
alembic revision --autogenerate -m "Schema Baseline"
Notifications
- Allow a user to set alert/notifications of tracker devices. Get notified by email on status changes.
- Extend notification/alerts for when a tracker has "gone dark".
Schema Improvement Notes
- Several geography-backed tables currently expose both legacy and modern GIST index names on the same column. If backward compatibility is no longer required, consider keeping one canonical index per table.
- If OAuth sign-in becomes the dominant login path, adding an index on users.auth_provider or users.provider_subject_id may help lookup-heavy auth flows.
- Short-lived tables such as password_reset_tokens may benefit from an explicit retention or cleanup policy being documented alongside the schema.
Support Tooling
- Built an MCP-based read-only diagnostic server so an AI agent can help triage "tracker hasn't reported" / "not in the expected zone" support cases. See docs/development/mcp-diagnostic-server-design.md for the on-demand ECS + SSM tunnel access pattern and tool surface. Live in local dev and staging; prod still needs its IAM policy attached and secret/role bootstrapped (see that doc's Next Steps).
Infrastructure
- Consider moving the stack from amd64 to arm64 (Graviton) for cost/perf.
See docs/development/arm64-migration-assessment.md
for what's actually involved - the Fargate/container side is a fairly
clean win, but the self-managed EC2 Postgres/TimescaleDB host
(
infra/modules/database) is a separate, higher-risk migration that shouldn't be coupled to it.
Geofence Status Pipeline
- Investigate why some trackers sit inside a delivery/storage geofence
but
current_statusnever updates fromIN_TRANSIT. Surfaced by thefind_status_zone_mismatchesMCP tool (see docs/development/mcp-diagnostic-server-design.md) - confirmed 4 real cases in dev data, all production run 108, all ~12-32m inside the "A&S Packing" storage zone. Likely candidate:services/unified_geofence_service/taskiq_service.py's_determine_status_with_hysteresis/_handle_no_status_changepath. Once staging/prod MCP access is live, run the same tool there first to see how widespread this is before deciding on a fix.
Test Suite
- Fix pre-existing test failures surfaced while verifying the
tracker_status_serviceremoval (2026-07-23) - confirmed unrelated to that removal by stashing it out and re-running; same failures exist onmain. -
tests/behaviors/geofence_management/test_unified_geofence_processing.pyandtests/behaviors/services/test_unified_geofence_service.pyfail to collect (ModuleNotFoundError) - both importfrom services.unified_geofence_service.service import UnifiedGeofenceService, but the actual module isservices/unified_geofence_service/taskiq_service.py, notservice.py. Dates to 2025-11-14. -
tests/integration/database/test_identity_column_implementation.py::TestForeignKeyIntegrity::test_cascade_deletion_maintains_integrityfails. -
tests/test_main.py::test_api_router_inclusionfails withAttributeError: '_Incl...'. -
tests/unit/core/test_config.py::TestSettings::test_settings_cors_origins_defaultfails. -
tests/unit/models/test_location_coordinates.py::TestDeliveryLocationCoordinates::test_get_coordinates_casts_geography_to_geometryfails - the test'sMock()doesn't produce real floats, soget_coordinatesraisesfloat() argument must be a string or a real number, not 'Mock'internally and returnsNones.
Documentation
- We need to look at replacing MkDocs if we want to keep Material. See docs/development/docs-platform-evaluation.md for the current analysis of MkDocs 2.0, Zensical, plugin compatibility, and Cloudflare hosting impact.
Usage Guide
- The user guide needs to be updated to cover the new design and move to the unified crud in Campaign Management Added a "Managing Clients, Brands, and Campaigns" section covering the drill-down interaction, hover-to-reveal edit/delete, mobile behavior, child runs, and per-run tracker management. Cross-linked from "Setup — Required Order".
Remove Stale Documents
- We need to do a review of documentation and remove cruft relating to the previous React version and any other outdated materials. Rewrote the Admin Panel (8 files) and Front End (7 files) sections wholesale -- both described the deleted React/react-admin apps as current, with dead paths and code samples. Fixed scattered stale mentions in docs/index.md, reference/system-architecture.md, and several guides/services docs. Left legitimate historical/migration docs (the Svelte migration assessments, the cutover TODO) untouched -- they're supposed to talk about the old React app for comparison.
- The documentation requires a restructure as the top level menu contains too many items and it looks disorganised.
Root cause:
mkdocs-awesome-pages-plugindefaults to reading.pagesfiles, but every nav file in this repo is named.nav.ymland the plugin was never told to look for that instead -- so none of the curated navigation was ever actually being read, and mkdocs was silently falling back to a flat alphabetical dump of all ~90 pages. Fixed the plugin config (one line), fixed the"*"wildcard token (should be"..."for this plugin version), fixed a broken nested-path reference and a dead redoc link, and moved/removed the handful of stray top-level directories that had no real content (an empty dir, a confusingly-duplicatedatabase/, asset-only dirs). Verified by building the full site and confirming the rendered nav matches the curated structure.
General Behaviour
- When a tracker changes status to delivered consider not tracking any further activity.
- We could remove it from the queue so it no longer asks Apple for FindMy reports
- or do we keep tracking but never change status from delivered?