09-Jul-2026 — Simpler site-hosting configuration behind the scenes #304
fly: collapse per-environment Fly tokens to a single apiKey (#304)
Each environment carried two Fly token fields: an app-scoped deploy apiKey and a separate org-scoped metricsToken, with the metrics code falling back through metricsToken || apiKey. That split was never a requirement - one org token per environment does everything (deploy, restart and read metrics).
Remove metricsToken entirely and resolve a single flyio.apiKey:
- FlyioConfig / FlyRuntimeConfig / FlySecureConfig drop metricsToken; createDefaultFlyioConfig and the FLY_METRICS_TOKEN env var go too.
- fly-runtime-config resolves the one token (env -> encrypted FLY_CONFIG bundle -> staging environments-config fallback); the FLY_CONFIG bundle built at deploy carries only apiToken + appName.
- fly-metrics uses the single token directly for Prometheus.
- Per-env settings UI drops the "Metrics Token" field and relabels the remaining one "API Token", noting it must be org-scoped.
Incidental fix: isIntegrationWorkerAvailable() previously required a metricsToken the worker never had, so it always returned false; it now checks apiToken && appName.
The staging config.environments doc has been consolidated to match: metricsToken removed from all 16 environments and, for the five that were split (ashford, canterbury, ekwg, medway, stag-walkers), the org token promoted metricsToken -> apiKey. Deployed apps keep working and adopt the single-token shape on their next normal deploy.
perf+fix(server+admin): stop area GeoJSON loads pinning 512MB machines, disable clear-all-forwards at zero (#304, #278)
What this fixes
Visiting System Settings for anything at all (staging-lite's case: adding OS maps API keys) instantiated every tab component on page load, so the area-map sync component's ngOnInit fired its preview-districts call, which parsed the 13.9MB bundled areas.geojson into ~170MB of cached heap (raw copy + full-resolution WGS84 district index) for 10 idle minutes. On a 512MB machine that pushed memory to the cap during an unrelated email session. Sites that never use area maps paid the full cost on every visit.
What you'll see
- Opening System Settings no longer loads any area GeoJSON; the MAPS tab component is created only when that tab is first opened, and stays alive afterwards so switching tabs behaves exactly as before
- On sites that do use area maps, opening the MAPS tab now settles at single-digit MB retained instead of ~170MB
- Committee Settings: the Clear all forwards button is disabled when there are no live Cloudflare forwarding rules for the domain, so the confirmation panel can no longer offer to Delete 0 forwards
Technical notes
- preview-districts only ever needed district names and match counts, so it now works entirely off the district index and never touches the raw GeoJSON cache
- The district index is built from a transient direct file read (the raw parse is GC-eligible as soon as the index exists) and each boundary is simplified at 10m tolerance before the BNG-to-WGS84 transform, shrinking the retained index from ~85MB to single-digit MB while keeping point-in-polygon group-to-district matching accurate
- featureCountForDistricts matches either the current LAD24NM name or the older LAD23NM name that group configs may store
- The bundledGeoJson idle cache remains only for endpoints that serve raw geometry: the areas endpoint's no-S3-map fallback and uploadDefaultAreaMap