+ up({ wzdx: { ...env.wzdx!, states: v } })}
+ helper="2-letter state codes to include from the WZDx Feed Registry, e.g. ID, OR" />
+ up({ wzdx: { ...env.wzdx!, registry_url: v } })}
+ placeholder="https://datahub.transportation.gov/resource/69qe-yiui.json?$limit=200"
+ helper="FHWA WZDx Feed Registry (Socrata) URL — lists every state DOT feed" />
>
)}
@@ -1291,7 +1327,11 @@ const save = async () => {
>)
case 'satpass': {
- const armedState = !satpassConfig.enabled
+ // Armed state keys off the NATIVE enable (environmental.satpass.enabled),
+ // which is what actually gates the native SGP4 broadcaster — consistent
+ // with the enable-toggle bug fix. dry_run is a Central adapter_config field
+ // (no native equivalent), so it still comes from satpassConfig.
+ const armedState = !env.satpass.enabled
? { label: 'OFF', color: 'text-[#777] bg-[#1a1a1a]', desc: '' }
: satpassConfig.dry_run
? { label: 'DRY RUN', color: 'text-sky-400 bg-sky-400/10 border border-sky-400/30', desc: ' — logging only, nothing transmits' }
@@ -1343,6 +1383,56 @@ const save = async () => {
setSatpassConfig({ ...satpassConfig, norad_ids: v })}
helper="NORAD catalog IDs to broadcast (empty = broadcast nothing, opt-in only)" />
+
+ {/* ── Native satpass (SGP4) ─────────────────────────────────────────
+ Fields on the environmental.satpass YAML layer that drive the local
+ TLE fetcher (env/tle_fetch.py) + SGP4 predictor (env/satpass.py).
+ Only used when this adapter's source = native. `Observers` seeds the
+ observer_locations table (persistence/observer_locations.py). */}
+
+
+ Native satpass (SGP4) — no Central required
+
+
+ {/* Observers — list of {slug, name, lat, lon, alt_m}; seeds observer_locations */}
+
+
Observers (ground stations the predictor computes passes for)
+ { const n = [...env.satpass.observers]; n[i] = { ...o, slug: v }; up({ satpass: { ...env.satpass, observers: n } }) }} placeholder="tvly" />
+ { const n = [...env.satpass.observers]; n[i] = { ...o, name: v }; up({ satpass: { ...env.satpass, observers: n } }) }} placeholder="Treasure Valley" />
+ { const n = [...env.satpass.observers]; n[i] = { ...o, lat: v }; up({ satpass: { ...env.satpass, observers: n } }) }} step={0.0001} />
+ { const n = [...env.satpass.observers]; n[i] = { ...o, lon: v }; up({ satpass: { ...env.satpass, observers: n } }) }} step={0.0001} />
+ { const n = [...env.satpass.observers]; n[i] = { ...o, alt_m: v }; up({ satpass: { ...env.satpass, observers: n } }) }} step={1} />
+
+
+ ))}
+
+
+
+
+ up({ satpass: { ...env.satpass, tle_groups: v } })}
+ helper="Celestrak GP group selectors, e.g. weather, stations, amateur"
+ infoLink="https://celestrak.org/NORAD/elements/" />
+
+ up({ satpass: { ...env.satpass, norad_ids: v } })}
+ helper="Specific NORAD catalog IDs to also fetch/predict, e.g. 25544, 33591" />
+
+
+ up({ satpass: { ...env.satpass, min_elevation_deg: v } })}
+ min={0} max={90} helper="Native SGP4 pass filter (separate from Central min elevation above)" />
+ up({ satpass: { ...env.satpass, window_hours: v } })}
+ min={1} max={168} helper="Hours ahead to predict passes" />
+ up({ satpass: { ...env.satpass, tle_refresh_seconds: v } })}
+ min={3600} helper="How often to re-fetch TLEs (default 21600 = 6h)" />
+
+
)}
}
@@ -1566,8 +1656,16 @@ const save = async () => {
activeAdapter === 'satpass' ? setSatpassConfig({ ...satpassConfig, enabled: v }) : setAdapterField(activeAdapter, { enabled: v })}
+ /* BUG FIX: the native satpass adapter is gated on
+ environmental.satpass.enabled AND feed_source=="native" (see
+ meshai/env/store.py::_register_adapter). The enable toggle previously
+ wrote adapter_config.satpass.enabled (the Central layer), which never
+ reached the native gate. All adapters — satpass included — now drive
+ their YAML `enabled` via the generic setAdapterField path. The native
+ enable is authoritative for the native adapter; the Central
+ adapter_config/satpass panel below is unchanged. */
+ enabled={a[activeAdapter]?.enabled ?? false}
+ onEnabled={(v) => setAdapterField(activeAdapter, { enabled: v })}
feedSource={a[activeAdapter]?.feed_source ?? 'native'}
onFeedSource={(v) => setAdapterField(activeAdapter, { feed_source: v })}
hasCentral={META[activeAdapter].hasCentral}