Summary
Wire the ProtoFleet infrastructure-devices UI (/fleet/infrastructure) to the InfrastructureService that landed in #724, replacing the local-React-state device list with persisted CRUD. Second PR of the facility-fan curtailment series; full design in docs/plans/2026-07-09-facility-fan-curtailment-integration-plan.md (Phase 2, including the post-PR 1 implementation prep notes).
No device behavior activates from this work — the write path lands with the later Modbus I/O and reconciler-sequencing phases. This is inventory/CRUD surface only.
Scope
infrastructureClient registration in client/src/protoFleet/api/clients.ts + new useInfrastructureDevices hook (list/create/update/delete), mirroring useCurtailmentResponseProfiles
FleetInfraPage / InfraDeviceList fetch devices from the API; state ownership moves from InfraDeviceList.localDevices into the hook, with devices + mutation callbacks passed down
ManualAddStep rework for the adapter model: driver-type select (Modbus TCP, sole option) rendering a per-driver-type form module (e.g. driverForms/modbusTcp.tsx) that owns driver_config encode/decode; register-address field takes the raw application address with field-help copy warning against the 4xxxx convention and noting the wire-level off-by-one
- Each form module also exports a connection-summary renderer for a single generic "Connection" list column and the detail modal's info rows (endpoint/port/unit-ID move into opaque
driver_config)
- Detail-modal edit rework onto the same form module, submitting through the update/delete RPCs — the detail modal stays the canonical edit surface
- API-backed list states: list-level loading and fetch-error with retry; add/edit modals surface RPC failures inline (
actionError banner pattern); the Enabled switch reverts on failure with an error toast, sending only the optional bool enabled (server preserves omitted fields)
- Graceful degradation where
driver_config is redacted (empty string) for site:read-only callers — connection summary renders an em dash or hides rather than failing to parse
- Hide the Status and Last-seen columns, their filters, the offline Alert styling, and the detail modal's status header until v2 read-back can populate them
- Tests: extend
InfraDeviceList.test.tsx, ManualAddStep.test.tsx, InfraDeviceDetailModal.test.tsx, FleetInfraPage.test.tsx; add an API-hook test (mirror useCurtailmentResponseProfiles.test.tsx)
Out of scope (later phases of the fan integration)
- Response-profile fan settings (
facility_fan_device_ids, delays) + the device deletion reference-guard that depends on that column (PR 3)
- Modbus TCP write path + dev simulator (PR 4)
- Reconciler fan sequencing (PR 5)
Repo hygiene
No proto/schema changes expected; routing is already wired (/fleet/infrastructure is routed and prefetched). Feature branch (never main); just lint before PR.
Summary
Wire the ProtoFleet infrastructure-devices UI (
/fleet/infrastructure) to theInfrastructureServicethat landed in #724, replacing the local-React-state device list with persisted CRUD. Second PR of the facility-fan curtailment series; full design indocs/plans/2026-07-09-facility-fan-curtailment-integration-plan.md(Phase 2, including the post-PR 1 implementation prep notes).No device behavior activates from this work — the write path lands with the later Modbus I/O and reconciler-sequencing phases. This is inventory/CRUD surface only.
Scope
infrastructureClientregistration inclient/src/protoFleet/api/clients.ts+ newuseInfrastructureDeviceshook (list/create/update/delete), mirroringuseCurtailmentResponseProfilesFleetInfraPage/InfraDeviceListfetch devices from the API; state ownership moves fromInfraDeviceList.localDevicesinto the hook, with devices + mutation callbacks passed downManualAddSteprework for the adapter model: driver-type select (Modbus TCP, sole option) rendering a per-driver-type form module (e.g.driverForms/modbusTcp.tsx) that ownsdriver_configencode/decode; register-address field takes the raw application address with field-help copy warning against the4xxxxconvention and noting the wire-level off-by-onedriver_config)actionErrorbanner pattern); the Enabled switch reverts on failure with an error toast, sending only theoptional bool enabled(server preserves omitted fields)driver_configis redacted (empty string) forsite:read-only callers — connection summary renders an em dash or hides rather than failing to parseInfraDeviceList.test.tsx,ManualAddStep.test.tsx,InfraDeviceDetailModal.test.tsx,FleetInfraPage.test.tsx; add an API-hook test (mirroruseCurtailmentResponseProfiles.test.tsx)Out of scope (later phases of the fan integration)
facility_fan_device_ids, delays) + the device deletion reference-guard that depends on that column (PR 3)Repo hygiene
No proto/schema changes expected; routing is already wired (
/fleet/infrastructureis routed and prefetched). Feature branch (nevermain);just lintbefore PR.