Skip to content

Info exchange tenant support (first onboarding: EWYAIX) #269

@rustynwac

Description

@rustynwac

Re-scope note (2026-06-11): Original issue asked to add the Eastern Idaho Info Exchange. After /grill-me design session, this issue is re-scoped to deliver the platform-wide info-exchange support with EWYAIX as the first concrete onboarding (they're already registered with NAC and have a name + domain). Eastern Idaho onboarding is tracked in #1111 and is a one-line entry once their group is named.

Problem

We need to support "info exchange" centers on the platform. These centers that don't issue avalanche forecasts but do publish observations and host weather stations. The NAC API already models these (EWYAIX, SOAIX) but our codebase excludes them due to schema strictness and a "every tenant is a forecasting center" assumption baked into provisioning and routing.

The first concrete onboarding target is Eastern Wyoming Avalanche Info Exchange (EWYAIX, ewyoavalanche.org). The platform work here also unblocks #1111 (Eastern Idaho) the day their group is named.

Goals

  • Treat info exchanges as a regular tenant flavor — no new entity type, no schema discriminator, no separate code path.
  • Drive forecast / weather / stations / observations visibility from NAC's platforms flags (already correctly populated for info exchanges).
  • Seed EWYAIX as a real tenant in our DB so we can verify the platform changes end-to-end against real NAC data.

Non-Goals (explicitly out of scope for this issue)

Approach

1. Tenant identity uses NAC platform flags, not a local discriminator

There is no kind field added to the Tenants collection. "Is this an info exchange" is identical to !platforms.forecasts && platforms.obs, which is also the operational definition of an info exchange (they don't forecast; they do publish obs).

2. Schema relaxation in src/services/nac/types/schemas.ts

avalancheCenterSchema currently rejects EWYAIX because:

Field EWYAIX returns Change
city null Make nullable
config null Make nullable. No code reads metadata.config.* directly, so the inner schema is untouched.
type \"other\" Add Other = 'other' to the AvalancheCenterType enum

Sanity-check: this should now also parse SOAIX (which was excluded for the same reasons). Worth a fixture test against both centers.

3. Route-level gating on platform flags

  • Homepage route (src/app/(frontend)/[center]/page.tsx) unconditionally renders <NACWidget widget=\"warnings\" />. Gate on platforms.warnings so it doesn't render empty for info exchanges. (Also a correctness win for any future center without warnings.)
  • The danger map widget stays as-is — info-exchange zones render as gray "no rating" polygons, which matches what EWYAIX shows today.

4. Provisioning changes in src/collections/Tenants/endpoints/provisionTenant.ts

  • BUILT_IN_PAGES: gate the "Weather Stations" entry on platforms.stations instead of including it unconditionally. EWYAIX has stations: true, so this is a no-op for them, but it's correctness for any future info exchange without stations.
  • resolveBuiltInPages: when platforms.forecasts === false, skip the AFP zones fetch entirely and produce zero forecast pages. Today the no-zones fallback creates a stale "All Forecasts" page; that fallback should also be gated on platforms.forecasts.
  • PAGES_TO_PROVISION stays as today for forecasting centers. For info exchanges (platforms.forecasts === false), provision a reduced set: About Us, Donate / Membership, Volunteer. (Other pages can be added by admins as needed.)
  • HomePages provisioning: when platforms.forecasts === false:
    • highlightedContent columns use info-exchange copy (drop "avalanche forecasts, mountain weather conditions" — phrase as "the latest avalanche observations and safety information from across our region").
    • layout contains a single observationsWidget block (see §5) instead of the EventList block.

5. New ObservationsWidget block

  • Slug: observationsWidget. Lives at src/blocks/ObservationsWidget/.
  • No fields. The block renders the same content as the /observations page: heading ("Observations"), Submit Observation button, <ObservationsDisclaimer />, <NACWidget widget=\"observations\" />.
  • Add to src/blocks/RenderBlocks.tsx.
  • HomePages collection (layout field) gains it via the existing blocks array.
  • Generate types after the field is added.

6. Add EWYAIX to AVALANCHE_CENTERS

In src/utilities/tenancy/avalancheCenters.ts:

```ts
ewyaix: {
name: 'Eastern Wyoming Avalanche Info Exchange',
customDomain: 'ewyoavalanche.org',
},
```

Comment in the file should be updated — EWYAIX is no longer "excluded due to no config object"; it's now supported.

Note: customDomain is canonical/configurational. Adding the entry does not flip DNS — that's the work tracked in #1110.

7. Seed the EWYAIX tenant

Run provisionTenant for ewyaix against prod (and as part of pnpm seed for local dev). Smoke-verify:

  • Homepage renders without the forecasts/weather tabs in nav.
  • Homepage layout shows the observations widget.
  • /observations, /observations/submit, /weather/stations/map work.
  • /forecasts/avalanche, /weather/forecast 404 (existing platform-flag gate handles this).
  • About Us, Donate / Membership, Volunteer pages exist in the admin Pages list.

Migrations

  • One Payload migration for the new observationsWidget block being available on HomePages. pnpm payload migrate:create add_observations_widget_block.
  • No migration needed for the schema relaxation (it's runtime parsing, not DB schema).

Risks & mitigations

  • Schema relaxation regresses forecasting tenants: mitigate with a fixture test parsing both NWAC (full data) and EWYAIX (nulls) responses.
  • Provisioning over-creates for info exchanges: the gating is a single platforms.forecasts branch — easy to unit-test on resolveBuiltInPages.
  • obs_view_url / obs_form_url in NAC widget config: verified unused by our integration. NWAC's value is a hash-routed URL and NWAC works fine. No action needed.

Follow-ups (tracked separately)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions